mirror of
				http://git.whoc.org.uk/git/password-manager.git
				synced 2025-11-03 20:47:35 +01:00 
			
		
		
		
	Merged all pending work done on the private repository
This commit is contained in:
		
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -26,7 +26,7 @@
 | 
			
		||||
				"ligatures": "generate password",
 | 
			
		||||
				"name": "key"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 1,
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setId": 7,
 | 
			
		||||
			"iconIdx": 141
 | 
			
		||||
		},
 | 
			
		||||
@@ -55,7 +55,7 @@
 | 
			
		||||
				"ligatures": "view password",
 | 
			
		||||
				"name": "eye"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 1,
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setId": 7,
 | 
			
		||||
			"iconIdx": 206
 | 
			
		||||
		},
 | 
			
		||||
@@ -81,7 +81,7 @@
 | 
			
		||||
				"name": "tag",
 | 
			
		||||
				"ligatures": "tag"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 0
 | 
			
		||||
		},
 | 
			
		||||
@@ -108,7 +108,7 @@
 | 
			
		||||
				"name": "tags",
 | 
			
		||||
				"ligatures": "tags"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 1
 | 
			
		||||
		},
 | 
			
		||||
@@ -135,7 +135,7 @@
 | 
			
		||||
				"name": "clock",
 | 
			
		||||
				"ligatures": "recent"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 2
 | 
			
		||||
		},
 | 
			
		||||
@@ -164,7 +164,7 @@
 | 
			
		||||
				"name": "spinner",
 | 
			
		||||
				"ligatures": "loading"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 3
 | 
			
		||||
		},
 | 
			
		||||
@@ -192,7 +192,7 @@
 | 
			
		||||
				"name": "search",
 | 
			
		||||
				"ligatures": "search"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 4
 | 
			
		||||
		},
 | 
			
		||||
@@ -220,7 +220,7 @@
 | 
			
		||||
				"name": "locked",
 | 
			
		||||
				"ligatures": "locked"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 6
 | 
			
		||||
		},
 | 
			
		||||
@@ -246,7 +246,7 @@
 | 
			
		||||
				"name": "unlocked",
 | 
			
		||||
				"ligatures": "unlocked"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 7
 | 
			
		||||
		},
 | 
			
		||||
@@ -275,7 +275,7 @@
 | 
			
		||||
				"name": "menu",
 | 
			
		||||
				"ligatures": "menu"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 8
 | 
			
		||||
		},
 | 
			
		||||
@@ -304,7 +304,7 @@
 | 
			
		||||
				"name": "close",
 | 
			
		||||
				"ligatures": "failure, failed, delete, clear, cancel, close"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 9
 | 
			
		||||
		},
 | 
			
		||||
@@ -333,7 +333,7 @@
 | 
			
		||||
				"name": "checkmark",
 | 
			
		||||
				"ligatures": "done, ok, save"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 2,
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setId": 6,
 | 
			
		||||
			"iconIdx": 10
 | 
			
		||||
		},
 | 
			
		||||
@@ -348,8 +348,10 @@
 | 
			
		||||
				"grid": 0,
 | 
			
		||||
				"tags": [
 | 
			
		||||
					"commands"
 | 
			
		||||
				]
 | 
			
		||||
				],
 | 
			
		||||
				"attrs": []
 | 
			
		||||
			},
 | 
			
		||||
			"attrs": [],
 | 
			
		||||
			"properties": {
 | 
			
		||||
				"order": 18,
 | 
			
		||||
				"id": 1,
 | 
			
		||||
@@ -358,7 +360,7 @@
 | 
			
		||||
				"name": "commands",
 | 
			
		||||
				"ligatures": "commands"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setIdx": 4,
 | 
			
		||||
			"setId": 4,
 | 
			
		||||
			"iconIdx": 0
 | 
			
		||||
		},
 | 
			
		||||
@@ -371,8 +373,10 @@
 | 
			
		||||
				"grid": 0,
 | 
			
		||||
				"tags": [
 | 
			
		||||
					"logo"
 | 
			
		||||
				]
 | 
			
		||||
				],
 | 
			
		||||
				"attrs": []
 | 
			
		||||
			},
 | 
			
		||||
			"attrs": [],
 | 
			
		||||
			"properties": {
 | 
			
		||||
				"order": 4,
 | 
			
		||||
				"id": 0,
 | 
			
		||||
@@ -381,7 +385,7 @@
 | 
			
		||||
				"name": "logo",
 | 
			
		||||
				"ligatures": "clipperz"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 3,
 | 
			
		||||
			"setIdx": 4,
 | 
			
		||||
			"setId": 4,
 | 
			
		||||
			"iconIdx": 1
 | 
			
		||||
		},
 | 
			
		||||
@@ -398,8 +402,10 @@
 | 
			
		||||
					"envelope",
 | 
			
		||||
					"contact"
 | 
			
		||||
				],
 | 
			
		||||
				"grid": 20
 | 
			
		||||
				"grid": 20,
 | 
			
		||||
				"attrs": []
 | 
			
		||||
			},
 | 
			
		||||
			"attrs": [],
 | 
			
		||||
			"properties": {
 | 
			
		||||
				"id": 4,
 | 
			
		||||
				"order": 19,
 | 
			
		||||
@@ -408,7 +414,7 @@
 | 
			
		||||
				"name": "mail",
 | 
			
		||||
				"ligatures": "email"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 6,
 | 
			
		||||
			"setIdx": 7,
 | 
			
		||||
			"setId": 0,
 | 
			
		||||
			"iconIdx": 4
 | 
			
		||||
		},
 | 
			
		||||
@@ -422,8 +428,10 @@
 | 
			
		||||
					"popout",
 | 
			
		||||
					"new window"
 | 
			
		||||
				],
 | 
			
		||||
				"grid": 20
 | 
			
		||||
				"grid": 20,
 | 
			
		||||
				"attrs": []
 | 
			
		||||
			},
 | 
			
		||||
			"attrs": [],
 | 
			
		||||
			"properties": {
 | 
			
		||||
				"id": 35,
 | 
			
		||||
				"order": 9,
 | 
			
		||||
@@ -432,7 +440,7 @@
 | 
			
		||||
				"name": "popup",
 | 
			
		||||
				"ligatures": "url, direct login"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 6,
 | 
			
		||||
			"setIdx": 7,
 | 
			
		||||
			"setId": 0,
 | 
			
		||||
			"iconIdx": 35
 | 
			
		||||
		},
 | 
			
		||||
@@ -446,17 +454,19 @@
 | 
			
		||||
					"add",
 | 
			
		||||
					"sum"
 | 
			
		||||
				],
 | 
			
		||||
				"grid": 20
 | 
			
		||||
				"grid": 20,
 | 
			
		||||
				"attrs": []
 | 
			
		||||
			},
 | 
			
		||||
			"attrs": [],
 | 
			
		||||
			"properties": {
 | 
			
		||||
				"id": 125,
 | 
			
		||||
				"order": 14,
 | 
			
		||||
				"prevSize": 32,
 | 
			
		||||
				"code": 58895,
 | 
			
		||||
				"name": "plus",
 | 
			
		||||
				"ligatures": "add new field"
 | 
			
		||||
				"ligatures": "add new field, create new OTP"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 6,
 | 
			
		||||
			"setIdx": 7,
 | 
			
		||||
			"setId": 0,
 | 
			
		||||
			"iconIdx": 125
 | 
			
		||||
		},
 | 
			
		||||
@@ -472,17 +482,19 @@
 | 
			
		||||
					"remove",
 | 
			
		||||
					"delete"
 | 
			
		||||
				],
 | 
			
		||||
				"grid": 20
 | 
			
		||||
				"grid": 20,
 | 
			
		||||
				"attrs": []
 | 
			
		||||
			},
 | 
			
		||||
			"attrs": [],
 | 
			
		||||
			"properties": {
 | 
			
		||||
				"id": 126,
 | 
			
		||||
				"order": 17,
 | 
			
		||||
				"prevSize": 32,
 | 
			
		||||
				"code": 58898,
 | 
			
		||||
				"name": "cross",
 | 
			
		||||
				"ligatures": "remove field, remove tag"
 | 
			
		||||
				"ligatures": "remove field, remove tag, remove OTP"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 6,
 | 
			
		||||
			"setIdx": 7,
 | 
			
		||||
			"setId": 0,
 | 
			
		||||
			"iconIdx": 126
 | 
			
		||||
		},
 | 
			
		||||
@@ -496,8 +508,10 @@
 | 
			
		||||
					"add",
 | 
			
		||||
					"sum"
 | 
			
		||||
				],
 | 
			
		||||
				"grid": 20
 | 
			
		||||
				"grid": 20,
 | 
			
		||||
				"attrs": []
 | 
			
		||||
			},
 | 
			
		||||
			"attrs": [],
 | 
			
		||||
			"properties": {
 | 
			
		||||
				"id": 128,
 | 
			
		||||
				"order": 20,
 | 
			
		||||
@@ -506,7 +520,7 @@
 | 
			
		||||
				"name": "plus3",
 | 
			
		||||
				"ligatures": "add card"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 6,
 | 
			
		||||
			"setIdx": 7,
 | 
			
		||||
			"setId": 0,
 | 
			
		||||
			"iconIdx": 128
 | 
			
		||||
		},
 | 
			
		||||
@@ -520,8 +534,10 @@
 | 
			
		||||
					"left",
 | 
			
		||||
					"previous"
 | 
			
		||||
				],
 | 
			
		||||
				"grid": 20
 | 
			
		||||
				"grid": 20,
 | 
			
		||||
				"attrs": []
 | 
			
		||||
			},
 | 
			
		||||
			"attrs": [],
 | 
			
		||||
			"properties": {
 | 
			
		||||
				"id": 205,
 | 
			
		||||
				"order": 11,
 | 
			
		||||
@@ -530,7 +546,7 @@
 | 
			
		||||
				"name": "arrow-left",
 | 
			
		||||
				"ligatures": "back"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 6,
 | 
			
		||||
			"setIdx": 7,
 | 
			
		||||
			"setId": 0,
 | 
			
		||||
			"iconIdx": 205
 | 
			
		||||
		},
 | 
			
		||||
@@ -544,8 +560,10 @@
 | 
			
		||||
					"right",
 | 
			
		||||
					"next"
 | 
			
		||||
				],
 | 
			
		||||
				"grid": 20
 | 
			
		||||
				"grid": 20,
 | 
			
		||||
				"attrs": []
 | 
			
		||||
			},
 | 
			
		||||
			"attrs": [],
 | 
			
		||||
			"properties": {
 | 
			
		||||
				"id": 208,
 | 
			
		||||
				"order": 12,
 | 
			
		||||
@@ -554,7 +572,7 @@
 | 
			
		||||
				"name": "arrow-right",
 | 
			
		||||
				"ligatures": "show detail"
 | 
			
		||||
			},
 | 
			
		||||
			"setIdx": 6,
 | 
			
		||||
			"setIdx": 7,
 | 
			
		||||
			"setId": 0,
 | 
			
		||||
			"iconIdx": 208
 | 
			
		||||
		}
 | 
			
		||||
@@ -584,7 +602,8 @@
 | 
			
		||||
			"showMetadata": false,
 | 
			
		||||
			"autoHost": false,
 | 
			
		||||
			"embed": true,
 | 
			
		||||
			"showVersion": true
 | 
			
		||||
			"showVersion": true,
 | 
			
		||||
			"ligaReset": "tags"
 | 
			
		||||
		},
 | 
			
		||||
		"imagePref": {
 | 
			
		||||
			"color": 0,
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,8 @@ Clipperz_normalizedNewLine = '\x0d\x0a';
 | 
			
		||||
//		Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline({ 'dataStore':dataStore, 'type':'OFFLINE_COPY', 'typeDescription':'Offline copy'});
 | 
			
		||||
		Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline({ 'dataStore':dataStore, 'type':'ONLINE', 'typeDescription':'Offline copy'});
 | 
			
		||||
		Clipperz.PM.Proxy.defaultProxy.dataStore().setupWithEncryptedData(testData);
 | 
			
		||||
 | 
			
		||||
	</script>
 | 
			
		||||
	<script>
 | 
			
		||||
		//	Live Reload hoock
 | 
			
		||||
		document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')
 | 
			
		||||
	</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,7 @@ MochiKit.Base.update(Clipperz.Base, {
 | 
			
		||||
			return MochiKit.Base.compare(a[aKey].toLowerCase(), b[aKey].toLowerCase());
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
	'dependsOn': function(module, deps) {
 | 
			
		||||
@@ -111,6 +112,10 @@ MochiKit.Base.update(Clipperz.Base, {
 | 
			
		||||
		return aValue.replace(/^\s+|\s+$/g, "");
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'zipWithRange': function (anArray) {
 | 
			
		||||
		return MochiKit.Base.zip(MochiKit.Iter.range(anArray.length), anArray);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'stringToByteArray': function (aValue) {
 | 
			
		||||
 
 | 
			
		||||
@@ -240,13 +240,13 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'updateCredentials': function (aUsername, aPassphrase, someUserData) {
 | 
			
		||||
	'updateCredentials': function (someData) {
 | 
			
		||||
		var	deferredResult;
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("Connection.updateCredentials", {trace:false});
 | 
			
		||||
		deferredResult.collectResults({
 | 
			
		||||
			'credentials': [
 | 
			
		||||
				MochiKit.Base.method(this, 'normalizedCredentials', {username:aUsername, password:aPassphrase}),
 | 
			
		||||
				MochiKit.Base.method(this, 'normalizedCredentials', {username:someData['newUsername'], password:someData['newPassphrase']}),
 | 
			
		||||
				MochiKit.Base.bind(function(someCredentials) {
 | 
			
		||||
					var srpConnection;
 | 
			
		||||
					var result;
 | 
			
		||||
@@ -258,15 +258,13 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
 | 
			
		||||
					return result;
 | 
			
		||||
				}, this)
 | 
			
		||||
			],
 | 
			
		||||
			'user':		MochiKit.Base.partial(MochiKit.Async.succeed, someUserData)
 | 
			
		||||
			'user':		MochiKit.Base.partial(MochiKit.Async.succeed, someData['user']),
 | 
			
		||||
			'oneTimePasswords': MochiKit.Base.partial(MochiKit.Async.succeed, someData['oneTimePasswords'])
 | 
			
		||||
		});
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
 | 
			
		||||
		deferredResult.addMethod(this, 'message', 'upgradeUserCredentials');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
		
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
		
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
@@ -309,12 +307,12 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
 | 
			
		||||
			'message': 'oneTimePassword',
 | 
			
		||||
			'version': Clipperz.PM.Connection.communicationProtocol.currentVersion,
 | 
			
		||||
			'parameters': {
 | 
			
		||||
				'oneTimePasswordKey':			Clipperz.PM.DataModel.OneTimePassword.computeKeyWithUsernameAndPassword(someParameters['username'], normalizedOTP),
 | 
			
		||||
				'oneTimePasswordKey':			Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword(normalizedOTP),
 | 
			
		||||
				'oneTimePasswordKeyChecksum':	Clipperz.PM.DataModel.OneTimePassword.computeKeyChecksumWithUsernameAndPassword(someParameters['username'], normalizedOTP)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return Clipperz.Async.callbacks("Connction.redeemOTP", [
 | 
			
		||||
		return Clipperz.Async.callbacks("Connction.redeemOneTimePassword", [
 | 
			
		||||
			MochiKit.Base.method(this.proxy(), 'handshake', args),
 | 
			
		||||
			function(aResult) {
 | 
			
		||||
				return Clipperz.PM.Crypto.deferredDecrypt({
 | 
			
		||||
@@ -364,8 +362,6 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
 | 
			
		||||
			return result;
 | 
			
		||||
		});
 | 
			
		||||
		deferredResult.addMethod(this.proxy(), 'handshake');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, this, 'updatedProgressState', 'connection_credentialVerification');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
 | 
			
		||||
		deferredResult.addCallback(function(someParameters) {
 | 
			
		||||
			var result;
 | 
			
		||||
 | 
			
		||||
@@ -415,10 +411,6 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
 | 
			
		||||
 | 
			
		||||
			return someParameters;
 | 
			
		||||
		}, this));
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, this, 'updatedProgressState', 'connection_loggedIn');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
 | 
			
		||||
//		deferredResult.addCallback(MochiKit.Async.succeed, {result:"done"});
 | 
			
		||||
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
		
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
 
 | 
			
		||||
@@ -120,7 +120,8 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.DirectLogin, Object, {
 | 
			
		||||
	'setLabelKeepingBackwardCompatibilityWithBeta': function (aValue) {
 | 
			
		||||
		return Clipperz.Async.callbacks("DirectLogin.setLabelKeepingBackwardCompatibilityWithBeta", [
 | 
			
		||||
			MochiKit.Base.method(this, 'setIndexDataForKey', 'label', aValue),
 | 
			
		||||
			MochiKit.Base.method(this, 'setValue', 'label', aValue)
 | 
			
		||||
			MochiKit.Base.method(this, 'setValue', 'label', aValue),
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Async.succeed, this),
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -497,7 +498,8 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.DirectLogin, Object, {
 | 
			
		||||
			MochiKit.Base.method(this, 'updateFormValuesAfterChangingBookmarkletConfiguration'),
 | 
			
		||||
			MochiKit.Base.method(this, 'updateBindingsAfterChangingBookmarkletConfiguration'),
 | 
			
		||||
			
 | 
			
		||||
			MochiKit.Base.noop
 | 
			
		||||
//			MochiKit.Base.noop
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Async.succeed, this),
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -607,6 +609,20 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.DirectLogin, Object, {
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'setBindings': function (someBindings, originalFields) {
 | 
			
		||||
		var	self = this;
 | 
			
		||||
 | 
			
		||||
		return Clipperz.Async.callbacks("DirectLogin.setBindings", [
 | 
			
		||||
			function () {
 | 
			
		||||
				return MochiKit.Base.map(function (aBindingInfo) {
 | 
			
		||||
					return self.bindFormFieldWithLabelToRecordFieldWithLabel(aBindingInfo[0], originalFields[aBindingInfo[1]]['label']);
 | 
			
		||||
				}, MochiKit.Base.zip(MochiKit.Base.keys(someBindings), MochiKit.Base.values(someBindings)));
 | 
			
		||||
			},
 | 
			
		||||
			Clipperz.Async.collectAll,
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Async.succeed, this),
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
	'bindingValues': function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,7 @@ Clipperz.PM.DataModel.EncryptedRemoteObject = function(args) {
 | 
			
		||||
//
 | 
			
		||||
//	getRemoteData
 | 
			
		||||
//		unpackRemoteData
 | 
			
		||||
//			getDecryptData [encryptedDataKeypath, encryptedVersionKeypath]
 | 
			
		||||
//			getDecryptedData [encryptedDataKeypath, encryptedVersionKeypath]
 | 
			
		||||
//				unpackData
 | 
			
		||||
//				
 | 
			
		||||
//				
 | 
			
		||||
 
 | 
			
		||||
@@ -31,18 +31,13 @@ if (typeof(Clipperz.PM.DataModel) == 'undefined') { Clipperz.PM.DataModel = {};
 | 
			
		||||
Clipperz.PM.DataModel.OneTimePassword = function(args) {
 | 
			
		||||
	args = args || {};
 | 
			
		||||
 | 
			
		||||
//	this._user = args['user'];
 | 
			
		||||
	this._username		= args['username'];
 | 
			
		||||
	this._passphraseCallback = args['passphraseCallback'];
 | 
			
		||||
	this._reference		= args['reference']	|| Clipperz.PM.Crypto.randomKey();
 | 
			
		||||
	this._password		= args['password'];
 | 
			
		||||
	this._passwordValue = Clipperz.PM.DataModel.OneTimePassword.normalizedOneTimePassword(args['password']);
 | 
			
		||||
	this._creationDate	= args['created']	? Clipperz.PM.Date.parseDateWithUTCFormat(args['created'])		: new Date();
 | 
			
		||||
	this._usageDate		= args['used']		? Clipperz.PM.Date.parseDateWithUTCFormat(args['used'])			: null;
 | 
			
		||||
		
 | 
			
		||||
	this._status		= args['status']	|| 'ACTIVE';	//	'REQUESTED', 'USED', 'DISABLED'
 | 
			
		||||
	this._connectionInfo= null;
 | 
			
		||||
	
 | 
			
		||||
	this._key			= null;
 | 
			
		||||
	this._keyChecksum	= null;
 | 
			
		||||
	this._label			= args['label'] || "";
 | 
			
		||||
	this._usageDate		= args['usageDate'] || null; // Usage date is stored when the client is sure that the otp was used
 | 
			
		||||
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
@@ -52,19 +47,35 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
 | 
			
		||||
	'toString': function() {
 | 
			
		||||
		return "Clipperz.PM.DataModel.OneTimePassword";
 | 
			
		||||
	},
 | 
			
		||||
/*
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'user': function() {
 | 
			
		||||
		return this._user;
 | 
			
		||||
	'username': function() {
 | 
			
		||||
		return this._username;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
	'passphraseCallback': function () {
 | 
			
		||||
		return this._passphraseCallback;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'setPassphraseCallback': function(aPassphraseCallback) {
 | 
			
		||||
		this._passphraseCallback = aPassphraseCallback;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'password': function() {
 | 
			
		||||
		return this._password;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'label': function() {
 | 
			
		||||
		return this._label;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'usageDate': function() {
 | 
			
		||||
		return this._usageDate;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'setUsageDate': function(aDate) {
 | 
			
		||||
		this._usageDate = aDate;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'passwordValue': function() {
 | 
			
		||||
@@ -73,12 +84,6 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'creationDate': function() {
 | 
			
		||||
		return this._creationDate;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'reference': function() {
 | 
			
		||||
		return this._reference;
 | 
			
		||||
	},
 | 
			
		||||
@@ -86,51 +91,18 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'key': function() {
 | 
			
		||||
		if (this._key == null) {
 | 
			
		||||
			this._key = Clipperz.PM.DataModel.OneTimePassword.computeKeyWithUsernameAndPassword(this.user().username(), this.passwordValue());
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return this._key;
 | 
			
		||||
		return Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword(this.passwordValue());
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'keyChecksum': function() {
 | 
			
		||||
		if (this._keyChecksum == null) {
 | 
			
		||||
			this._keyChecksum = Clipperz.PM.DataModel.OneTimePassword.computeKeyChecksumWithUsernameAndPassword(this.user().username(), this.passwordValue());
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return this._keyChecksum;
 | 
			
		||||
	},
 | 
			
		||||
*/
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'status': function() {
 | 
			
		||||
		return this._status;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	'setStatus': function(aValue) {
 | 
			
		||||
		this._status = aValue;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
	'serializedData': function() {
 | 
			
		||||
		var result;
 | 
			
		||||
		
 | 
			
		||||
		result = {
 | 
			
		||||
			'password': 	this.password(),
 | 
			
		||||
			'created':		this.creationDate()		? Clipperz.PM.Date.formatDateWithUTCFormat(this.creationDate())		: null,
 | 
			
		||||
			'used':			this.usageDate()		? Clipperz.PM.Date.formatDateWithUTCFormat(this.usageDate())		: null,
 | 
			
		||||
			'status':	 	this.status()
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		return result;
 | 
			
		||||
		return Clipperz.PM.DataModel.OneTimePassword.computeKeyChecksumWithUsernameAndPassword(this.username(), this.passwordValue());
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'packedPassphrase': function() {
 | 
			
		||||
	'packedPassphrase': function(aPassphrase) {
 | 
			
		||||
		var result;
 | 
			
		||||
		var packedPassphrase;
 | 
			
		||||
		var encodedPassphrase;
 | 
			
		||||
@@ -140,32 +112,29 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
 | 
			
		||||
		
 | 
			
		||||
		getRandomBytes = MochiKit.Base.method(Clipperz.Crypto.PRNG.defaultRandomGenerator(), 'getRandomBytes');
 | 
			
		||||
		
 | 
			
		||||
		encodedPassphrase = new Clipperz.ByteArray(this.user().passphrase()).toBase64String();
 | 
			
		||||
//Clipperz.logDebug("--- encodedPassphrase.length: " + encodedPassphrase.length);
 | 
			
		||||
		encodedPassphrase = new Clipperz.ByteArray(aPassphrase).toBase64String();
 | 
			
		||||
		prefixPadding = getRandomBytes(getRandomBytes(1).byteAtIndex(0)).toBase64String();
 | 
			
		||||
//Clipperz.logDebug("--- prefixPadding.length: " + prefixPadding.length);
 | 
			
		||||
		suffixPadding = getRandomBytes((500 - prefixPadding.length - encodedPassphrase.length) * 6 / 8).toBase64String();
 | 
			
		||||
//Clipperz.logDebug("--- suffixPadding.length: " + suffixPadding.length);
 | 
			
		||||
//Clipperz.logDebug("--- total.length: " + (prefixPadding.length + encodedPassphrase.length + suffixPadding.length));
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
		packedPassphrase = {
 | 
			
		||||
			'prefix': prefixPadding,
 | 
			
		||||
			'passphrase': encodedPassphrase,
 | 
			
		||||
			'suffix': suffixPadding
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
//		result = Clipperz.Base.serializeJSON(packedPassphrase);
 | 
			
		||||
		result = packedPassphrase;
 | 
			
		||||
//Clipperz.logDebug("===== OTP packedPassprase: [" + result.length + "]" + result);
 | 
			
		||||
//Clipperz.logDebug("<<< OneTimePassword.packedPassphrase");
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'encryptedPackedPassphrase': function() {
 | 
			
		||||
		return Clipperz.PM.Crypto.deferredEncryptWithCurrentVersion(this.passwordValue(), this.packedPassphrase())
 | 
			
		||||
	'encryptedPackedPassphrase': function(aPassphrase) {
 | 
			
		||||
		return Clipperz.PM.Crypto.deferredEncrypt({
 | 
			
		||||
			'key':		this.passwordValue(),
 | 
			
		||||
			'value':	this.packedPassphrase(aPassphrase),
 | 
			
		||||
			'version':	Clipperz.PM.Crypto.encryptingFunctions.currentVersion
 | 
			
		||||
		})
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
@@ -174,8 +143,6 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
		var	result;
 | 
			
		||||
 | 
			
		||||
//Clipperz.logDebug(">>> OneTimePassword.encryptedData");
 | 
			
		||||
//Clipperz.logDebug("--- OneTimePassword.encryptedData - id: " + this.reference());
 | 
			
		||||
		result = {
 | 
			
		||||
			'reference': this.reference(),
 | 
			
		||||
			'key': this.key(),
 | 
			
		||||
@@ -183,116 +150,26 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
 | 
			
		||||
			'data': "",
 | 
			
		||||
			'version': Clipperz.PM.Crypto.encryptingFunctions.currentVersion
 | 
			
		||||
		}
 | 
			
		||||
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 2: " + Clipperz.Base.serializeJSON(result));
 | 
			
		||||
		deferredResult = new MochiKit.Async.Deferred();
 | 
			
		||||
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 3");
 | 
			
		||||
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.encryptedData - 1: " + res); return res;});
 | 
			
		||||
//#		deferredResult.addCallback(Clipperz.PM.Crypto.deferredEncryptWithCurrentVersion, this.passwordValue(), this.packedPassphrase());
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("OneTimePassword.encryptedData", {trace: false});
 | 
			
		||||
		deferredResult.addCallback(this.passphraseCallback());
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.method(this, 'encryptedPackedPassphrase'));
 | 
			
		||||
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 4");
 | 
			
		||||
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.encryptedData - 2: [" + res.length + "]" + res); return res;});
 | 
			
		||||
		deferredResult.addCallback(function(aResult, res) {
 | 
			
		||||
			aResult['data'] = res;
 | 
			
		||||
			return aResult;
 | 
			
		||||
		}, result);
 | 
			
		||||
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 5");
 | 
			
		||||
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.encryptedData - 3: " + Clipperz.Base.serializeJSON(res)); return res;});
 | 
			
		||||
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 6");
 | 
			
		||||
		
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'saveChanges': function() {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
		var	result;
 | 
			
		||||
 | 
			
		||||
//Clipperz.logDebug(">>> OneTimePassword.saveChanges");
 | 
			
		||||
		result = {};
 | 
			
		||||
		deferredResult = new MochiKit.Async.Deferred();
 | 
			
		||||
 | 
			
		||||
		deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'updatedProgressState', 'saveOTP_encryptUserData');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.method(this.user(), 'encryptedData'));
 | 
			
		||||
		deferredResult.addCallback(function(aResult, res) {
 | 
			
		||||
			aResult['user'] = res;
 | 
			
		||||
			return aResult;
 | 
			
		||||
		}, result);
 | 
			
		||||
 | 
			
		||||
		deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'updatedProgressState', 'saveOTP_encryptOTPData');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.method(this, 'encryptedData'));
 | 
			
		||||
		deferredResult.addCallback(function(aResult, res) {
 | 
			
		||||
			aResult['oneTimePassword'] = res;
 | 
			
		||||
			return aResult;
 | 
			
		||||
		}, result);
 | 
			
		||||
 | 
			
		||||
		deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'updatedProgressState', 'saveOTP_sendingData');
 | 
			
		||||
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.saveChanges - 1: " + Clipperz.Base.serializeJSON(res)); return res;});
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.method(this.user().connection(), 'message'), 'addNewOneTimePassword');
 | 
			
		||||
 | 
			
		||||
		deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'updatedProgressState', 'saveOTP_updatingInterface');
 | 
			
		||||
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.saveChanges - 2: " + res); return res;});
 | 
			
		||||
		deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'notify', 'OTPUpdated');
 | 
			
		||||
		deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'oneTimePassword_saveChanges_done', null);
 | 
			
		||||
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.saveChanges - 2: " + res); return res;});
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
//Clipperz.logDebug("<<< OneTimePassword.saveChanges");
 | 
			
		||||
		
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'usageDate': function() {
 | 
			
		||||
		return this._usageDate;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'setUsageDate': function(aValue) {
 | 
			
		||||
		this._usageDate = aValue;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'connectionInfo': function() {
 | 
			
		||||
		return this._connectionInfo;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'setConnectionInfo': function(aValue) {
 | 
			
		||||
		this._connectionInfo = aValue;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'isExpired': function() {
 | 
			
		||||
		return (this.usageDate() != null);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'updateStatusWithValues': function(someValues) {
 | 
			
		||||
		var result;
 | 
			
		||||
 | 
			
		||||
		result = false;
 | 
			
		||||
		
 | 
			
		||||
		if (someValues['status'] != this.status()) {
 | 
			
		||||
			result = true;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		this.setStatus(someValues['status']);
 | 
			
		||||
		this.setUsageDate(Clipperz.PM.Date.parseDateWithUTCFormat(someValues['requestDate']));
 | 
			
		||||
		this.setConnectionInfo(someValues['connection']);
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
*/	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
	__syntaxFix__: "syntax fix"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//#############################################################################
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.DataModel.OneTimePassword.computeKeyWithUsernameAndPassword = function(anUsername, aPassword) {
 | 
			
		||||
Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword = function(aPassword) {
 | 
			
		||||
	return Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aPassword)).toHexString().substring(2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -348,3 +225,32 @@ Clipperz.PM.DataModel.OneTimePassword.normalizedOneTimePassword = function(aPass
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//#############################################################################
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.DataModel.OneTimePassword.generateRandomBase32OTPValue = function() {
 | 
			
		||||
		var randomValue;
 | 
			
		||||
		var	result;
 | 
			
		||||
 | 
			
		||||
		randomValue = Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(160/8);
 | 
			
		||||
		result = randomValue.toBase32String();
 | 
			
		||||
		result = result.replace(/.{4}\B/g, '$&' + ' ');
 | 
			
		||||
		result = result.replace(/(.{4} ){2}/g, '$&' + '- ');
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
//#############################################################################
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.DataModel.OneTimePassword.createNewOneTimePassword = function(aUsername, aPassphraseCallback) {
 | 
			
		||||
	var result;
 | 
			
		||||
	var password;
 | 
			
		||||
	
 | 
			
		||||
	password = Clipperz.PM.DataModel.OneTimePassword.generateRandomBase32OTPValue();
 | 
			
		||||
	result = new Clipperz.PM.DataModel.OneTimePassword({
 | 
			
		||||
		'username':	aUsername,
 | 
			
		||||
		'passphraseCallback': aPassphraseCallback,
 | 
			
		||||
		'password': password,
 | 
			
		||||
		'label': ""
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
@@ -175,7 +175,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record.Version.Field, Object, {
 | 
			
		||||
		deferredResult.addMethod(this, 'actionType');
 | 
			
		||||
		deferredResult.addCallback(function (aValue) { fieldValues['actionType'] = aValue; });
 | 
			
		||||
		deferredResult.addMethod(this, 'isHidden');
 | 
			
		||||
		deferredResult.addCallback(function (aValue) { fieldValues['isHidden'] = aValue; });
 | 
			
		||||
		deferredResult.addCallback(function (aValue) { fieldValues['hidden'] = aValue; });
 | 
			
		||||
		deferredResult.addCallback(function () { return fieldValues; });
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -327,16 +327,15 @@ console.log("Record.Version.hasPendingChanges");
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred('Record.Version.export', {trace:false});
 | 
			
		||||
		deferredResult.addMethod(this,'fields');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.values);
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.map, function(fieldIn) {
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.map, function (fieldIn) {
 | 
			
		||||
			return fieldIn.content();
 | 
			
		||||
		});
 | 
			
		||||
		deferredResult.addCallback(Clipperz.Async.collectAll);
 | 
			
		||||
		deferredResult.addCallback(function(listIn) {
 | 
			
		||||
//			return listIn.reduce(function(result, field) {
 | 
			
		||||
			return MochiKit.Iter.reduce(function(result, field) {
 | 
			
		||||
				var ref = field.reference;
 | 
			
		||||
				var ref = field['reference'];
 | 
			
		||||
				result[ref] = field;
 | 
			
		||||
				delete result[ref].reference;
 | 
			
		||||
				delete result[ref]['reference'];
 | 
			
		||||
				return result;
 | 
			
		||||
			}, listIn, {});
 | 
			
		||||
		});
 | 
			
		||||
 
 | 
			
		||||
@@ -170,7 +170,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//............................................................................
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	'label': function () {
 | 
			
		||||
		return Clipperz.Async.callbacks("Record.label", [
 | 
			
		||||
			MochiKit.Base.method(this, 'fullLabel'),
 | 
			
		||||
@@ -1128,7 +1128,8 @@ console.log("Record.hasPendingChanges RESULT", result);
 | 
			
		||||
			Clipperz.Async.collectAll,
 | 
			
		||||
 | 
			
		||||
			MochiKit.Base.method(aRecord, 'directLogins'), MochiKit.Base.values,
 | 
			
		||||
//function (aValue) { console.log("-> DirectLogin Values", aValue); return aValue; },
 | 
			
		||||
//function (aValue) { console.log("-> SETUP WITH RECORD: DirectLogin Values", aValue); return aValue; },
 | 
			
		||||
			//	TODO: possibly broken implementation of direct login cloning
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addDirectLogin')),
 | 
			
		||||
//function (aValue) { console.log("-> DirectLogin Values", aValue); return aValue; },
 | 
			
		||||
//			Clipperz.Async.collectAll,
 | 
			
		||||
@@ -1136,16 +1137,25 @@ console.log("Record.hasPendingChanges RESULT", result);
 | 
			
		||||
			MochiKit.Base.bind(function () { return this; }, this)
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	'directLoginWithJsonData': function (someData) {
 | 
			
		||||
		var	result;
 | 
			
		||||
 | 
			
		||||
	'setUpWithJSON': function(data) {
 | 
			
		||||
		result = new Clipperz.PM.DataModel.DirectLogin({'record': this});
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'setUpWithJSON': function(data, labelPostfix) {
 | 
			
		||||
		return Clipperz.Async.callbacks("Record.setUpWithJSON", [
 | 
			
		||||
			// TODO: proper tag handling
 | 
			
		||||
			MochiKit.Base.method(this,'setLabel',data.label),
 | 
			
		||||
			MochiKit.Base.method(this,'setNotes',data.data.notes),
 | 
			
		||||
			MochiKit.Base.method(this,'setLabel', data['label'] + ((labelPostfix) ? labelPostfix : '')),
 | 
			
		||||
			MochiKit.Base.method(this,'setNotes', data['data']['notes']),
 | 
			
		||||
			// TODO: check whether fields' order is kept or not
 | 
			
		||||
			function(){ return MochiKit.Base.values(data.currentVersion.fields); },
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Base.map,MochiKit.Base.method(this, 'addField')),
 | 
			
		||||
			Clipperz.Async.collectAll
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Base.values, data['currentVersion']['fields']),
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addField')),
 | 
			
		||||
			Clipperz.Async.collectAll,
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Async.succeed, this),
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -1174,12 +1184,12 @@ console.log("Record.hasPendingChanges RESULT", result);
 | 
			
		||||
		var label;
 | 
			
		||||
		var data;
 | 
			
		||||
		var currentVersion;
 | 
			
		||||
		var directLogins;
 | 
			
		||||
//		var directLogins;
 | 
			
		||||
		var currentVersionObject;
 | 
			
		||||
 | 
			
		||||
		data = {};
 | 
			
		||||
		currentVersion = {};
 | 
			
		||||
		directLogins = {};
 | 
			
		||||
//		directLogins = {};
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred('Record.export', {trace:false});
 | 
			
		||||
		deferredResult.addMethod(this, 'getCurrentRecordVersion');
 | 
			
		||||
		deferredResult.addCallback(function(recordVersionIn) { currentVersionObject = recordVersionIn; })
 | 
			
		||||
@@ -1211,7 +1221,6 @@ console.log("Record.hasPendingChanges RESULT", result);
 | 
			
		||||
	__syntaxFix__: "syntax fix"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.DataModel.Record.defaultCardInfo = {
 | 
			
		||||
	'_rowObject':			MochiKit.Async.succeed,
 | 
			
		||||
	'_reference':			MochiKit.Base.methodcaller('reference'),
 | 
			
		||||
@@ -1277,3 +1286,14 @@ Clipperz.PM.DataModel.Record.extractTagsFromFullLabel = function (aLabel) {
 | 
			
		||||
	
 | 
			
		||||
	return result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.DataModel.Record.labelContainsTag = function (aLabel, aTag) {
 | 
			
		||||
	return MochiKit.Iter.some(
 | 
			
		||||
		MochiKit.Base.keys(Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aLabel)),
 | 
			
		||||
		MochiKit.Base.partial(MochiKit.Base.operator.eq, aTag)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.DataModel.Record.labelContainsArchiveTag = function (aLabel) {
 | 
			
		||||
	return Clipperz.PM.DataModel.Record.labelContainsTag(aLabel, Clipperz.PM.DataModel.Record.archivedTag);
 | 
			
		||||
}
 | 
			
		||||
@@ -31,7 +31,13 @@ if (typeof(Clipperz.PM.DataModel.User.Header) == 'undefined') { Clipperz.PM.Data
 | 
			
		||||
Clipperz.PM.DataModel.User.Header.OneTimePasswords = function(args) {
 | 
			
		||||
	Clipperz.PM.DataModel.User.Header.OneTimePasswords.superclass.constructor.apply(this, arguments);
 | 
			
		||||
 | 
			
		||||
	// TODO: there are still method calls around passing these values: should be cleared...
 | 
			
		||||
	this._connection = args['connection'];
 | 
			
		||||
	this._username = args['username'];
 | 
			
		||||
	this._passphraseCallback = args['retrieveKeyFunction'];
 | 
			
		||||
 | 
			
		||||
	this._oneTimePasswords = null;
 | 
			
		||||
	this._oneTimePasswordsDetails = null;
 | 
			
		||||
	
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
@@ -45,55 +51,45 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.OneTimePasswords, Clipper
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
	'packData': function (someData) {	//	++
 | 
			
		||||
		var result;
 | 
			
		||||
 | 
			
		||||
		result = Clipperz.PM.DataModel.User.Header.OneTimePasswords.superclass.packData.apply(this, arguments);
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	'connection': function() {
 | 
			
		||||
		return this._connection;
 | 
			
		||||
	},
 | 
			
		||||
*/
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
	'packRemoteData': function (someData) {
 | 
			
		||||
		var result;
 | 
			
		||||
 | 
			
		||||
		result = Clipperz.PM.DataModel.User.Header.OneTimePasswords.superclass.packRemoteData.apply(this, arguments);
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	'username': function() {
 | 
			
		||||
		return this._username;
 | 
			
		||||
	},
 | 
			
		||||
*/
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
	'prepareRemoteDataWithKey': function (aKey) {
 | 
			
		||||
		var result;
 | 
			
		||||
 | 
			
		||||
		result = Clipperz.PM.DataModel.User.Header.OneTimePasswords.superclass.prepareRemoteDataWithKey.apply(this, arguments);
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	'passphraseCallback': function() {
 | 
			
		||||
		return this._passphraseCallback;
 | 
			
		||||
	},
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	'oneTimePasswords': function () {
 | 
			
		||||
		var	deferredResult;
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("User.Header.OneTimePasswords.oneTimePasswords", {trace:false});
 | 
			
		||||
 | 
			
		||||
		// TODO: change with transient state
 | 
			
		||||
		// Also, OTPs created here don't store username, making it impossible to generate the key checksum (shouldn't be used anywhere, but probably the design should be changed)
 | 
			
		||||
		if (this._oneTimePasswords == null) {
 | 
			
		||||
			deferredResult.addMethod(this, 'values')
 | 
			
		||||
			deferredResult.addMethod(this, 'values');
 | 
			
		||||
			deferredResult.addCallback(MochiKit.Base.bind(function (someData) {
 | 
			
		||||
				var	otpKey;
 | 
			
		||||
				
 | 
			
		||||
				this._oneTimePasswords = {};
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
				for (otpKey in someData) {
 | 
			
		||||
					var otp;
 | 
			
		||||
					var otpParameters;
 | 
			
		||||
					
 | 
			
		||||
					otpParameters = Clipperz.Base.deepClone(someData[otpKey]);
 | 
			
		||||
					otpParameters['reference'] = otpKey;
 | 
			
		||||
					
 | 
			
		||||
					otpParameters['username'] = this.username();
 | 
			
		||||
					otpParameters['passphraseCallback'] = this.passphraseCallback();
 | 
			
		||||
					otpParameters['usageDate'] = someData[otpKey]['usageDate'] || null;
 | 
			
		||||
 | 
			
		||||
					otp = new Clipperz.PM.DataModel.OneTimePassword(otpParameters);
 | 
			
		||||
					this._oneTimePasswords[otpKey] = otp;
 | 
			
		||||
				}
 | 
			
		||||
@@ -109,6 +105,182 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.OneTimePasswords, Clipper
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'oneTimePasswordsDetails': function() {
 | 
			
		||||
		if (this._oneTimePasswordsDetails) {
 | 
			
		||||
			return MochiKit.Async.succeed(this._oneTimePasswordsDetails);
 | 
			
		||||
		} else {
 | 
			
		||||
			return Clipperz.Async.callbacks("User.oneTimePasswordsDetails", [
 | 
			
		||||
				MochiKit.Base.method(this.connection(), 'message', 'getOneTimePasswordsDetails'),
 | 
			
		||||
				MochiKit.Base.bind(function(someData) {
 | 
			
		||||
					this._oneTimePasswordsDetails = someData;
 | 
			
		||||
 | 
			
		||||
					return someData;
 | 
			
		||||
				}, this)
 | 
			
		||||
			], {trace:false});
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	'getReferenceFromKey': function(aKey) {
 | 
			
		||||
		return Clipperz.Async.callbacks("User.Header.OneTimePasswords.getReferenceFromKey", [
 | 
			
		||||
			MochiKit.Base.method(this, 'values'),
 | 
			
		||||
			function(someValues) {
 | 
			
		||||
				var result;
 | 
			
		||||
				var normalizedOTP;
 | 
			
		||||
				var i;
 | 
			
		||||
 | 
			
		||||
				result = null;
 | 
			
		||||
				for (i in someValues) {
 | 
			
		||||
					normalizedOTP = Clipperz.PM.DataModel.OneTimePassword.normalizedOneTimePassword(someValues[i]['password']);
 | 
			
		||||
 | 
			
		||||
					if (Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword(normalizedOTP) == aKey) {
 | 
			
		||||
						result = i;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return result;
 | 
			
		||||
			}
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	'createNewOTP': function (aUsername, aPassphraseCallback) {
 | 
			
		||||
		var newOneTimePassword;
 | 
			
		||||
 | 
			
		||||
		newOneTimePassword = Clipperz.PM.DataModel.OneTimePassword.createNewOneTimePassword(aUsername, aPassphraseCallback);
 | 
			
		||||
 | 
			
		||||
		// TODO: this is deferred --> change everything to deferred
 | 
			
		||||
		// TestData include 'created' and 'status'
 | 
			
		||||
		this.setValue(newOneTimePassword.reference(), {
 | 
			
		||||
			// 'created': newOneTimePassword.creationDate().toString(), // won't work: creation date is no more stored in OTP
 | 
			
		||||
			'password': newOneTimePassword.password(),
 | 
			
		||||
			'label': newOneTimePassword.label()
 | 
			
		||||
			// 'status': newOneTimePassword.status()
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this._oneTimePasswords = null;
 | 
			
		||||
		this._oneTimePasswordsDetails = null;
 | 
			
		||||
 | 
			
		||||
		return newOneTimePassword;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//.........................................................................
 | 
			
		||||
 | 
			
		||||
	'deleteOTPs': function (aList) {
 | 
			
		||||
		this._oneTimePasswords = null;
 | 
			
		||||
		this._oneTimePasswordsDetails = null;
 | 
			
		||||
 | 
			
		||||
		return Clipperz.Async.callbacks("User.Header.OneTimePasswords.deleteOTPs", [
 | 
			
		||||
			MochiKit.Base.method(this, 'values'),
 | 
			
		||||
			MochiKit.Base.keys,
 | 
			
		||||
			MochiKit.Base.bind(function(someKeys) {
 | 
			
		||||
				var result;
 | 
			
		||||
				
 | 
			
		||||
				result = [];
 | 
			
		||||
				MochiKit.Base.map(MochiKit.Base.bind(function(aList, aKey) {
 | 
			
		||||
					if (aList.indexOf(aKey) >= 0) {
 | 
			
		||||
						this.removeValue(aKey);
 | 
			
		||||
					} else {
 | 
			
		||||
						result.push(aKey);
 | 
			
		||||
					}
 | 
			
		||||
				}, this, aList), someKeys);
 | 
			
		||||
 | 
			
		||||
				return result;	// Return a list of references of the remaining OTPs, needed for the 'updateOneTimePasswords' message
 | 
			
		||||
								// Maybe this logic should be moved to another method
 | 
			
		||||
			}, this),
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//.........................................................................
 | 
			
		||||
 | 
			
		||||
	'changeOTPLabel': function (aReference, aLabel) {
 | 
			
		||||
		this._oneTimePasswords = null;
 | 
			
		||||
 | 
			
		||||
		return Clipperz.Async.callbacks("User.Header.OneTimePasswords.changeOTPLabel", [
 | 
			
		||||
			MochiKit.Base.method(this, 'getValue', aReference),
 | 
			
		||||
			function(aValue) {
 | 
			
		||||
				aValue['label'] = aLabel;
 | 
			
		||||
				return aValue;
 | 
			
		||||
			},
 | 
			
		||||
			MochiKit.Base.method(this, 'setValue', aReference)
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//.........................................................................
 | 
			
		||||
 | 
			
		||||
	'markOTPAsUsed': function(aKey) {
 | 
			
		||||
		var reference;
 | 
			
		||||
 | 
			
		||||
		this._oneTimePasswords = null;
 | 
			
		||||
 | 
			
		||||
		return Clipperz.Async.callbacks("User.Header.OneTimePasswords.markOTPAsUsed", [
 | 
			
		||||
			MochiKit.Base.method(this, 'getReferenceFromKey', aKey),
 | 
			
		||||
			function(aReference) {
 | 
			
		||||
				reference = aReference;
 | 
			
		||||
				return aReference;
 | 
			
		||||
			},
 | 
			
		||||
			MochiKit.Base.method(this, 'getValue'),
 | 
			
		||||
			MochiKit.Base.bind(function(aValue) {
 | 
			
		||||
				if (aValue) {
 | 
			
		||||
					aValue['usageDate'] = new Date().toString();
 | 
			
		||||
					this.setValue(reference, aValue);
 | 
			
		||||
				}
 | 
			
		||||
			}, this)
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	'getEncryptedOTPData': function(aPassphraseCallback) {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("User.Header.OneTimePasswords.getEncryptedOTPData", {trace:false});
 | 
			
		||||
 | 
			
		||||
		deferredResult.collectResults({
 | 
			
		||||
			'oneTimePasswords': MochiKit.Base.method(this, 'oneTimePasswords'),
 | 
			
		||||
			'oneTimePasswordsDetails': MochiKit.Base.method(this, 'oneTimePasswordsDetails')
 | 
			
		||||
		});
 | 
			
		||||
		deferredResult.addCallback(function (someData) {
 | 
			
		||||
			var result;
 | 
			
		||||
			var otpFilteredList;
 | 
			
		||||
			var i;
 | 
			
		||||
 | 
			
		||||
			var otpList = MochiKit.Base.values(someData['oneTimePasswords']);
 | 
			
		||||
 | 
			
		||||
			otpFilteredList = MochiKit.Base.filter(function (aOTP) {
 | 
			
		||||
				return (someData['oneTimePasswordsDetails'][aOTP.reference()]
 | 
			
		||||
					&&	someData['oneTimePasswordsDetails'][aOTP.reference()]['status'] == 'ACTIVE'
 | 
			
		||||
					&&	! someData['oneTimePasswords'][aOTP.reference()].usageDate()
 | 
			
		||||
				);
 | 
			
		||||
			}, otpList);
 | 
			
		||||
			
 | 
			
		||||
			result = MochiKit.Base.map(function (aOTP) {
 | 
			
		||||
				aOTP.setPassphraseCallback(aPassphraseCallback);
 | 
			
		||||
				return aOTP.encryptedData();
 | 
			
		||||
			}, otpFilteredList);
 | 
			
		||||
 | 
			
		||||
			return result;
 | 
			
		||||
		});
 | 
			
		||||
		deferredResult.addCallback(Clipperz.Async.collectAll);
 | 
			
		||||
		deferredResult.addCallback(function (someData) {
 | 
			
		||||
			var result;
 | 
			
		||||
			var i;
 | 
			
		||||
			
 | 
			
		||||
			result = {};
 | 
			
		||||
			for (i in someData) {
 | 
			
		||||
				result[someData[i].reference] = someData[i];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return result;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
	__syntaxFix__: "syntax fix"
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,8 @@ Clipperz.PM.DataModel.User = function (args) {
 | 
			
		||||
		'__syntaxFix__': 'syntax fix'
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	this._usedOTP = null;
 | 
			
		||||
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -80,6 +82,40 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'setUsedOTP': function(aOTP) {
 | 
			
		||||
		this._usedOTP = aOTP;
 | 
			
		||||
 | 
			
		||||
		return aOTP;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'resetUsedOTP': function(aOTP) {
 | 
			
		||||
		this._usedOTP = null;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'markUsedOTP': function(aOTP) {
 | 
			
		||||
		var result;
 | 
			
		||||
		var oneTimePasswordKey;
 | 
			
		||||
 | 
			
		||||
		if (this._usedOTP) {
 | 
			
		||||
			oneTimePasswordKey = Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword(
 | 
			
		||||
				Clipperz.PM.DataModel.OneTimePassword.normalizedOneTimePassword(this._usedOTP)
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			result = Clipperz.Async.callbacks("User.markUsedOTP", [ // NOTE: fired also when passphrase looks exactly like OTP
 | 
			
		||||
				MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
 | 
			
		||||
				MochiKit.Base.methodcaller('markOTPAsUsed', oneTimePasswordKey),
 | 
			
		||||
				MochiKit.Base.method(this,'saveChanges'), // Too 'heavy'?
 | 
			
		||||
				MochiKit.Base.method(this, 'resetUsedOTP')
 | 
			
		||||
			], {'trace': false});
 | 
			
		||||
		} else {
 | 
			
		||||
			result = MochiKit.Async.succeed();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
//	this.setSubscription(new Clipperz.PM.DataModel.User.Subscription(someServerData['subscription']));
 | 
			
		||||
	'accountInfo': function () {
 | 
			
		||||
		return this._accountInfo;
 | 
			
		||||
@@ -138,21 +174,17 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'getPassphrase': function() {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("User.getPassphrase", {trace:false});
 | 
			
		||||
		deferredResult.acquireLock(this.deferredLockForSection('passphrase'));
 | 
			
		||||
		deferredResult.addMethod(this.data(), 'deferredGetOrSet', 'passphrase', this.getPassphraseFunction());
 | 
			
		||||
		deferredResult.releaseLock(this.deferredLockForSection('passphrase'));
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
		return this._getPassphraseFunction();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'getPassphraseFunction': function () {
 | 
			
		||||
		return this._getPassphraseFunction;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'setPassphraseFunction': function(aFunction) {
 | 
			
		||||
		this._getPassphraseFunction = aFunction;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'getCredentials': function () {
 | 
			
		||||
@@ -164,26 +196,44 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'changePassphrase': function (aNewValue) {
 | 
			
		||||
		return this.updateCredentials(this.username(), aNewValue);
 | 
			
		||||
	'changePassphrase': function (aNewValueCallback) {
 | 
			
		||||
		return this.updateCredentials(this.username(), aNewValueCallback);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//.........................................................................
 | 
			
		||||
 | 
			
		||||
	'updateCredentials': function (aUsername, aPassphrase) {
 | 
			
		||||
	'updateCredentials': function (aUsername, aPassphraseCallback) {
 | 
			
		||||
		var	deferredResult;
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("User.updateCredentials", {trace:false});
 | 
			
		||||
//		deferredResult.addMethod(this, 'getPassphrase');
 | 
			
		||||
//		deferredResult.setValue('currentPassphrase');
 | 
			
		||||
		deferredResult.addMethod(this.connection(), 'ping');
 | 
			
		||||
		deferredResult.addMethod(this, 'setUsername', aUsername)
 | 
			
		||||
		deferredResult.acquireLock(this.deferredLockForSection('passphrase'));
 | 
			
		||||
		deferredResult.addMethod(this.data(), 'deferredGetOrSet', 'passphrase', aPassphrase);
 | 
			
		||||
		deferredResult.releaseLock(this.deferredLockForSection('passphrase'));
 | 
			
		||||
//		deferredResult.getValue('currentPassphrase');
 | 
			
		||||
		deferredResult.addMethod(this, 'prepareRemoteDataWithKey', aPassphrase);
 | 
			
		||||
		deferredResult.addMethod(this.connection(), 'updateCredentials', aUsername, aPassphrase);
 | 
			
		||||
		deferredResult.collectResults({
 | 
			
		||||
			'newUsername': MochiKit.Base.partial(MochiKit.Async.succeed, aUsername),
 | 
			
		||||
			'newPassphrase': aPassphraseCallback,
 | 
			
		||||
			'user': MochiKit.Base.method(this, 'prepareRemoteDataWithKeyFunction', aPassphraseCallback),
 | 
			
		||||
			'oneTimePasswords': [
 | 
			
		||||
				MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
 | 
			
		||||
				MochiKit.Base.methodcaller('getEncryptedOTPData', aPassphraseCallback),
 | 
			
		||||
				function (otps) {
 | 
			
		||||
					var result;
 | 
			
		||||
					var otpRefs;
 | 
			
		||||
					var i, c;
 | 
			
		||||
 | 
			
		||||
					result = {};
 | 
			
		||||
					otpRefs = MochiKit.Base.keys(otps);
 | 
			
		||||
					c = otpRefs.length;
 | 
			
		||||
					for (i=0; i<c; i++) {
 | 
			
		||||
						result[otpRefs[i]] = {}
 | 
			
		||||
						result[otpRefs[i]]['data'] = otps[otpRefs[i]]['data'];
 | 
			
		||||
						result[otpRefs[i]]['version'] = otps[otpRefs[i]]['version'];
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					return result;
 | 
			
		||||
				}
 | 
			
		||||
			]
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(this.connection(), 'updateCredentials');
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
		
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
@@ -212,8 +262,10 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
					'retrieveKeyFunction': MochiKit.Base.method(this, 'getPassphrase')
 | 
			
		||||
				}),
 | 
			
		||||
				'oneTimePasswords': new Clipperz.PM.DataModel.User.Header.OneTimePasswords({
 | 
			
		||||
					'name':	'preferences',
 | 
			
		||||
					'retrieveKeyFunction': MochiKit.Base.method(this, 'getPassphrase')
 | 
			
		||||
					'connection': this.connection(),
 | 
			
		||||
					'name':	'oneTimePasswords',
 | 
			
		||||
					'username': this.username(),
 | 
			
		||||
					'passphraseCallback': MochiKit.Base.method(this, 'getPassphrase')
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
@@ -227,18 +279,10 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("User.registerAsNewAccount", {trace:false});
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'updateProgress', {'extraSteps':3});
 | 
			
		||||
		deferredResult.addMethod(this, 'initialSetupWithNoData')
 | 
			
		||||
		deferredResult.addMethod(this, 'getPassphrase');
 | 
			
		||||
		deferredResult.addMethod(this, 'prepareRemoteDataWithKey');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
 | 
			
		||||
		deferredResult.addMethod(this.connection(), 'register');
 | 
			
		||||
//		deferredResult.addCallback(MochiKit.Base.itemgetter('lock'));
 | 
			
		||||
//		deferredResult.addMethod(this, 'setServerLockValue');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal,	Clipperz.Signal.NotificationCenter, 'userSuccessfullyRegistered');
 | 
			
		||||
 | 
			
		||||
//		deferredResult.addErrback (MochiKit.Base.method(this, 'handleRegistrationFailure'));
 | 
			
		||||
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
		
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
@@ -276,22 +320,44 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
 | 
			
		||||
	'login': function () {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
		var oneTimePasswordReference;
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("User.login", {trace:false});
 | 
			
		||||
		deferredResult.addMethod(this, 'getPassphrase');
 | 
			
		||||
		deferredResult.addCallback(Clipperz.PM.DataModel.OneTimePassword.isValidOneTimePasswordValue);
 | 
			
		||||
 | 
			
		||||
		deferredResult.addCallback(Clipperz.Async.deferredIf("Is the passphrase an OTP", [
 | 
			
		||||
			MochiKit.Base.method(this,'getPassphrase'),
 | 
			
		||||
			MochiKit.Base.method(this,'setUsedOTP'),
 | 
			
		||||
			MochiKit.Base.method(this, 'getCredentials'),
 | 
			
		||||
			MochiKit.Base.method(this.connection(), 'redeemOneTimePassword'),
 | 
			
		||||
			MochiKit.Base.method(this.data(), 'setValue', 'passphrase')
 | 
			
		||||
			function (aPassphrase) {
 | 
			
		||||
				return MochiKit.Base.partial(MochiKit.Async.succeed, aPassphrase);
 | 
			
		||||
			},
 | 
			
		||||
			MochiKit.Base.method(this, 'setPassphraseFunction')
 | 
			
		||||
		], []));
 | 
			
		||||
		deferredResult.addErrback(MochiKit.Base.method(this, 'getPassphrase'));
 | 
			
		||||
 | 
			
		||||
		deferredResult.addBoth(MochiKit.Base.method(this, 'loginWithPassphrase'));
 | 
			
		||||
		deferredResult.addBoth(MochiKit.Base.method(this, 'resetUsedOTP'));
 | 
			
		||||
		
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
		
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'loginWithPassphrase': function () {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("User.loginWithPassphrase", {trace:false});
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(this, 'getPassphrase');
 | 
			
		||||
		deferredResult.addMethod(this.connection(), 'login', false);
 | 
			
		||||
		deferredResult.addMethod(this, 'setupAccountInfo');
 | 
			
		||||
		deferredResult.addMethod(this, 'markUsedOTP');
 | 
			
		||||
		deferredResult.addErrback (MochiKit.Base.method(this, 'handleConnectionFallback'));
 | 
			
		||||
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -300,21 +366,18 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
	'handleConnectionFallback': function(aValue) {
 | 
			
		||||
		var result;
 | 
			
		||||
 | 
			
		||||
//console.log("USER - handleConnectionFallback", aValue, aValue['isPermanent']);
 | 
			
		||||
		if (aValue instanceof MochiKit.Async.CancelledError) {
 | 
			
		||||
			result = aValue;
 | 
			
		||||
		} else if ((aValue['isPermanent'] === true) || (Clipperz.PM.Connection.communicationProtocol.fallbackVersions[this.connectionVersion()] == null)) {
 | 
			
		||||
			result = Clipperz.Async.callbacks("User.handleConnectionFallback - failed", [
 | 
			
		||||
				MochiKit.Base.method(this.data(), 'removeValue', 'passphrase'),
 | 
			
		||||
				MochiKit.Base.method(this, 'setConnectionVersion', 'current'),
 | 
			
		||||
//				MochiKit.Base.partial(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'userLoginFailed'),
 | 
			
		||||
//				MochiKit.Base.partial(MochiKit.Async.fail, Clipperz.PM.DataModel.User.exception.LoginFailed)
 | 
			
		||||
				MochiKit.Base.partial(MochiKit.Async.fail, aValue)
 | 
			
		||||
			], {trace:false});
 | 
			
		||||
		} else {
 | 
			
		||||
			this.setConnectionVersion(Clipperz.PM.Connection.communicationProtocol.fallbackVersions[this.connectionVersion()]);
 | 
			
		||||
			result = new Clipperz.Async.Deferred("User.handleConnectionFallback - retry");
 | 
			
		||||
			result.addMethod(this, 'login');
 | 
			
		||||
			result.addMethod(this, 'loginWithPassphrase');
 | 
			
		||||
			result.callback();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -324,8 +387,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'setupAccountInfo': function (aValue) {
 | 
			
		||||
//console.log("User.setupAccountInfo", aValue, aValue['accountInfo']);
 | 
			
		||||
//		this.setLoginInfo(aValue['loginInfo']);
 | 
			
		||||
		this.setAccountInfo(new Clipperz.PM.DataModel.User.AccountInfo(aValue['accountInfo']));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -373,8 +434,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
		var preferences;
 | 
			
		||||
		var oneTimePasswords;
 | 
			
		||||
 | 
			
		||||
//		this.setServerLockValue(someServerData['lock']);
 | 
			
		||||
 | 
			
		||||
		headerVersion = this.headerFormatVersion(someServerData['header']);
 | 
			
		||||
		switch (headerVersion) {
 | 
			
		||||
			case 'LEGACY':
 | 
			
		||||
@@ -429,7 +488,9 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
 | 
			
		||||
				if (typeof(headerData['oneTimePasswords']) != 'undefined') {
 | 
			
		||||
					oneTimePasswords = new Clipperz.PM.DataModel.User.Header.OneTimePasswords({
 | 
			
		||||
						'name':	'preferences',
 | 
			
		||||
						'name':	'oneTimePasswords',
 | 
			
		||||
						'connection': this.connection(),
 | 
			
		||||
						'username': this.username(),
 | 
			
		||||
						'retrieveKeyFunction': MochiKit.Base.method(this, 'getPassphrase'),
 | 
			
		||||
						'remoteData': {
 | 
			
		||||
							'data': headerData['oneTimePasswords']['data'],
 | 
			
		||||
@@ -438,7 +499,9 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
					});
 | 
			
		||||
				} else {
 | 
			
		||||
					oneTimePasswords = new Clipperz.PM.DataModel.User.Header.OneTimePasswords({
 | 
			
		||||
						'name':	'preferences',
 | 
			
		||||
						'name':	'OneTimePasswords',
 | 
			
		||||
						'connection': this.connection(),
 | 
			
		||||
						'username': this.username(),
 | 
			
		||||
						'retrieveKeyFunction': MochiKit.Base.method(this, 'getPassphrase')
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
@@ -595,7 +658,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
*/			
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
/*
 | 
			
		||||
	'filterRecordsInfo': function (someArgs) {
 | 
			
		||||
		var	info			= (someArgs.info			? someArgs.info				: Clipperz.PM.DataModel.Record.defaultCardInfo);
 | 
			
		||||
@@ -688,8 +750,47 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//.........................................................................
 | 
			
		||||
 | 
			
		||||
	'createNewRecordFromJSON': function(someJSON) {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("User.createNewRecordFromJSON", {trace:false});
 | 
			
		||||
		deferredResult.collectResults({
 | 
			
		||||
			'recordIndex': MochiKit.Base.method(this, 'getHeaderIndex', 'recordsIndex'),
 | 
			
		||||
			'newRecord': [
 | 
			
		||||
				MochiKit.Base.method(this, 'createNewRecord'),
 | 
			
		||||
				MochiKit.Base.methodcaller('setUpWithJSON', someJSON),
 | 
			
		||||
			]
 | 
			
		||||
		});
 | 
			
		||||
		deferredResult.addCallback(function (someInfo) {
 | 
			
		||||
			var	record = someInfo['newRecord'];
 | 
			
		||||
			var	recordIndex = someInfo['recordIndex'];
 | 
			
		||||
			
 | 
			
		||||
			return MochiKit.Base.map(function (aDirectLogin) {
 | 
			
		||||
				var	configuration = JSON.stringify({
 | 
			
		||||
					'page': {'title': aDirectLogin['label']},
 | 
			
		||||
					'form': aDirectLogin['formData'],
 | 
			
		||||
					'version': '0.2' // correct?
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				return Clipperz.Async.callbacks("User.createNewRecordFromJSON__inner", [
 | 
			
		||||
					MochiKit.Base.method(recordIndex, 'createNewDirectLogin', record),
 | 
			
		||||
					MochiKit.Base.methodcaller('setLabel', aDirectLogin['label']),
 | 
			
		||||
					MochiKit.Base.methodcaller('setBookmarkletConfiguration', configuration),
 | 
			
		||||
					MochiKit.Base.methodcaller('setBindings', aDirectLogin['bindingData'], someJSON['currentVersion']['fields']),
 | 
			
		||||
				], {'trace': false});
 | 
			
		||||
			}, MochiKit.Base.values(someJSON.data.directLogins));
 | 
			
		||||
		});
 | 
			
		||||
		deferredResult.addCallback(Clipperz.Async.collectAll);
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'cloneRecord': function (aRecord) {
 | 
			
		||||
//console.log("USER.cloneRecord", aRecord);
 | 
			
		||||
		var	result;
 | 
			
		||||
		var	user = this;
 | 
			
		||||
		
 | 
			
		||||
@@ -700,9 +801,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
			], [
 | 
			
		||||
				MochiKit.Base.method(user, 'createNewRecord'),
 | 
			
		||||
				MochiKit.Base.methodcaller('setUpWithRecord', aRecord),
 | 
			
		||||
//				function (aValue) { result = aValue; return aValue; },
 | 
			
		||||
//				MochiKit.Base.method(user, 'saveChanges'),
 | 
			
		||||
//				function () { return result; }
 | 
			
		||||
			])
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
@@ -731,6 +829,62 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'getOneTimePasswordsDetails': function() {
 | 
			
		||||
		return Clipperz.Async.callbacks("User.getOneTimePasswords", [
 | 
			
		||||
			MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
 | 
			
		||||
			MochiKit.Base.methodcaller('oneTimePasswordsDetails', this.connection()),
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'createNewOTP': function () {
 | 
			
		||||
		var messageParameters;
 | 
			
		||||
 | 
			
		||||
		messageParameters = {};
 | 
			
		||||
		return Clipperz.Async.callbacks("User.createNewOTP", [
 | 
			
		||||
			MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
 | 
			
		||||
			MochiKit.Base.methodcaller('createNewOTP', this.username(), MochiKit.Base.method(this, 'getPassphrase')),
 | 
			
		||||
			MochiKit.Base.methodcaller('encryptedData'),
 | 
			
		||||
			MochiKit.Base.partial(function(someParameters, someOTPEncryptedData) {
 | 
			
		||||
				someParameters['oneTimePassword'] = someOTPEncryptedData;
 | 
			
		||||
			}, messageParameters),
 | 
			
		||||
			MochiKit.Base.method(this, 'getPassphrase'),
 | 
			
		||||
			MochiKit.Base.method(this, 'prepareRemoteDataWithKey'),
 | 
			
		||||
			MochiKit.Base.partial(function(someParameters, someEncryptedRemoteData) {
 | 
			
		||||
				someParameters['user'] = someEncryptedRemoteData;
 | 
			
		||||
			}, messageParameters),
 | 
			
		||||
			MochiKit.Base.method(this.connection(), 'message', 'addNewOneTimePassword', messageParameters)
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'deleteOTPs': function (aList) {
 | 
			
		||||
		var messageParameters;
 | 
			
		||||
 | 
			
		||||
		messageParameters = {};
 | 
			
		||||
		return Clipperz.Async.callbacks("User.deleteOTPs", [
 | 
			
		||||
			MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
 | 
			
		||||
			MochiKit.Base.methodcaller('deleteOTPs', aList),
 | 
			
		||||
			MochiKit.Base.partial(function(someParameters, aList) {
 | 
			
		||||
				someParameters['oneTimePasswords'] = aList
 | 
			
		||||
			}, messageParameters),
 | 
			
		||||
			MochiKit.Base.method(this, 'getPassphrase'),
 | 
			
		||||
			MochiKit.Base.method(this, 'prepareRemoteDataWithKey'),
 | 
			
		||||
			MochiKit.Base.partial(function(someParameters, someEncryptedRemoteData) {
 | 
			
		||||
				someParameters['user'] = someEncryptedRemoteData;
 | 
			
		||||
			}, messageParameters),
 | 
			
		||||
			MochiKit.Base.method(this.connection(), 'message', 'updateOneTimePasswords', messageParameters)			
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'changeOTPLabel': function (aReference, aLabel) {
 | 
			
		||||
		return Clipperz.Async.callbacks("User.changeOTPLabel", [
 | 
			
		||||
			MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
 | 
			
		||||
			MochiKit.Base.methodcaller('changeOTPLabel', aReference, aLabel),
 | 
			
		||||
			MochiKit.Base.method(this,'saveChanges') // Too 'heavy'? Should be moved to MainController to prevent glitch in the UI? 
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	'invokeMethodNamedOnHeader': function (aMethodName, aValue) {
 | 
			
		||||
@@ -804,13 +958,9 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
 | 
			
		||||
	'revertChanges': function () {
 | 
			
		||||
		return Clipperz.Async.callbacks("User.revertChanges", [
 | 
			
		||||
//function (aValue) { console.log("User.revertChanges - 1"); return aValue; },
 | 
			
		||||
			MochiKit.Base.method(this, 'invokeMethodNamedOnHeader', 'revertChanges'),
 | 
			
		||||
//function (aValue) { console.log("User.revertChanges - 2"); return aValue; },
 | 
			
		||||
			MochiKit.Base.method(this, 'invokeMethodNamedOnRecords', 'revertChanges'),
 | 
			
		||||
//function (aValue) { console.log("User.revertChanges - 3"); return aValue; },
 | 
			
		||||
			MochiKit.Base.method(this, 'resetTransientState', false),
 | 
			
		||||
//function (aValue) { console.log("User.revertChanges - 4"); return aValue; },
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -882,6 +1032,13 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'prepareRemoteDataWithKeyFunction': function(aKeyFunction) {
 | 
			
		||||
		return new Clipperz.Async.callbacks("User.prepareRemoteDataWithKeyFunction", [
 | 
			
		||||
			aKeyFunction,
 | 
			
		||||
			MochiKit.Base.method(this, 'prepareRemoteDataWithKey')
 | 
			
		||||
		], {'trace': false})
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	'saveChanges': function () {
 | 
			
		||||
@@ -895,24 +1052,17 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
 | 
			
		||||
		deferredResult.addMethod(this, 'getHeaderIndex', 'recordsIndex');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.methodcaller('prepareRemoteDataForChangedRecords'));
 | 
			
		||||
		deferredResult.addCallback(Clipperz.Async.setItem, messageParameters, 'records');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(this, 'getPassphrase');
 | 
			
		||||
		deferredResult.addMethod(this, 'prepareRemoteDataWithKey');
 | 
			
		||||
		deferredResult.addCallback(Clipperz.Async.setItem, messageParameters, 'user');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
 | 
			
		||||
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Async.succeed, messageParameters);
 | 
			
		||||
		deferredResult.addMethod(this.connection(), 'message', 'saveChanges');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.update, this.transientState())
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(this, 'commitTransientState');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
 | 
			
		||||
//		deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'userDataSuccessfullySaved');
 | 
			
		||||
 | 
			
		||||
		deferredResult.addErrbackPass(MochiKit.Base.method(this, 'revertChanges'));
 | 
			
		||||
//		deferredResult.addErrbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'failureWhileSavingUserData');
 | 
			
		||||
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -93,6 +93,7 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	// Should this be updated to include OTP field?
 | 
			
		||||
	'setupWithData': function(someData) {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
		var resultData;
 | 
			
		||||
@@ -319,13 +320,28 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
 | 
			
		||||
		result = {};
 | 
			
		||||
		if (someParameters.message == "connect") {
 | 
			
		||||
			var userData;
 | 
			
		||||
			var otpsData, userOTPs;
 | 
			
		||||
			var randomBytes;
 | 
			
		||||
			var v;
 | 
			
		||||
 | 
			
		||||
			userData = this.data()['users'][someParameters.parameters.C];
 | 
			
		||||
			
 | 
			
		||||
			otpsData = (typeof(this.data()['onetimePasswords']) != 'undefined') ? this.data()['onetimePasswords'] : {};
 | 
			
		||||
 | 
			
		||||
//console.log("Proxy.Offline.DataStore._handshake: otpsData:", otpsData);
 | 
			
		||||
 | 
			
		||||
			userOTPs = {};
 | 
			
		||||
			MochiKit.Base.map(function(aOTP) {
 | 
			
		||||
				if (aOTP['user'] == someParameters.parameters.C) {
 | 
			
		||||
					userOTPs[aOTP['key']] = aOTP;
 | 
			
		||||
				}
 | 
			
		||||
			},MochiKit.Base.values(otpsData));
 | 
			
		||||
 | 
			
		||||
//console.log("Proxy.Offline.DataStore._handshake: userOTPs:", userOTPs);
 | 
			
		||||
//console.log("Proxy.Offline.DataStore._handshake(): userOTPs:",userOTPs);
 | 
			
		||||
 | 
			
		||||
			if ((typeof(userData) != 'undefined') && (userData['version'] == someParameters.version)) {
 | 
			
		||||
				aConnection['userData'] = userData;
 | 
			
		||||
				aConnection['userOTPs'] = userOTPs;
 | 
			
		||||
				aConnection['C'] = someParameters.parameters.C;
 | 
			
		||||
			} else {
 | 
			
		||||
				aConnection['userData'] = this.data()['users']['catchAllUser'];
 | 
			
		||||
@@ -495,6 +511,17 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
 | 
			
		||||
			MochiKit.Base.update(result, aConnection['userData']['records'][someParameters['parameters']['reference']]);
 | 
			
		||||
			result['reference'] = someParameters['parameters']['reference'];
 | 
			
		||||
 | 
			
		||||
		}  else if (someParameters.message == 'getOneTimePasswordsDetails') {
 | 
			
		||||
			var result = MochiKit.Iter.reduce(function(prev, cur){
 | 
			
		||||
				prev[cur.reference] = {
 | 
			
		||||
					'status': cur.status,
 | 
			
		||||
					'usage_date': cur.usage_date
 | 
			
		||||
				};
 | 
			
		||||
				return prev;
 | 
			
		||||
			}, MochiKit.Base.values(aConnection['userOTPs']), {});
 | 
			
		||||
 | 
			
		||||
			MochiKit.Base.update(result, result);
 | 
			
		||||
// console.log("Proxy.Offline.DataStore.getOneTimePasswordsDetails:",result);
 | 
			
		||||
		//=====================================================================
 | 
			
		||||
		//
 | 
			
		||||
		//		R	E	A	D	-	W	R	I	T	E		M e t h o d s
 | 
			
		||||
@@ -652,12 +679,60 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		//=====================================================================
 | 
			
		||||
		} else if (someParameters.message == 'addNewOneTimePassword') {
 | 
			
		||||
			if (this.isReadOnly() == false) {
 | 
			
		||||
//console.log("Proxy.Offline.DataStore.addNewOneTimePassword: someParameters:", someParameters);
 | 
			
		||||
 | 
			
		||||
				var otpKey = someParameters['parameters']['oneTimePassword'].key;
 | 
			
		||||
 | 
			
		||||
				if (aConnection['userData']['lock']	!= someParameters['parameters']['user']['lock']) {
 | 
			
		||||
					throw "the lock attribute is not processed correctly"
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				aConnection['userData']['userDetails']			= someParameters['parameters']['user']['header'];
 | 
			
		||||
				aConnection['userData']['statistics']			= someParameters['parameters']['user']['statistics'];
 | 
			
		||||
				aConnection['userData']['userDetailsVersion']	= someParameters['parameters']['user']['version'];
 | 
			
		||||
 | 
			
		||||
				aConnection['userOTPs'][otpKey] = someParameters['parameters']['oneTimePassword'];
 | 
			
		||||
				aConnection['userOTPs'][otpKey]['user'] = aConnection['C'];
 | 
			
		||||
				aConnection['userOTPs'][otpKey]['status'] = 'ACTIVE';
 | 
			
		||||
				aConnection['userOTPs'][otpKey]['creation_date'] = new Date().toISOString().substr(0, 19).replace('T', ' '); // Not an elegant way to give the date the same format as the others
 | 
			
		||||
				aConnection['userOTPs'][otpKey]['request_date'] = "4001-01-01 09:00:00";
 | 
			
		||||
				aConnection['userOTPs'][otpKey]['usage_date'] = "4001-01-01 09:00:00";
 | 
			
		||||
//console.log("Proxy.Offline.DataStore.addNewOneTimePassword: aConnection:", aConnection);
 | 
			
		||||
			} else {
 | 
			
		||||
				throw Clipperz.PM.Proxy.Offline.DataStore.exception.ReadOnly;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
		} else if (someParameters.message == 'updateOneTimePasswords') {
 | 
			
		||||
			if (this.isReadOnly() == false) {
 | 
			
		||||
console.log("Proxy.Offline.DataStore.updateOneTimePasswords: someParameters:", someParameters);
 | 
			
		||||
 | 
			
		||||
				if (aConnection['userData']['lock']	!= someParameters['parameters']['user']['lock']) {
 | 
			
		||||
					throw "the lock attribute is not processed correctly"
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				aConnection['userData']['userDetails']			= someParameters['parameters']['user']['header'];
 | 
			
		||||
				aConnection['userData']['statistics']			= someParameters['parameters']['user']['statistics'];
 | 
			
		||||
				aConnection['userData']['userDetailsVersion']	= someParameters['parameters']['user']['version'];
 | 
			
		||||
 | 
			
		||||
console.log("Proxy.Offline.DataStore.updateOneTimePasswords: userOTPs:", aConnection['userOTPs']);
 | 
			
		||||
 | 
			
		||||
				MochiKit.Base.map(function(aOTP) {
 | 
			
		||||
					if (someParameters['parameters']['oneTimePasswords'].indexOf(aOTP.reference) < 0) {
 | 
			
		||||
						delete aConnection['userOTPs'][aOTP.key];
 | 
			
		||||
					}
 | 
			
		||||
				},MochiKit.Base.values(aConnection['userOTPs']));
 | 
			
		||||
			} else {
 | 
			
		||||
				throw Clipperz.PM.Proxy.Offline.DataStore.exception.ReadOnly;
 | 
			
		||||
			}			
 | 
			
		||||
		//=====================================================================
 | 
			
		||||
		//
 | 
			
		||||
		//		U	N	H	A	N	D	L	E	D		M e t h o d
 | 
			
		||||
		//
 | 
			
		||||
		//=====================================================================
 | 
			
		||||
		} else {
 | 
			
		||||
			Clipperz.logError("Clipperz.PM.Proxy.Test.message - unhandled message: " + someParameters.message);
 | 
			
		||||
			Clipperz.logError("Clipperz.PM.Proxy.Test.message - unhandled message (Proxy.Offline.DataStore): " + someParameters.message);
 | 
			
		||||
		}
 | 
			
		||||
	
 | 
			
		||||
		result = {
 | 
			
		||||
 
 | 
			
		||||
@@ -226,7 +226,9 @@ console.log("DROP");	//, anEvent);
 | 
			
		||||
 | 
			
		||||
	handleKeyDown: function (aField) {
 | 
			
		||||
		var	self = this;
 | 
			
		||||
 | 
			
		||||
		return function (anEvent) {
 | 
			
		||||
 | 
			
		||||
			switch (anEvent.keyCode) {
 | 
			
		||||
				case 9: // tab
 | 
			
		||||
					var	fieldReferences = MochiKit.Base.map(function (aValue) { return aValue['_reference']}, self.fields());
 | 
			
		||||
@@ -236,12 +238,14 @@ console.log("DROP");	//, anEvent);
 | 
			
		||||
							MochiKit.Base.method(aField, 'isEmpty'),
 | 
			
		||||
							Clipperz.Async.deferredIf('isEmpty',[
 | 
			
		||||
							], [
 | 
			
		||||
								MochiKit.Base.method(self, 'addNewField')
 | 
			
		||||
								MochiKit.Base.method(anEvent, 'preventDefault'),
 | 
			
		||||
								MochiKit.Base.method(self, 'addNewField'),
 | 
			
		||||
//	TODO: set the focus to the newly created field
 | 
			
		||||
//	hints: http://stackoverflow.com/questions/24248234/react-js-set-input-value-from-sibling-component
 | 
			
		||||
							])
 | 
			
		||||
						], {trace:false});
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
@@ -393,6 +397,7 @@ console.log("DROP");	//, anEvent);
 | 
			
		||||
		if (this.state['draggedFieldReference'] != null) {
 | 
			
		||||
			renderedFields = this.updateRenderedFieldsWithDropArea(renderedFields);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return	React.DOM.div({'className':'cardFields' /*, 'dropzone':'move'*/}, renderedFields);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -72,8 +72,10 @@ Clipperz.PM.UI.Components.Cards.TagEditorClass = React.createClass({
 | 
			
		||||
	//----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	addTagValue: function (anEvent) {
 | 
			
		||||
		this.addTag(anEvent.currentTarget.value);
 | 
			
		||||
		anEvent.currentTarget.value = "";
 | 
			
		||||
		if (anEvent.currentTarget.value) {
 | 
			
		||||
			this.addTag(anEvent.currentTarget.value);
 | 
			
		||||
			anEvent.currentTarget.value = "";
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	handleKeyDown: function (anEvent) {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,9 +36,9 @@ Clipperz.PM.UI.Components.CheckboxClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	render: function () {
 | 
			
		||||
		return	React.DOM.div({className:'checkbox', onClick:this.props['eventHandler']}, [
 | 
			
		||||
					React.DOM.input({name:this.props['id'], id:this.props['id'], value:this.props['id'], type:'checkbox', checked:this.props['checked']}),
 | 
			
		||||
					React.DOM.label({className:'check', 'for':this.props['id']}),
 | 
			
		||||
					React.DOM.label({className:'info', 'for':this.props['id']}, "enable local storage")
 | 
			
		||||
					React.DOM.input({'name':this.props['id'], 'id':this.props['id'], 'value':this.props['id'], 'type':'checkbox', 'checked':this.props['checked']}),
 | 
			
		||||
					React.DOM.label({'className':'check', 'htmlFor':this.props['id']}),
 | 
			
		||||
					React.DOM.label({'className':'info', 'htmlFor':this.props['id']}, "enable local storage")
 | 
			
		||||
				]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	render: function () {
 | 
			
		||||
		return	React.DOM.div({className:'extraFeature devicePIN'}, [
 | 
			
		||||
			React.DOM.h1({}, "Export"),
 | 
			
		||||
			React.DOM.div({'className':'header'}, [
 | 
			
		||||
				React.DOM.h1({}, "Export"),
 | 
			
		||||
			]),
 | 
			
		||||
			React.DOM.div({'className': 'content'}, [
 | 
			
		||||
				React.DOM.ul({}, [
 | 
			
		||||
					React.DOM.li({}, [
 | 
			
		||||
@@ -76,7 +78,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
 | 
			
		||||
						React.DOM.div({'className':'description'}, [
 | 
			
		||||
							React.DOM.p({}, "Download a printer-friendly HTML file that lists the content of all your cards."),
 | 
			
		||||
							React.DOM.p({}, "This same file also contains all your data in JSON format."),
 | 
			
		||||
							React.DOM.p({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file.")
 | 
			
		||||
							React.DOM.p({'className':'warning'}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file.")
 | 
			
		||||
						]),
 | 
			
		||||
						React.DOM.a({'className':'button', 'onClick':this.handleExportLink}, "download HTML+JSON")
 | 
			
		||||
					]),
 | 
			
		||||
 
 | 
			
		||||
@@ -24,144 +24,76 @@ refer to http://www.clipperz.com.
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
 | 
			
		||||
 | 
			
		||||
var _steps = ['Input', 'CsvColumns', 'CsvLabels', 'CsvTitles', 'CsvNotes', 'CsvHidden', 'Preview'];
 | 
			
		||||
var _stepNames = ['Input', 'Columns', 'Labels', 'Titles', 'Notes','Hidden','Preview'];
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImportClass = React.createClass({
 | 
			
		||||
	_steps: _steps,
 | 
			
		||||
	_stepNames: _stepNames,
 | 
			
		||||
	_relevantSteps: {
 | 
			
		||||
		'csv': _steps,
 | 
			
		||||
		'json': [_steps[0], _steps[6]]
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'currentStep': this._steps[0],
 | 
			
		||||
			'importContext': new Clipperz.PM.UI.ImportContext(),
 | 
			
		||||
			'nextStepCallback': null,
 | 
			
		||||
			'error': null
 | 
			
		||||
			'importContext': new Clipperz.PM.UI.ImportContext(this),
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	componentWillUnmount: function () {
 | 
			
		||||
		this.state['importContext'].release();
 | 
			
		||||
		this.setState({'importContext': null})
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	getStepIndex: function(aStep) {
 | 
			
		||||
		return this._steps.indexOf(aStep);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	getStepAfter: function() {
 | 
			
		||||
		return this._steps[this.getStepIndex(this.state.currentStep) + 1];
 | 
			
		||||
	importContext: function () {
 | 
			
		||||
		return this.state.importContext;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getStepBefore: function() {
 | 
			
		||||
		return this._steps[this.getStepIndex(this.state.currentStep) - 1];
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	isStepRelevant: function(aStep, aFormat) {
 | 
			
		||||
		if (!aFormat) {
 | 
			
		||||
			return true
 | 
			
		||||
		} else {
 | 
			
		||||
			return (this._relevantSteps[aFormat].indexOf(aStep) >= 0);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//--------------------------------------------------------------------------
 | 
			
		||||
	
 | 
			
		||||
	goToStep: function(aStep) {
 | 
			
		||||
		this.setState({
 | 
			
		||||
			'currentStep': aStep,
 | 
			
		||||
			'nextStepCallback': null,
 | 
			
		||||
			'error': null
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	handleNextStepOnClick: function() {
 | 
			
		||||
		if (this.state.nextStepCallback) {
 | 
			
		||||
			var newImportContext = this.state.nextStepCallback();
 | 
			
		||||
			
 | 
			
		||||
			if (newImportContext) {
 | 
			
		||||
				MochiKit.Base.update(this.state.importContext, newImportContext);
 | 
			
		||||
				
 | 
			
		||||
				if (this.state.currentStep == 'Input' && this.state.importContext.format == 'json') {
 | 
			
		||||
					this.goToStep('Preview');
 | 
			
		||||
				} else if (this.state.currentStep == 'Preview') {
 | 
			
		||||
					this.state.importContext.resetContext();
 | 
			
		||||
					this.goToStep('Input');
 | 
			
		||||
				} else {
 | 
			
		||||
					this.goToStep(this.getStepAfter());
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if (this.state.currentStep == "Input") {
 | 
			
		||||
					this.setState({'error': "unrecognized input format."});
 | 
			
		||||
				} else {
 | 
			
		||||
					this.setState({'error': "unknown error."});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	handleBackOnClick: function() {
 | 
			
		||||
		if (this.state.importContext.format == 'json' && this.state.currentStep == 'Preview') {
 | 
			
		||||
			delete this.state.importContext.format;
 | 
			
		||||
			this.goToStep('Input');
 | 
			
		||||
		} else if (this.state.currentStep != this._steps[0]) {
 | 
			
		||||
			this.goToStep(this.getStepBefore());
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	setNextStepCallback: function(aFunction) {
 | 
			
		||||
		this.setState({'nextStepCallback': aFunction});
 | 
			
		||||
	},	
 | 
			
		||||
	
 | 
			
		||||
	getStepNavbarClass: function(aStep) {
 | 
			
		||||
		var result;
 | 
			
		||||
		
 | 
			
		||||
		if (aStep == this.state.currentStep) {
 | 
			
		||||
			result = 'active';
 | 
			
		||||
		} else if (this.state.importContext.format == 'json' && (aStep>=1&&aStep<=5) ) {
 | 
			
		||||
			result = 'disabled';
 | 
			
		||||
		} else {
 | 
			
		||||
			result = 'inactive';
 | 
			
		||||
	componentWithName: function (aName) {
 | 
			
		||||
		var	result;
 | 
			
		||||
		var	path;
 | 
			
		||||
		var i, c;
 | 
			
		||||
 | 
			
		||||
		path = aName.split('.');
 | 
			
		||||
 | 
			
		||||
		result = Clipperz.PM.UI.Components.ExtraFeatures.DataImport;
 | 
			
		||||
		c = path.length;
 | 
			
		||||
		for (i=0; i<c; i++) {
 | 
			
		||||
			result = result[path[i]];
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	renderNavbar: function (currentStep) {
 | 
			
		||||
		return React.DOM.ul({'className': 'stepNavbar' + ' ' + currentStep},
 | 
			
		||||
			MochiKit.Base.map(MochiKit.Base.bind(function(aStep){
 | 
			
		||||
//				return React.DOM.li({'className': this.importContext().stepStatus(aStep)}, this.importContext().stepName(aStep));
 | 
			
		||||
				return React.DOM.li({'className': this.importContext().stepStatus(aStep)}, '.');
 | 
			
		||||
			}, this),this.importContext().steps())
 | 
			
		||||
		)
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function () {
 | 
			
		||||
		return	React.DOM.div({className:'extraFeature dataImport'}, [
 | 
			
		||||
			React.DOM.h1({}, "Import"),
 | 
			
		||||
			React.DOM.div({'className': 'content'}, [
 | 
			
		||||
				React.DOM.ul({'className': 'stepNavbar'},
 | 
			
		||||
					MochiKit.Base.map(MochiKit.Base.bind(function(aStep){
 | 
			
		||||
						var className;
 | 
			
		||||
						
 | 
			
		||||
						if (this.isStepRelevant(aStep,this.state.importContext.format)) {
 | 
			
		||||
							className = (aStep == this.state.currentStep) ? 'active' : 'inactive';
 | 
			
		||||
						} else {
 | 
			
		||||
							className = 'disabled';
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						return React.DOM.li({
 | 
			
		||||
							'className': className
 | 
			
		||||
						}, this._stepNames[this.getStepIndex(aStep)]);
 | 
			
		||||
					}, this),this._steps)
 | 
			
		||||
				),
 | 
			
		||||
				new Clipperz.PM.UI.Components.ExtraFeatures.DataImport[this.state.currentStep]({
 | 
			
		||||
					'importContext': this.state.importContext,
 | 
			
		||||
					'setNextStepCallback': this.setNextStepCallback,
 | 
			
		||||
				}),
 | 
			
		||||
				React.DOM.a({
 | 
			
		||||
					'className': 'button'+((this.state.currentStep == this._steps[0]) ? ' disabled' : ''),
 | 
			
		||||
					'onClick': this.handleBackOnClick,
 | 
			
		||||
				}, "Back"),
 | 
			
		||||
				React.DOM.a({
 | 
			
		||||
					'className': 'button'+((! this.state.nextStepCallback) ? ' disabled' : ''),
 | 
			
		||||
					'onClick': this.handleNextStepOnClick,
 | 
			
		||||
				}, "Next"),
 | 
			
		||||
				(this.state.error) ? React.DOM.p({'className': 'error'}, "Error: " + this.state.error) : null
 | 
			
		||||
		var currentStep = this.importContext().currentStep().replace('.','_');
 | 
			
		||||
 | 
			
		||||
		return React.DOM.div({className:'extraFeature dataImport'}, [
 | 
			
		||||
			React.DOM.div({'className':'header'}, [
 | 
			
		||||
				React.DOM.h1({}, "Import"),
 | 
			
		||||
			]),
 | 
			
		||||
			React.DOM.div({'className': 'content' + ' ' + currentStep + ' ' + this.importContext().inputFormat()}, [
 | 
			
		||||
				React.DOM.div({'className': 'step' + ' ' + currentStep}, [
 | 
			
		||||
					new this.componentWithName(this.importContext().currentStep())({
 | 
			
		||||
						'importContext': this.importContext(),
 | 
			
		||||
					}),
 | 
			
		||||
				]),
 | 
			
		||||
				this.renderNavbar(currentStep),
 | 
			
		||||
				React.DOM.div({'className': 'buttons' + ' ' + currentStep}, [
 | 
			
		||||
					React.DOM.a({
 | 
			
		||||
						'className': 'button back ' + this.importContext().backButtonStatus(),
 | 
			
		||||
						'onClick': this.importContext().goBackHandler()
 | 
			
		||||
					}, React.DOM.span({}, "Back")),
 | 
			
		||||
					React.DOM.a({
 | 
			
		||||
						'className': 'button next ' + this.importContext().forwardButtonStatus(),
 | 
			
		||||
						'onClick': this.importContext().goForwardHandler()
 | 
			
		||||
					}, React.DOM.span({}, "Next"))
 | 
			
		||||
				])
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,73 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.ColumnsClass = React.createClass({
 | 
			
		||||
	
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'selectedColumns': this.props.importContext.state('csvData.selectedColumns'),
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	toggleColumn: function(columnIndex) {
 | 
			
		||||
		var newSelectedColumns;
 | 
			
		||||
	
 | 
			
		||||
		newSelectedColumns = this.state['selectedColumns'];
 | 
			
		||||
		newSelectedColumns[columnIndex] = ! newSelectedColumns[columnIndex];
 | 
			
		||||
		
 | 
			
		||||
		this.setState({'selectedColumns': newSelectedColumns});
 | 
			
		||||
		this.props.importContext.setState('csvData.selectedColumns', newSelectedColumns);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var	importContext = this.props.importContext;
 | 
			
		||||
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Select the columns you want to import."),
 | 
			
		||||
			React.DOM.table({'className':'csvTable', 'key':'csvTableColumns'},[
 | 
			
		||||
				React.DOM.thead({}, React.DOM.tr({'className':'columnSelectors', 'key':'csv-colsel'}, MochiKit.Base.map(MochiKit.Base.bind(function (columnIndex) {
 | 
			
		||||
					var thClasses = {
 | 
			
		||||
						'title': (columnIndex == importContext.state('csvData.titleIndex')),
 | 
			
		||||
						'notes': (columnIndex == importContext.state('csvData.notesIndex')),
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					return React.DOM.th({
 | 
			
		||||
						'key': 'csv-colsel-' + columnIndex,
 | 
			
		||||
						'className': Clipperz.PM.UI.Components.classNames(thClasses)
 | 
			
		||||
					}, React.DOM.input({
 | 
			
		||||
						'key': 'csv-label-input-' + columnIndex,
 | 
			
		||||
						'type': 'checkbox',
 | 
			
		||||
						'checked': this.state['selectedColumns'][columnIndex],
 | 
			
		||||
						'onChange': MochiKit.Base.partial(this.toggleColumn, columnIndex)
 | 
			
		||||
					}));
 | 
			
		||||
				}, this), MochiKit.Iter.range(this.state['selectedColumns'].length)))),
 | 
			
		||||
				this.props.importContext.renderCsvTableBody(false)
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Columns = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.ColumnsClass);
 | 
			
		||||
@@ -0,0 +1,92 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.HiddenClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'hiddenFields': this.props.importContext.state('csvData.hiddenFields'),
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	onChangeCallback: function (columnIndex) {
 | 
			
		||||
		var newHiddenColumns = this.state['hiddenFields'];
 | 
			
		||||
	
 | 
			
		||||
		newHiddenColumns[columnIndex] = ! newHiddenColumns[columnIndex];
 | 
			
		||||
		
 | 
			
		||||
		this.setState({'hiddenFields': newHiddenColumns});
 | 
			
		||||
		this.props.importContext.setState('csvData.hiddenFields', newHiddenColumns);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var importContext = this.props.importContext;
 | 
			
		||||
		
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Select the fields that should be hidden. (passwords, PINs, ...)"),
 | 
			
		||||
			React.DOM.table({'className': 'csvTable'},[
 | 
			
		||||
				React.DOM.thead({},
 | 
			
		||||
					React.DOM.tr({},
 | 
			
		||||
						MochiKit.Base.map(MochiKit.Base.bind(function (cellInfo) {
 | 
			
		||||
							var result;
 | 
			
		||||
							var	columnIndex = cellInfo[0];
 | 
			
		||||
							var	columnValue = cellInfo[1];
 | 
			
		||||
							var thClasses = {
 | 
			
		||||
								'title': (columnIndex == importContext.state('csvData.titleIndex')),
 | 
			
		||||
								'notes': (columnIndex == importContext.state('csvData.notesIndex')),
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							if (importContext.state('csvData.selectedColumns')[columnIndex]) {
 | 
			
		||||
								result = React.DOM.th({
 | 
			
		||||
									'key': 'csv-notes-header-' + columnIndex,
 | 
			
		||||
									'className': Clipperz.PM.UI.Components.classNames(thClasses)
 | 
			
		||||
								}, [
 | 
			
		||||
									React.DOM.input({
 | 
			
		||||
										'type': 'checkbox',
 | 
			
		||||
										'id':  'csv-notes-input-' + columnIndex,
 | 
			
		||||
										'key': 'csv-notes-input-' + columnIndex,
 | 
			
		||||
										'ref': 'csv-notes-input-' + columnIndex,
 | 
			
		||||
										'checked': this.state['hiddenFields'][columnIndex],
 | 
			
		||||
										'onChange': MochiKit.Base.partial(this.onChangeCallback, columnIndex),
 | 
			
		||||
										'disabled': ((columnIndex == importContext.state('csvData.titleIndex')) || (columnIndex == importContext.state('csvData.notesIndex')))
 | 
			
		||||
									}),
 | 
			
		||||
									React.DOM.label({'htmlFor': 'csv-notes-input-' + columnIndex}, columnValue),
 | 
			
		||||
								]);
 | 
			
		||||
							} else {
 | 
			
		||||
								result = null;
 | 
			
		||||
							}
 | 
			
		||||
							
 | 
			
		||||
							return result;
 | 
			
		||||
						}, this), Clipperz.Base.zipWithRange(importContext.state('csvData.labels')))
 | 
			
		||||
					)
 | 
			
		||||
				),
 | 
			
		||||
				importContext.renderCsvTableBody(true)
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Hidden = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.HiddenClass);
 | 
			
		||||
@@ -0,0 +1,121 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.LabelsClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'useFirstRowAsLabels': this.props.importContext.state('csvData.useFirstRowAsLabels'),
 | 
			
		||||
			'labels': this.props.importContext.state('csvData.labels'),
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	updateImportContextState: function () {
 | 
			
		||||
		this.props.importContext.setState('csvData.useFirstRowAsLabels', this.state['useFirstRowAsLabels']);
 | 
			
		||||
		this.props.importContext.setState('csvData.labels', this.state['labels']);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	toggleFirstRow: function() {
 | 
			
		||||
		var newState;
 | 
			
		||||
 | 
			
		||||
		newState = this.state;
 | 
			
		||||
		newState['useFirstRowAsLabels'] = ! this.state['useFirstRowAsLabels'];
 | 
			
		||||
		if (newState['useFirstRowAsLabels']) {
 | 
			
		||||
			newState['labels'] = MochiKit.Base.map(Clipperz.Base.trim, this.props.importContext.state('csvData.data')[0]);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.setState(newState);
 | 
			
		||||
		this.updateImportContextState();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	onChangeLabelCallback: function(columnIndex) {
 | 
			
		||||
		var newState;
 | 
			
		||||
	
 | 
			
		||||
		newState = this.state;
 | 
			
		||||
		newState['labels'][columnIndex] = this.refs['csv-labels-input-' + columnIndex].getDOMNode().value;
 | 
			
		||||
 | 
			
		||||
		this.setState(newState);
 | 
			
		||||
		this.updateImportContextState();
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var	importContext = this.props.importContext;
 | 
			
		||||
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Set a label for each field in your data. If the first row of the CSV file contains field labels, tick off the checkbox below."),
 | 
			
		||||
			React.DOM.div({}, [
 | 
			
		||||
				React.DOM.input({
 | 
			
		||||
					'id': 'csv-labels-firstrow',
 | 
			
		||||
					'type': 'checkbox',
 | 
			
		||||
					'checked': this.state['useFirstRowAsLabels'],
 | 
			
		||||
					'onChange': this.toggleFirstRow
 | 
			
		||||
				}),
 | 
			
		||||
				React.DOM.label({'htmlFor':'csv-labels-firstrow'}, "Use the first row as labels")
 | 
			
		||||
			]),
 | 
			
		||||
			React.DOM.table({'className': 'csvTable'},[
 | 
			
		||||
				React.DOM.thead({},
 | 
			
		||||
					React.DOM.tr({},
 | 
			
		||||
						MochiKit.Base.map(MochiKit.Base.bind(function(cellInfo) {
 | 
			
		||||
							var result;
 | 
			
		||||
							var	columnIndex = cellInfo[0];
 | 
			
		||||
							var	columnValue = cellInfo[1];
 | 
			
		||||
							var thClasses = {
 | 
			
		||||
								'title': (columnIndex == importContext.state('csvData.titleIndex')),
 | 
			
		||||
								'notes': (columnIndex == importContext.state('csvData.notesIndex')),
 | 
			
		||||
							}
 | 
			
		||||
							
 | 
			
		||||
							if (importContext.state('csvData.selectedColumns')[columnIndex]) {
 | 
			
		||||
								result = React.DOM.th({
 | 
			
		||||
									'key':'csv-labels-header-' + columnIndex,
 | 
			
		||||
									'className': Clipperz.PM.UI.Components.classNames(thClasses)
 | 
			
		||||
								}, [
 | 
			
		||||
									React.DOM.input({
 | 
			
		||||
										'type': 'text',
 | 
			
		||||
										'id': 'csv-labels-input-' + columnIndex,
 | 
			
		||||
										'key': 'csv-labels-input-' + columnIndex,
 | 
			
		||||
										'ref': 'csv-labels-input-' + columnIndex,
 | 
			
		||||
										'value': columnValue,
 | 
			
		||||
										'placeholder': "…",
 | 
			
		||||
										'onChange': MochiKit.Base.partial(this.onChangeLabelCallback, columnIndex)
 | 
			
		||||
									})
 | 
			
		||||
								]);
 | 
			
		||||
							} else {
 | 
			
		||||
								result = null;
 | 
			
		||||
							}
 | 
			
		||||
							
 | 
			
		||||
							return result;
 | 
			
		||||
						}, this), Clipperz.Base.zipWithRange(this.state['labels']))
 | 
			
		||||
					)
 | 
			
		||||
				),
 | 
			
		||||
				importContext.renderCsvTableBody(true)
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Labels = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.LabelsClass);
 | 
			
		||||
@@ -0,0 +1,93 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.NotesClass = React.createClass({
 | 
			
		||||
	
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'notesIndex': this.props.importContext.state('csvData.notesIndex'),
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	onChangeCallback: function (columnIndex) {
 | 
			
		||||
		this.setState({'notesIndex': columnIndex});
 | 
			
		||||
		this.props.importContext.setState('csvData.notesIndex', columnIndex);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var importContext = this.props.importContext;
 | 
			
		||||
		
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Select the column that represents a \"notes\" field. (optional)"),
 | 
			
		||||
			React.DOM.input({
 | 
			
		||||
				'id': 'csv-notes-nonotes',
 | 
			
		||||
				'type': 'radio',
 | 
			
		||||
				'checked': ! this.state['notesIndex'],
 | 
			
		||||
				'onChange': MochiKit.Base.partial(this.onChangeCallback, null)
 | 
			
		||||
			}),
 | 
			
		||||
			React.DOM.label({'htmlFor': 'csv-notes-nonotes'}, "\"notes\" field not present"),
 | 
			
		||||
			React.DOM.table({'className': 'csvTable'},[
 | 
			
		||||
				React.DOM.thead({},
 | 
			
		||||
					React.DOM.tr({}, MochiKit.Base.map(MochiKit.Base.bind(function (cellInfo) {
 | 
			
		||||
						var result;
 | 
			
		||||
						var	columnIndex = cellInfo[0];
 | 
			
		||||
						var	columnValue = cellInfo[1];
 | 
			
		||||
						var thClasses = {
 | 
			
		||||
							'title': (columnIndex == importContext.state('csvData.titleIndex')),
 | 
			
		||||
							'notes': (columnIndex == importContext.state('csvData.notesIndex')),
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						if (importContext.state('csvData.selectedColumns')[columnIndex]) {
 | 
			
		||||
							result = React.DOM.th({
 | 
			
		||||
								'key': 'csv-notes-header-' + columnIndex,
 | 
			
		||||
								'className': Clipperz.PM.UI.Components.classNames(thClasses)
 | 
			
		||||
							}, [
 | 
			
		||||
								React.DOM.input({
 | 
			
		||||
									'type': 'radio',
 | 
			
		||||
									'id':  'csv-notes-input-' + columnIndex,
 | 
			
		||||
									'key': 'csv-notes-input-' + columnIndex,
 | 
			
		||||
									'ref': 'csv-notes-input-' + columnIndex,
 | 
			
		||||
									'checked':  (columnIndex == this.state['notesIndex']),
 | 
			
		||||
									'onChange': MochiKit.Base.partial(this.onChangeCallback, columnIndex),
 | 
			
		||||
									'disabled': (columnIndex == importContext.state('csvData.titleIndex'))
 | 
			
		||||
								}),
 | 
			
		||||
								React.DOM.label({'htmlFor': 'csv-notes-input-' + columnIndex}, columnValue),
 | 
			
		||||
							]);
 | 
			
		||||
						} else {
 | 
			
		||||
							result = null;
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						return result;
 | 
			
		||||
					}, this), Clipperz.Base.zipWithRange(this.props.importContext.state('csvData.labels'))))
 | 
			
		||||
				),
 | 
			
		||||
				importContext.renderCsvTableBody(true)
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Notes = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.NotesClass);
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.TitlesClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'titleIndex': this.props.importContext.state('csvData.titleIndex'),
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	onChangeCallback: function(columnIndex) {
 | 
			
		||||
		this.setState({'titleIndex': columnIndex});
 | 
			
		||||
		this.props.importContext.setState('csvData.titleIndex', columnIndex);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var importContext = this.props.importContext;		
 | 
			
		||||
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Select the column that contains the title of cards you are importing."),
 | 
			
		||||
			React.DOM.table({'className': 'csvTable'},[
 | 
			
		||||
				React.DOM.thead({},
 | 
			
		||||
					React.DOM.tr({}, MochiKit.Base.map(MochiKit.Base.bind(function (cellInfo) {
 | 
			
		||||
						var result;
 | 
			
		||||
						var	columnIndex = cellInfo[0];
 | 
			
		||||
						var columnValue = cellInfo[1];
 | 
			
		||||
						var thClasses = {
 | 
			
		||||
							'title': (columnIndex == importContext.state('csvData.titleIndex')),
 | 
			
		||||
							'notes': (columnIndex == importContext.state('csvData.notesIndex')),
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						if (importContext.state('csvData.selectedColumns')[columnIndex]) {
 | 
			
		||||
							result = React.DOM.th({
 | 
			
		||||
								'key':'csv-titles-header-' + columnIndex,
 | 
			
		||||
								'className': Clipperz.PM.UI.Components.classNames(thClasses)
 | 
			
		||||
							}, [
 | 
			
		||||
								React.DOM.input({
 | 
			
		||||
									'type': 'radio',
 | 
			
		||||
									'id':  'csv-titles-input-' + columnIndex,
 | 
			
		||||
									'key': 'csv-titles-input-' + columnIndex,
 | 
			
		||||
									'ref': 'csv-titles-input-' + columnIndex,
 | 
			
		||||
									'checked': (columnIndex == this.state['titleIndex']),
 | 
			
		||||
									'onChange': MochiKit.Base.partial(this.onChangeCallback, columnIndex)
 | 
			
		||||
								}),
 | 
			
		||||
								React.DOM.label({'htmlFor': 'csv-titles-input-' + columnIndex}, columnValue),
 | 
			
		||||
							]);
 | 
			
		||||
						} else {
 | 
			
		||||
							result = null;
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						return result;
 | 
			
		||||
					}, this), Clipperz.Base.zipWithRange(importContext.state('csvData.labels'))))
 | 
			
		||||
				),
 | 
			
		||||
				importContext.renderCsvTableBody(true)
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Titles = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.TitlesClass);
 | 
			
		||||
@@ -1,97 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass = React.createClass({
 | 
			
		||||
	
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'selectedColumns': this.props.importContext.selectedColumns
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	componentDidMount() {
 | 
			
		||||
		this.props.setNextStepCallback(this.handleNextStep);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	handleNextStep: function() {
 | 
			
		||||
		return this.state;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
	
 | 
			
		||||
	toggleColumn: function(columnN) {
 | 
			
		||||
		var newSelectedColumns;
 | 
			
		||||
	
 | 
			
		||||
		newSelectedColumns = this.state.selectedColumns;
 | 
			
		||||
		newSelectedColumns[columnN] = ! newSelectedColumns[columnN];
 | 
			
		||||
		
 | 
			
		||||
		this.setState({'selectedColumns': newSelectedColumns});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
//console.log(this.props.importContext);
 | 
			
		||||
		var columnSelectors;
 | 
			
		||||
		var rowCount;
 | 
			
		||||
		var i;
 | 
			
		||||
		
 | 
			
		||||
		columnSelectors = [];
 | 
			
		||||
		for (i=0; i<this.props.importContext.nColumns; i++) {
 | 
			
		||||
			columnSelectors.push( React.DOM.td({'key': 'csv-colsel-' + i}, React.DOM.input({
 | 
			
		||||
				'type': 'checkbox',
 | 
			
		||||
				'checked': this.state.selectedColumns[i],
 | 
			
		||||
				'onChange': MochiKit.Base.partial(this.toggleColumn,i)
 | 
			
		||||
			}) ) );
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		rowCount = 0;
 | 
			
		||||
		
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Select the columns you want to import."),
 | 
			
		||||
			React.DOM.table({'className': 'csvTable'},[
 | 
			
		||||
				React.DOM.thead({}, React.DOM.tr({'className': 'columnSelectors', 'key': 'csv-colsel'}, columnSelectors)),
 | 
			
		||||
				React.DOM.tbody({},
 | 
			
		||||
					MochiKit.Base.map(function(row){
 | 
			
		||||
						var cellCount;
 | 
			
		||||
						var result
 | 
			
		||||
						
 | 
			
		||||
						cellCount = 0;
 | 
			
		||||
						result = React.DOM.tr({'key': 'csv-row-' + (rowCount++)}, MochiKit.Base.map(function(cell) {
 | 
			
		||||
							return React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount++)},cell);
 | 
			
		||||
						}, row));
 | 
			
		||||
						rowCount++;
 | 
			
		||||
						
 | 
			
		||||
						return result;
 | 
			
		||||
					}, this.props.importContext.parsedCsv)
 | 
			
		||||
				),
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumns = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass);
 | 
			
		||||
@@ -1,148 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHiddenClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'hiddenColumns': this.props.importContext.hiddenColumns
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	componentDidMount() {
 | 
			
		||||
		this.props.setNextStepCallback(this.handleNextStep);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	handleNextStep: function() {
 | 
			
		||||
		//var importData = this.props.importState.importData;
 | 
			
		||||
		//var json = this.props.csvToJsonCallback();
 | 
			
		||||
		//this.props.setImportStateCallback({
 | 
			
		||||
		//	'importData': importData,
 | 
			
		||||
		//	'jsonToImport': json,
 | 
			
		||||
		//	'recordsToImport': MochiKit.Base.map(function(r){return r._importId},json),
 | 
			
		||||
		//	'currentStep': 'preview',
 | 
			
		||||
		//	'previousStep': 'csv-hidden'
 | 
			
		||||
		//})
 | 
			
		||||
		
 | 
			
		||||
		MochiKit.Base.update(this.props.importContext, this.state);
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
	
 | 
			
		||||
	onChangeCallback: function(columnN) {
 | 
			
		||||
		var newHiddenColumns = this.state.hiddenColumns;
 | 
			
		||||
	
 | 
			
		||||
		newHiddenColumns[columnN] = ! newHiddenColumns[columnN];
 | 
			
		||||
		
 | 
			
		||||
		this.setState({'hiddenColumns': newHiddenColumns});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var cellCount, rowCount;
 | 
			
		||||
		
 | 
			
		||||
		var importContext = this.props.importContext;
 | 
			
		||||
		
 | 
			
		||||
		cellCount = 0;
 | 
			
		||||
		rowCount = 0;
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Select the fields that should be hidden. (passwords, PINs, ...)"),
 | 
			
		||||
			React.DOM.table({'className': 'csvTable'},[
 | 
			
		||||
				React.DOM.thead({},
 | 
			
		||||
					
 | 
			
		||||
					React.DOM.tr({},
 | 
			
		||||
						MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
 | 
			
		||||
							var result;
 | 
			
		||||
							
 | 
			
		||||
							var thId = 'csv-notes-header-' + cellCount;
 | 
			
		||||
							var inputId = 'csv-notes-input-' + cellCount;
 | 
			
		||||
							
 | 
			
		||||
							if (! importContext.selectedColumns[cellCount]) {
 | 
			
		||||
								result = null;
 | 
			
		||||
							} else {
 | 
			
		||||
								result = React.DOM.th({'key': thId}, [
 | 
			
		||||
									React.DOM.label({'htmlFor': inputId}, importContext.getCsvLabels()[cellCount]),
 | 
			
		||||
									React.DOM.input({
 | 
			
		||||
										'type': 'checkbox',
 | 
			
		||||
										'id': inputId,
 | 
			
		||||
										'key': inputId,
 | 
			
		||||
										'ref': inputId,
 | 
			
		||||
										'checked': this.state.hiddenColumns[cellCount],
 | 
			
		||||
										'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount),
 | 
			
		||||
										'disabled': (cellCount == importContext.titlesColumn || cellCount == importContext.notesColumn)
 | 
			
		||||
									})
 | 
			
		||||
								]);
 | 
			
		||||
							}
 | 
			
		||||
							
 | 
			
		||||
							cellCount++;
 | 
			
		||||
							
 | 
			
		||||
							return result;
 | 
			
		||||
						}, this), importContext.parsedCsv[0])
 | 
			
		||||
					)
 | 
			
		||||
					
 | 
			
		||||
				),
 | 
			
		||||
				React.DOM.tbody({},
 | 
			
		||||
					
 | 
			
		||||
					MochiKit.Base.map(MochiKit.Base.bind(function(row){
 | 
			
		||||
						var result;
 | 
			
		||||
						
 | 
			
		||||
						cellCount = 0;
 | 
			
		||||
						
 | 
			
		||||
						if (rowCount == 0 && importContext.firstRowAsLabels) {
 | 
			
		||||
							result = null;
 | 
			
		||||
						} else {							
 | 
			
		||||
							result = React.DOM.tr({'key': 'csv-row-' + (rowCount)}, MochiKit.Base.map( function(cell) {
 | 
			
		||||
								var result;
 | 
			
		||||
								
 | 
			
		||||
								if (importContext.selectedColumns[cellCount]) {
 | 
			
		||||
									result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
 | 
			
		||||
								} else{
 | 
			
		||||
									result = null;
 | 
			
		||||
								}
 | 
			
		||||
								
 | 
			
		||||
								cellCount++;
 | 
			
		||||
								
 | 
			
		||||
								return  result;
 | 
			
		||||
							}, row));
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						rowCount++;
 | 
			
		||||
						
 | 
			
		||||
						return result;
 | 
			
		||||
					},this), importContext.parsedCsv)
 | 
			
		||||
					
 | 
			
		||||
				)
 | 
			
		||||
		
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHidden = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHiddenClass);
 | 
			
		||||
@@ -1,184 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabelsClass = React.createClass({
 | 
			
		||||
	
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'firstRowAsLabels': this.props.importContext.firstRowAsLabels,
 | 
			
		||||
			'columnLabels': this.props.importContext.columnLabels,
 | 
			
		||||
			'columnLabelsFirstrow': this.props.importContext.columnLabelsFirstrow
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	componentDidMount() {
 | 
			
		||||
		this.props.setNextStepCallback((this.isNextDisabled()) ? null : this.handleNextStep);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	handleNextStep: function() {
 | 
			
		||||
		return this.state;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	updateNextStatus: function() {
 | 
			
		||||
		this.props.setNextStepCallback((! this.isNextDisabled()) ? this.handleNextStep : null);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	isNextDisabled: function() {
 | 
			
		||||
		var result;
 | 
			
		||||
		
 | 
			
		||||
		var importContext = this.props.importContext;
 | 
			
		||||
		var columnLabels = this.getLabels();
 | 
			
		||||
		
 | 
			
		||||
		result = false;
 | 
			
		||||
		for (i in columnLabels) {
 | 
			
		||||
			result = result || ((columnLabels[i] == '')&&(importContext.selectedColumns[i]));
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
	
 | 
			
		||||
	getLabels: function() {
 | 
			
		||||
		return (this.state.firstRowAsLabels) ? this.state.columnLabelsFirstrow : this.state.columnLabels;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	toggleFirstRow: function() {
 | 
			
		||||
		var newState;
 | 
			
		||||
		var cellCount;
 | 
			
		||||
 | 
			
		||||
		newState = this.state;
 | 
			
		||||
		newState.firstRowAsLabels = ! newState.firstRowAsLabels;
 | 
			
		||||
		
 | 
			
		||||
		cellCount = 0;
 | 
			
		||||
		MochiKit.Base.map(function(cell){
 | 
			
		||||
			newState.columnLabelsFirstrow[cellCount++] = cell;
 | 
			
		||||
		}, this.props.importContext.parsedCsv[0]);
 | 
			
		||||
		
 | 
			
		||||
		this.updateNextStatus();
 | 
			
		||||
		
 | 
			
		||||
		this.setState(newState);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	onChangeCallback: function(columnN) {
 | 
			
		||||
		var newState;
 | 
			
		||||
	
 | 
			
		||||
		newState = this.state;
 | 
			
		||||
		if (newState.firstRowAsLabels) {
 | 
			
		||||
			newState.columnLabelsFirstrow[columnN] = this.refs['csv-labels-input-' + columnN].getDOMNode().value;
 | 
			
		||||
		} else {
 | 
			
		||||
			newState.columnLabels[columnN] = this.refs['csv-labels-input-' + columnN].getDOMNode().value;
 | 
			
		||||
		}
 | 
			
		||||
	
 | 
			
		||||
		this.updateNextStatus();
 | 
			
		||||
		
 | 
			
		||||
		this.setState(newState);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	render: function() {
 | 
			
		||||
//console.log("labels-render",this.props.importContext);
 | 
			
		||||
//return React.DOM.p({}, "labels")
 | 
			
		||||
		var rowCount, cellCount;
 | 
			
		||||
 | 
			
		||||
		var importContext = this.props.importContext;
 | 
			
		||||
		var columnLabels = this.getLabels();
 | 
			
		||||
		
 | 
			
		||||
		rowCount = 0;
 | 
			
		||||
		cellCount = 0;
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Set a label for each field in your data. If the first row of the CSV file contains field labels, tick off the checkbox below."),
 | 
			
		||||
			React.DOM.input({
 | 
			
		||||
				'id': 'csv-labels-firstrow',
 | 
			
		||||
				'type': 'checkbox',
 | 
			
		||||
				'checked': this.state.firstRowAsLabels,
 | 
			
		||||
				'onChange': this.toggleFirstRow
 | 
			
		||||
			}),
 | 
			
		||||
			React.DOM.label({'htmlFor':'csv-labels-firstrow'}, "Use the first row as labels"),
 | 
			
		||||
			React.DOM.table({'className': 'csvTable'},[
 | 
			
		||||
				React.DOM.thead({},
 | 
			
		||||
					React.DOM.tr({},
 | 
			
		||||
						MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
 | 
			
		||||
							var result;
 | 
			
		||||
							
 | 
			
		||||
							if (! importContext.selectedColumns[cellCount]) {
 | 
			
		||||
								result = null;
 | 
			
		||||
							} else {
 | 
			
		||||
								result = React.DOM.th({'key': 'csv-labels-header-' + cellCount}, [
 | 
			
		||||
									React.DOM.input({
 | 
			
		||||
										'type': 'text',
 | 
			
		||||
										'id': 'csv-labels-input-' + cellCount,
 | 
			
		||||
										'key': 'csv-labels-input-' + cellCount,
 | 
			
		||||
										'ref': 'csv-labels-input-' + cellCount,
 | 
			
		||||
										'value': columnLabels[cellCount],
 | 
			
		||||
										'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount)
 | 
			
		||||
									})
 | 
			
		||||
								]);
 | 
			
		||||
							}
 | 
			
		||||
							
 | 
			
		||||
							cellCount++;
 | 
			
		||||
							
 | 
			
		||||
							return result;
 | 
			
		||||
						}, this), this.props.importContext.parsedCsv[0])
 | 
			
		||||
					)
 | 
			
		||||
				),
 | 
			
		||||
				React.DOM.tbody({},
 | 
			
		||||
					MochiKit.Base.map(MochiKit.Base.bind(function(row){
 | 
			
		||||
						var result;
 | 
			
		||||
						
 | 
			
		||||
						cellCount = 0;
 | 
			
		||||
						
 | 
			
		||||
						if (rowCount == 0 && this.state.firstRowAsLabels) {
 | 
			
		||||
							result = null;
 | 
			
		||||
						} else {							
 | 
			
		||||
							result = React.DOM.tr({'key': 'csv-row-' + (rowCount)}, MochiKit.Base.map( function(cell) {
 | 
			
		||||
								var result;
 | 
			
		||||
								
 | 
			
		||||
								if (importContext.selectedColumns[cellCount]) {
 | 
			
		||||
									result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
 | 
			
		||||
								} else{
 | 
			
		||||
									result = null;
 | 
			
		||||
								}
 | 
			
		||||
								
 | 
			
		||||
								cellCount++;
 | 
			
		||||
								return  result;
 | 
			
		||||
							}, row));
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						rowCount++;
 | 
			
		||||
						
 | 
			
		||||
						return result;
 | 
			
		||||
					},this), importContext.parsedCsv)
 | 
			
		||||
				)
 | 
			
		||||
		
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabels = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabelsClass);
 | 
			
		||||
@@ -1,138 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotesClass = React.createClass({
 | 
			
		||||
	
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'notesColumn': this.props.importContext.notesColumn
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	componentDidMount() {
 | 
			
		||||
		this.props.setNextStepCallback(this.handleNextStep);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	handleNextStep: function() {
 | 
			
		||||
		return this.state;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
	
 | 
			
		||||
	onChangeCallback: function(columnN) {
 | 
			
		||||
		this.setState({'notesColumn': columnN});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var cellCount, rowCount;
 | 
			
		||||
		
 | 
			
		||||
		var importContext = this.props.importContext;
 | 
			
		||||
		
 | 
			
		||||
		cellCount = 0;
 | 
			
		||||
		rowCount = 0;
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Select the column that represents a \"notes\" field. (optional)"),
 | 
			
		||||
			React.DOM.input({
 | 
			
		||||
				'id': 'csv-notes-nonotes',
 | 
			
		||||
				'type': 'radio',
 | 
			
		||||
				'checked': ! this.state.notesColumn,
 | 
			
		||||
				'onChange': MochiKit.Base.partial(this.onChangeCallback, null)
 | 
			
		||||
			}),
 | 
			
		||||
			React.DOM.label({'htmlFor': 'csv-notes-nonotes'}, "\"notes\" field not present"),
 | 
			
		||||
			React.DOM.table({'className': 'csvTable'},[
 | 
			
		||||
				React.DOM.thead({},
 | 
			
		||||
 | 
			
		||||
				React.DOM.tr({},
 | 
			
		||||
						MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
 | 
			
		||||
							var result;
 | 
			
		||||
							
 | 
			
		||||
							var thId = 'csv-notes-header-' + cellCount;
 | 
			
		||||
							var inputId = 'csv-notes-input-' + cellCount;
 | 
			
		||||
							
 | 
			
		||||
							if (! importContext.selectedColumns[cellCount]) {
 | 
			
		||||
								result = null;
 | 
			
		||||
							} else {
 | 
			
		||||
								result = React.DOM.th({'key': thId}, [
 | 
			
		||||
									React.DOM.label({'htmlFor': inputId}, importContext.getCsvLabels()[cellCount]),
 | 
			
		||||
									React.DOM.input({
 | 
			
		||||
										'type': 'radio',
 | 
			
		||||
										'id': inputId,
 | 
			
		||||
										'key': inputId,
 | 
			
		||||
										'ref': inputId,
 | 
			
		||||
										'checked': cellCount == this.state.notesColumn,
 | 
			
		||||
										'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount),
 | 
			
		||||
										'disabled': cellCount == importContext.titlesColumn
 | 
			
		||||
									})
 | 
			
		||||
								]);
 | 
			
		||||
							}
 | 
			
		||||
							
 | 
			
		||||
							cellCount++;
 | 
			
		||||
							
 | 
			
		||||
							return result;
 | 
			
		||||
						}, this), importContext.parsedCsv[0])
 | 
			
		||||
					)
 | 
			
		||||
 | 
			
		||||
				),
 | 
			
		||||
				React.DOM.tbody({},
 | 
			
		||||
					
 | 
			
		||||
					MochiKit.Base.map(MochiKit.Base.bind(function(row){
 | 
			
		||||
						var result;
 | 
			
		||||
						
 | 
			
		||||
						cellCount = 0;
 | 
			
		||||
						
 | 
			
		||||
						if (rowCount == 0 && importContext.firstRowAsLabels) {
 | 
			
		||||
							result = null;
 | 
			
		||||
						} else {							
 | 
			
		||||
							result = React.DOM.tr({'key': 'csv-row-' + (rowCount)}, MochiKit.Base.map( function(cell) {
 | 
			
		||||
								var result;
 | 
			
		||||
								
 | 
			
		||||
								if (importContext.selectedColumns[cellCount]) {
 | 
			
		||||
									result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
 | 
			
		||||
								} else{
 | 
			
		||||
									result = null;
 | 
			
		||||
								}
 | 
			
		||||
								
 | 
			
		||||
								cellCount++;
 | 
			
		||||
								
 | 
			
		||||
								return  result;
 | 
			
		||||
							}, row));
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						rowCount++;
 | 
			
		||||
						
 | 
			
		||||
						return result;
 | 
			
		||||
					},this), importContext.parsedCsv)
 | 
			
		||||
				)
 | 
			
		||||
					
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotes = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotesClass);
 | 
			
		||||
@@ -1,146 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitlesClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'titlesColumn': this.props.importContext.titlesColumn,
 | 
			
		||||
			'notesColumn': this.props.importContext.notesColumn
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	componentDidMount() {
 | 
			
		||||
		this.props.setNextStepCallback((this.isNextDisabled()) ? null : this.handleNextStep);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	handleNextStep: function() {
 | 
			
		||||
		return this.state;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	updateNextStatus: function() {
 | 
			
		||||
		this.props.setNextStepCallback((! this.isNextDisabled()) ? this.handleNextStep : null);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	isNextDisabled: function() {
 | 
			
		||||
		return (this.state.titlesColumn != 0 && ! this.state.titlesColumn );
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
	
 | 
			
		||||
	onChangeCallback: function(columnN) {
 | 
			
		||||
		var newState = this.state;
 | 
			
		||||
	
 | 
			
		||||
		if (newState.notesColumn == columnN) {
 | 
			
		||||
			newState.notesColumn = null;
 | 
			
		||||
		}
 | 
			
		||||
		newState.titlesColumn = columnN;
 | 
			
		||||
		
 | 
			
		||||
		this.updateNextStatus();
 | 
			
		||||
		
 | 
			
		||||
		this.setState(newState);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var rowCount, cellCount;
 | 
			
		||||
		
 | 
			
		||||
		var importContext = this.props.importContext;		
 | 
			
		||||
		var columnLabels = importContext.getCsvLabels();
 | 
			
		||||
 | 
			
		||||
		rowCount = 0;
 | 
			
		||||
		cellCount = 0;
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.p({}, "Select the column that contains titles of the cards you are importing. (mandatory)"),
 | 
			
		||||
			React.DOM.table({'className': 'csvTable'},[
 | 
			
		||||
				React.DOM.thead({},
 | 
			
		||||
					React.DOM.tr({},
 | 
			
		||||
						MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
 | 
			
		||||
							var result;
 | 
			
		||||
							
 | 
			
		||||
							var thId = 'csv-titles-header-' + cellCount;
 | 
			
		||||
							var inputId = 'csv-titles-input-' + cellCount;
 | 
			
		||||
							
 | 
			
		||||
							if (! importContext.selectedColumns[cellCount]) {
 | 
			
		||||
								result = null;
 | 
			
		||||
							} else {
 | 
			
		||||
								result = React.DOM.th({'key': thId}, [
 | 
			
		||||
									React.DOM.label({'htmlFor': inputId}, columnLabels[cellCount]),
 | 
			
		||||
									React.DOM.input({
 | 
			
		||||
										'type': 'radio',
 | 
			
		||||
										'id': inputId,
 | 
			
		||||
										'key': inputId,
 | 
			
		||||
										'ref': inputId,
 | 
			
		||||
										'checked': cellCount == this.state.titlesColumn,
 | 
			
		||||
										'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount)
 | 
			
		||||
									})
 | 
			
		||||
								]);
 | 
			
		||||
							}
 | 
			
		||||
							
 | 
			
		||||
							cellCount++;
 | 
			
		||||
							
 | 
			
		||||
							return result;
 | 
			
		||||
						}, this), this.props.importContext.parsedCsv[0])
 | 
			
		||||
					)
 | 
			
		||||
				),
 | 
			
		||||
				React.DOM.tbody({},
 | 
			
		||||
					MochiKit.Base.map(MochiKit.Base.bind(function(row){
 | 
			
		||||
						var result;
 | 
			
		||||
						
 | 
			
		||||
						cellCount = 0;
 | 
			
		||||
						
 | 
			
		||||
						if (rowCount == 0 && importContext.firstRowAsLabels) {
 | 
			
		||||
							result = null;
 | 
			
		||||
						} else {							
 | 
			
		||||
							result = React.DOM.tr({'key': 'csv-row-'+(rowCount)}, MochiKit.Base.map( function(cell) {
 | 
			
		||||
								var result;
 | 
			
		||||
								
 | 
			
		||||
								if (importContext.selectedColumns[cellCount]) {
 | 
			
		||||
									result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
 | 
			
		||||
								} else{
 | 
			
		||||
									result = null;
 | 
			
		||||
								}
 | 
			
		||||
								
 | 
			
		||||
								cellCount++;
 | 
			
		||||
								
 | 
			
		||||
								return  result;
 | 
			
		||||
							}, row));
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						rowCount++;
 | 
			
		||||
						
 | 
			
		||||
						return result;
 | 
			
		||||
					},this), importContext.parsedCsv)
 | 
			
		||||
				)
 | 
			
		||||
		
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitles = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitlesClass);
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.ImportClass = React.createClass({
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	importHandler: function (anEvent) {
 | 
			
		||||
		MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'importCards', this.props.importContext.state('recordsToImport'));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		return React.DOM.div({}, [
 | 
			
		||||
			React.DOM.h5({}, "Cards to import: " + this.props.importContext.state('recordsToImport').length + " (of " + this.props.importContext.state('jsonData').length + ")"),
 | 
			
		||||
			React.DOM.a({'className': 'button import', 'onClick': this.importHandler}, "Import")
 | 
			
		||||
			
 | 
			
		||||
		]);
 | 
			
		||||
		
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.Import = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.ImportClass);
 | 
			
		||||
@@ -25,15 +25,16 @@ refer to http://www.clipperz.com.
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClass({
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
			'inputString': (this.props.importContext.inputString) ? this.props.importContext.inputString : null,
 | 
			
		||||
			'format': (this.props.importContext.format) ? this.props.importContext.format : null,
 | 
			
		||||
			'inputString': this.props.importContext.inputString(),
 | 
			
		||||
//			'inputString': (this.props.importContext.inputString) ? this.props.importContext.inputString : null,
 | 
			
		||||
//			'format': (this.props.importContext.format) ? this.props.importContext.format : null,
 | 
			
		||||
			//'parsedInput': (this.props.importContext.parsedInput) ? this.props.importContext.parsedInput : null,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
/*	
 | 
			
		||||
	componentDidMount: function() {
 | 
			
		||||
		this.updateNextStatus(this.state.inputString);
 | 
			
		||||
	},
 | 
			
		||||
@@ -46,12 +47,13 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
 | 
			
		||||
		var parsedInput;
 | 
			
		||||
		
 | 
			
		||||
		var inputString = this.refs['input-textarea'].getDOMNode().value.trim();
 | 
			
		||||
		
 | 
			
		||||
//		this.props.importContext.setData(inputString);
 | 
			
		||||
 | 
			
		||||
		result = {'inputString': inputString};
 | 
			
		||||
		
 | 
			
		||||
/*		
 | 
			
		||||
		parsedInput = this.parseJson(inputString);
 | 
			
		||||
		if (parsedInput) {
 | 
			
		||||
			MochiKit.Base.update(result,this.props.importContext.getInitialJsonContext(parsedInput));
 | 
			
		||||
			MochiKit.Base.update(result, this.props.importContext.getInitialJsonContext(parsedInput));
 | 
			
		||||
		} else {
 | 
			
		||||
			parsedInput = this.parseCsv(inputString);
 | 
			
		||||
			if (parsedInput) {
 | 
			
		||||
@@ -60,12 +62,12 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
 | 
			
		||||
				result = false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
* /		
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	updateNextStatus: function(newInputString) {
 | 
			
		||||
		this.props.setNextStepCallback((newInputString) ? this.handleNextStep : null);
 | 
			
		||||
//		this.props.setNextStepCallback((newInputString) ? this.handleNextStep : null);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
@@ -145,69 +147,87 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
 | 
			
		||||
		
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
*/
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
	
 | 
			
		||||
	handleUploadFiles: function(someFiles) {
 | 
			
		||||
 | 
			
		||||
	updateTextAreaContent: function (aValue, shouldMoveForwardToo) {
 | 
			
		||||
		var	value;
 | 
			
		||||
		value = this.props.importContext.setInputString(aValue, shouldMoveForwardToo);
 | 
			
		||||
		this.setState({'inputString': value});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	handleUploadFiles: function (someFiles) {
 | 
			
		||||
		var file;
 | 
			
		||||
		var reader;
 | 
			
		||||
		
 | 
			
		||||
		if (someFiles.length == 1) {
 | 
			
		||||
			file = someFiles[0];
 | 
			
		||||
				reader = new FileReader();
 | 
			
		||||
			reader = new FileReader();
 | 
			
		||||
			
 | 
			
		||||
			// Binary files are just thrown in the textarea as weird UTF-8 characters: should we do something about it?
 | 
			
		||||
			reader.onloadend = MochiKit.Base.bind(function() {
 | 
			
		||||
/*
 | 
			
		||||
				var	extractedJson = this.extractJsonFromClipperzExport(reader.result);
 | 
			
		||||
				var newInputString;
 | 
			
		||||
				
 | 
			
		||||
				// Binary files are just thrown in the textarea as weird UTF-8 characters: should we do something about it?
 | 
			
		||||
				reader.onloadend = MochiKit.Base.bind(function() {
 | 
			
		||||
					var extractedJson = this.extractJsonFromClipperzExport(reader.result);
 | 
			
		||||
					var newInputString;
 | 
			
		||||
					
 | 
			
		||||
					if (extractedJson) {
 | 
			
		||||
						newInputString = extractedJson;
 | 
			
		||||
					} else {
 | 
			
		||||
						newInputString = reader.result;
 | 
			
		||||
					}
 | 
			
		||||
				if (extractedJson) {
 | 
			
		||||
					newInputString = extractedJson;
 | 
			
		||||
				} else {
 | 
			
		||||
					newInputString = reader.result;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
					this.setState({'inputString': newInputString});
 | 
			
		||||
					this.updateNextStatus(newInputString);
 | 
			
		||||
				},this,reader);
 | 
			
		||||
				
 | 
			
		||||
				reader.readAsText(file);
 | 
			
		||||
				this.setState({'inputString': newInputString});
 | 
			
		||||
				this.updateNextStatus(newInputString);
 | 
			
		||||
*/
 | 
			
		||||
//console.log("handleUploadFiles", this.props.importContext, this.state, this);
 | 
			
		||||
//				this.props.importContext.setInputString(reader.result);
 | 
			
		||||
				this.updateTextAreaContent(reader.result, true);
 | 
			
		||||
			}, this);
 | 
			
		||||
			
 | 
			
		||||
			reader.readAsText(file);
 | 
			
		||||
		} else {
 | 
			
		||||
			// Should this be removed?
 | 
			
		||||
			alert("Error: expecting a file as input.");
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	handleOnDrop: function(e) {
 | 
			
		||||
		e.preventDefault();
 | 
			
		||||
	handleOnDrop: function (anEvent) {
 | 
			
		||||
		anEvent.preventDefault();
 | 
			
		||||
		
 | 
			
		||||
		this.handleUploadFiles(e.dataTransfer.files)
 | 
			
		||||
		this.handleUploadFiles(anEvent.dataTransfer.files)
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	handleInputFiles: function(e) {
 | 
			
		||||
		e.preventDefault();
 | 
			
		||||
	handleInputFiles: function (anEvent) {
 | 
			
		||||
		anEvent.preventDefault();
 | 
			
		||||
		
 | 
			
		||||
		this.handleUploadFiles(e.target.files)
 | 
			
		||||
		this.handleUploadFiles(anEvent.target.files)
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	handleOnDragOver: function(e) {
 | 
			
		||||
	handleOnDragOver: function (anEvent) {
 | 
			
		||||
		// Somehow necessary:
 | 
			
		||||
		// http://enome.github.io/javascript/2014/03/24/drag-and-drop-with-react-js.html
 | 
			
		||||
		// https://code.google.com/p/chromium/issues/detail?id=168387
 | 
			
		||||
		// http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
 | 
			
		||||
		e.preventDefault();
 | 
			
		||||
		anEvent.preventDefault();
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	handleTextareaChange: function() {
 | 
			
		||||
		var newInputString = this.refs['input-textarea'].getDOMNode().value;
 | 
			
		||||
		this.setState({'inputString': newInputString});
 | 
			
		||||
		this.updateNextStatus(newInputString);
 | 
			
		||||
	handleTextareaChange: function () {
 | 
			
		||||
//		var newInputString;
 | 
			
		||||
//		
 | 
			
		||||
//		newInputString = this.refs['input-textarea'].getDOMNode().value;
 | 
			
		||||
//		this.setState({'inputString': newInputString});
 | 
			
		||||
//		this.props.importContext.setInputString(newInputString);
 | 
			
		||||
 | 
			
		||||
		this.updateTextAreaContent(this.refs['input-textarea'].getDOMNode().value, false);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
	
 | 
			
		||||
	render: function() {
 | 
			
		||||
		return React.DOM.div({},[
 | 
			
		||||
			React.DOM.div({'className':'description'}, [
 | 
			
		||||
				React.DOM.p({}, "You can import either CSV data, or Clipperz data exported in JSON"),
 | 
			
		||||
			]),
 | 
			
		||||
			React.DOM.form({'key':'form', 'className':'importForm' }, [
 | 
			
		||||
				React.DOM.input({
 | 
			
		||||
					'type': 'file',
 | 
			
		||||
@@ -228,7 +248,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
 | 
			
		||||
						'key':'input-textarea',
 | 
			
		||||
						'name':'input-textarea',
 | 
			
		||||
						'ref':'input-textarea',
 | 
			
		||||
						'placeholder':"Open the JSON file exported from Clipperz in a text editor. Then copy and paste its content here.",
 | 
			
		||||
						'placeholder':"Copy or type your data here",
 | 
			
		||||
						'value': this.state.inputString,
 | 
			
		||||
						'onChange': this.handleTextareaChange,
 | 
			
		||||
						'onDragOver': this.handleOnDragOver,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,57 +27,44 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		if (this.props.importContext.format == 'csv') {
 | 
			
		||||
			return this.props.importContext.processCsv()
 | 
			
		||||
		} else {
 | 
			
		||||
			return {
 | 
			
		||||
				'jsonToImport': this.props.importContext.jsonToImport,
 | 
			
		||||
				'recordsToImport': this.props.importContext.recordsToImport,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	componentDidMount() {
 | 
			
		||||
		this.props.setNextStepCallback(this.handleImport);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	handleImport: function() {
 | 
			
		||||
		MochiKit.Base.update(this.props.importContext, this.state);
 | 
			
		||||
 | 
			
		||||
		var filteredImportData = MochiKit.Base.filter(
 | 
			
		||||
			MochiKit.Base.bind(function(r) {
 | 
			
		||||
				return this.isRecordToImport(r);
 | 
			
		||||
			}, this),
 | 
			
		||||
			this.state.jsonToImport
 | 
			
		||||
		var	recordsToImport;
 | 
			
		||||
		
 | 
			
		||||
		recordsToImport = MochiKit.Iter.reduce(
 | 
			
		||||
			function (acc, item) { acc[item['reference']] = item; return acc; },
 | 
			
		||||
			MochiKit.Base.filter(
 | 
			
		||||
				function (aRecord) { return !Clipperz.PM.DataModel.Record.labelContainsArchiveTag(aRecord['label']); },
 | 
			
		||||
				this.props.importContext.state('recordsToImport')
 | 
			
		||||
			),
 | 
			
		||||
			{}
 | 
			
		||||
		);
 | 
			
		||||
		
 | 
			
		||||
		MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'importCards', filteredImportData);
 | 
			
		||||
		
 | 
			
		||||
		return true;
 | 
			
		||||
 | 
			
		||||
		this.props.importContext.setState('recordsToImport', MochiKit.Base.values(recordsToImport));
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			'recordsToImport': recordsToImport
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	toggleRecordToImport: function(record) {
 | 
			
		||||
		var newRecordsToImport;
 | 
			
		||||
		var recordPosition;
 | 
			
		||||
 | 
			
		||||
		newRecordsToImport = this.state.recordsToImport;
 | 
			
		||||
		recordPosition = newRecordsToImport.indexOf(record._importId);
 | 
			
		||||
		
 | 
			
		||||
		if (recordPosition === -1) {
 | 
			
		||||
			newRecordsToImport.push(record._importId);
 | 
			
		||||
 | 
			
		||||
		if (this.isRecordToImport(record)) {
 | 
			
		||||
			delete newRecordsToImport[record['reference']];
 | 
			
		||||
		} else {
 | 
			
		||||
			newRecordsToImport.splice(recordPosition,1);
 | 
			
		||||
			newRecordsToImport[record['reference']] = record;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		this.setState({'recordsToImport': newRecordsToImport});
 | 
			
		||||
		this.props.importContext.setState('recordsToImport', MochiKit.Base.values(newRecordsToImport));
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	isRecordToImport: function(record) {
 | 
			
		||||
		return (this.state.recordsToImport.indexOf(record._importId)>=0) ? true : false;
 | 
			
		||||
		return (MochiKit.Base.keys(this.state.recordsToImport).indexOf(record['reference']) != -1) ? true : false;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getTags: function (aTitle) {
 | 
			
		||||
@@ -85,10 +72,10 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createCl
 | 
			
		||||
		var tagList;
 | 
			
		||||
		
 | 
			
		||||
		var tagObject = Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aTitle);
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		tagList = MochiKit.Base.keys(tagObject);
 | 
			
		||||
		tagList = MochiKit.Base.filter(function(aTag) { return tagObject[aTag] }, tagList);
 | 
			
		||||
		
 | 
			
		||||
		tagList = MochiKit.Base.filter(function(aTag) { return !Clipperz.PM.DataModel.Record.isSpecialTag(aTag); }, tagList);
 | 
			
		||||
 | 
			
		||||
		if (tagList.length > 0) {
 | 
			
		||||
			result = React.DOM.ul({'className': 'tagList'},
 | 
			
		||||
				MochiKit.Base.map(function(aTag){
 | 
			
		||||
@@ -102,50 +89,44 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createCl
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	renderCardFields: function(someFields) {
 | 
			
		||||
		return MochiKit.Base.map(function(key) {
 | 
			
		||||
	renderCardFields: function (someFields) {
 | 
			
		||||
		return MochiKit.Base.map(function (key) {
 | 
			
		||||
			var field = someFields[key];
 | 
			
		||||
			
 | 
			
		||||
			return [
 | 
			
		||||
				React.DOM.dt({},field.label),			
 | 
			
		||||
				React.DOM.dd({},field.value),			
 | 
			
		||||
				React.DOM.dt({}, field['label']),
 | 
			
		||||
				React.DOM.dd({'className': field['actionType'] + (field['hidden'] ? ' password' : '')}, field['value']),
 | 
			
		||||
			];
 | 
			
		||||
		} ,MochiKit.Base.keys(someFields));
 | 
			
		||||
		}, MochiKit.Base.keys(someFields));
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	renderCard: function(aCard) {
 | 
			
		||||
		var notesParagraph = (aCard.data.notes) ? React.DOM.p({'className': 'notes'}, aCard.data.notes) : null;
 | 
			
		||||
		return React.DOM.li({'className': 'card'}, [
 | 
			
		||||
 | 
			
		||||
	renderCard: function (aCard) {
 | 
			
		||||
		var	classes;
 | 
			
		||||
		
 | 
			
		||||
		classes = {
 | 
			
		||||
			'card': true,
 | 
			
		||||
			'archived': Clipperz.PM.DataModel.Record.labelContainsArchiveTag(aCard['label'])
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return React.DOM.li({'className':Clipperz.PM.UI.Components.classNames(classes)}, [
 | 
			
		||||
			React.DOM.input({
 | 
			
		||||
				'type': 'checkbox',
 | 
			
		||||
				'checked': this.isRecordToImport(aCard),
 | 
			
		||||
				'onChange': MochiKit.Base.partial(this.toggleRecordToImport,aCard)
 | 
			
		||||
				'onChange': MochiKit.Base.partial(this.toggleRecordToImport, aCard)
 | 
			
		||||
			}),
 | 
			
		||||
			React.DOM.h3({}, Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aCard.label)),
 | 
			
		||||
			this.getTags(aCard.label),
 | 
			
		||||
			React.DOM.dl({'className': 'fields'}, this.renderCardFields(aCard.currentVersion.fields)),
 | 
			
		||||
			notesParagraph
 | 
			
		||||
			React.DOM.div({'className': 'cardContent'}, [
 | 
			
		||||
				React.DOM.h3({}, Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aCard['label'])),
 | 
			
		||||
				this.getTags(aCard['label']),
 | 
			
		||||
				React.DOM.dl({'className': 'fields'}, this.renderCardFields(aCard['currentVersion']['fields'])),
 | 
			
		||||
				(aCard['data']['notes']) ? React.DOM.p({'className': 'notes'}, aCard['data']['notes']) : null
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var result;
 | 
			
		||||
		
 | 
			
		||||
		if (typeof(this.state.jsonToImport)=='undefined' || !this.state.jsonToImport) {
 | 
			
		||||
			result = "Error";
 | 
			
		||||
		} else {
 | 
			
		||||
			var renderedPreview = React.DOM.ul({},
 | 
			
		||||
				MochiKit.Base.map(this.renderCard, this.state.jsonToImport)
 | 
			
		||||
			);
 | 
			
		||||
			
 | 
			
		||||
			result =
 | 
			
		||||
				React.DOM.div({'className': 'jsonPreview'}, React.DOM.ul({},
 | 
			
		||||
					MochiKit.Base.map(this.renderCard, this.state.jsonToImport)
 | 
			
		||||
				) );
 | 
			
		||||
		}
 | 
			
		||||
			
 | 
			
		||||
		return React.DOM.div({},result);
 | 
			
		||||
	}
 | 
			
		||||
		return React.DOM.div({'className': 'preview'},
 | 
			
		||||
			React.DOM.ul({}, MochiKit.Base.map(MochiKit.Base.method(this, 'renderCard'), this.props.importContext.state('jsonData')))
 | 
			
		||||
		);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -75,17 +75,19 @@ Clipperz.PM.UI.Components.ExtraFeatures.DeleteAccountClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	render: function () {
 | 
			
		||||
		return	React.DOM.div({className:'extraFeature deleteAccount'}, [
 | 
			
		||||
			React.DOM.h1({}, "Delete Account"),
 | 
			
		||||
			React.DOM.div({'className':'header'}, [
 | 
			
		||||
				React.DOM.h1({}, "Delete Account"),
 | 
			
		||||
			]),
 | 
			
		||||
			React.DOM.div({'className': 'content'}, [
 | 
			
		||||
				React.DOM.form({'key':'form', 'className':'deleteAccountForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleDeleteAccount}, [
 | 
			
		||||
					React.DOM.div({'key':'fields'},[
 | 
			
		||||
						React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
 | 
			
		||||
						React.DOM.input({'key':'username', 'className':this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
 | 
			
		||||
						React.DOM.label({'key':'passphrase-label', 'autoFocus': 'true', 'htmlFor' :'passphrase'}, "passphrase"),
 | 
			
		||||
						React.DOM.input({'key':'passphrase', 'className':this.state['passphrase'], 'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase"}),
 | 
			
		||||
						React.DOM.label({'key':'username-label', 'htmlFor':'name'}, "username"),
 | 
			
		||||
						React.DOM.input({'key':'username', 'className': this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
 | 
			
		||||
						React.DOM.label({'key':'passphrase-label', 'autoFocus': 'true', 'htmlFor':'passphrase'}, "passphrase"),
 | 
			
		||||
						React.DOM.input({'key':'passphrase', 'className': this.state['passphrase'], 'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase"}),
 | 
			
		||||
						React.DOM.p({}, [
 | 
			
		||||
							React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
 | 
			
		||||
							React.DOM.span({}, "I understand that all my data will be deleted and that this action is irreversible.")
 | 
			
		||||
							React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'id':'deleteAccountConfirmCheckbox', 'name':'confirm', 'ref':'confirm'}),
 | 
			
		||||
							React.DOM.label({'htmlFor':'deleteAccountConfirmCheckbox'}, "I understand that all my data will be deleted and that this action is not reversible.")
 | 
			
		||||
						]),
 | 
			
		||||
					]),
 | 
			
		||||
					React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableDeleteAccountButton(), 'className':'button'}, "Delete my account")
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.DevicePINClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	render: function () {
 | 
			
		||||
		return	React.DOM.div({className:'extraFeature devicePIN'}, [
 | 
			
		||||
			React.DOM.h1({}, "Device PIN"),
 | 
			
		||||
			React.DOM.div({'className':'header'}, [
 | 
			
		||||
				React.DOM.h1({}, "Device PIN"),
 | 
			
		||||
			]),
 | 
			
		||||
			React.DOM.div({'className': 'content'}, [
 | 
			
		||||
				React.DOM.h3({}, this.props['PIN'])
 | 
			
		||||
			])
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										229
									
								
								frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/OTP.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/OTP.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,229 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.OTPClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	//	TODO: add print button!!!!
 | 
			
		||||
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
		return {
 | 
			
		||||
//			'selectedOTPs': [],
 | 
			
		||||
			'labelBeingEdited': null,
 | 
			
		||||
			'otpLabel': '',
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	propTypes: {
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	handleNew: function() {
 | 
			
		||||
		MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'createNewOTP');
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	handleDelete: function (anOtpReference) {
 | 
			
		||||
		MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'deleteOTPs', [anOtpReference]);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	enableOtpLabelEditing: function(anOTP) {
 | 
			
		||||
		var newState = this.state;
 | 
			
		||||
 | 
			
		||||
		newState['labelBeingEdited'] = anOTP.reference();
 | 
			
		||||
		newState['otpLabel'] = anOTP.label();
 | 
			
		||||
 | 
			
		||||
		this.setState(newState);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	updateOtpLabel: function (anOTP, anEvent) {
 | 
			
		||||
		var newState = this.state;
 | 
			
		||||
		var	newLabel = anEvent.target.value
 | 
			
		||||
 | 
			
		||||
		newState['otpLabel'] = newLabel;
 | 
			
		||||
 | 
			
		||||
		this.setState(newState);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	handleKeyPressed: function (anOTP, anEvent) {
 | 
			
		||||
		switch (anEvent.keyCode) {
 | 
			
		||||
			case  9: // tab
 | 
			
		||||
				this.handleLabelSave(anOTP);
 | 
			
		||||
				//	TODO: edit label of next OTP
 | 
			
		||||
				break;
 | 
			
		||||
			case 13: // enter
 | 
			
		||||
				this.handleLabelSave(anOTP);
 | 
			
		||||
			case 27: // escape
 | 
			
		||||
				this.handleLabelCancel(anOTP);
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	handleLabelSave: function (anOTP) {
 | 
			
		||||
		MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'changeOTPLabel', anOTP.reference(), this.state['otpLabel']);
 | 
			
		||||
		this.handleLabelCancel()
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	handleLabelCancel: function() {
 | 
			
		||||
		var newState;
 | 
			
		||||
 | 
			
		||||
		newState = this.state;
 | 
			
		||||
		newState['labelBeingEdited'] = null;
 | 
			
		||||
 | 
			
		||||
		this.setState(newState);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	handlePrint: function () {
 | 
			
		||||
		this.printOneTimePasswords();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	printOneTimePasswords: function () {
 | 
			
		||||
		var newWindow;
 | 
			
		||||
 | 
			
		||||
		var filteredOtpList = MochiKit.Base.filter(MochiKit.Base.bind(function (anOTP) {
 | 
			
		||||
			return (this.props.userInfo.otpsDetails[anOTP.reference()]['status'] == 'ACTIVE');
 | 
			
		||||
		}, this), this.props.userInfo.otpList);
 | 
			
		||||
 | 
			
		||||
		newWindow = window.open("", "");
 | 
			
		||||
		newWindow.document.write(
 | 
			
		||||
			'<!DOCTYPE html>' +
 | 
			
		||||
			'<html lang="en">' +
 | 
			
		||||
				'<head>' +
 | 
			
		||||
					'<meta charset="utf-8">' +
 | 
			
		||||
					'<title>Active One Time Passwords - Clipperz</title>' +
 | 
			
		||||
					'<style>' +
 | 
			
		||||
						'li { padding-bottom: 10px; }' +
 | 
			
		||||
						'li span { display: block; }' +
 | 
			
		||||
						'span.password { font-family: monospace; font-size: 16pt; padding-bottom: 5px; }' +
 | 
			
		||||
						'span.label { font-family: sans-serif; font-size: 12pt; }' +
 | 
			
		||||
					'</style>' +
 | 
			
		||||
				'</head>' +
 | 
			
		||||
				'<body>' +
 | 
			
		||||
					'<ul>' +
 | 
			
		||||
						MochiKit.Base.map(function (anOTP) {
 | 
			
		||||
							return	'<li>' +
 | 
			
		||||
										'<span class="password">' + anOTP.password() + '</span>' +
 | 
			
		||||
										'<span class="label">' + anOTP.label() + '</span>' +
 | 
			
		||||
									'</li>';
 | 
			
		||||
						}, filteredOtpList).join('') +
 | 
			
		||||
					'</ul>' +
 | 
			
		||||
				'</body>' +
 | 
			
		||||
			'</html>'
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		newWindow.document.close();
 | 
			
		||||
		newWindow.focus();
 | 
			
		||||
		newWindow.print();
 | 
			
		||||
		newWindow.close();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	renderOtpRows: function() {
 | 
			
		||||
		var result;
 | 
			
		||||
 | 
			
		||||
		if (this.props.userInfo.otpList) {
 | 
			
		||||
			result = MochiKit.Base.map(MochiKit.Base.bind(function (anOTP) {
 | 
			
		||||
				var	reference = anOTP.reference();
 | 
			
		||||
				var	otpDetailInfo = this.props.userInfo.otpsDetails[reference];
 | 
			
		||||
				var	labelComponent;
 | 
			
		||||
				var	otpStatusInfo;
 | 
			
		||||
				var	otpClasses;
 | 
			
		||||
				var	optLabel;
 | 
			
		||||
 | 
			
		||||
				otpClasses = {
 | 
			
		||||
					'otpDetail': true,
 | 
			
		||||
				};
 | 
			
		||||
				otpClasses[otpDetailInfo['status']] = true;
 | 
			
		||||
 | 
			
		||||
				if (otpDetailInfo['status'] != 'ACTIVE') {
 | 
			
		||||
					otpStatusInfo = React.DOM.div({'className':'otpStatusInfo'}, [
 | 
			
		||||
						React.DOM.span({'className':'otpStatus'}, otpDetailInfo['status']),
 | 
			
		||||
						React.DOM.span({'className':'requestDate'}, otpDetailInfo['requestDate']),
 | 
			
		||||
						React.DOM.span({'className':'connectionIp'}, otpDetailInfo['connection']['ip']),
 | 
			
		||||
						React.DOM.span({'className':'connectionBrowser'}, otpDetailInfo['connection']['browser']),
 | 
			
		||||
					])
 | 
			
		||||
				} else {
 | 
			
		||||
					otpStatusInfo = null;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (reference == this.state.labelBeingEdited) {
 | 
			
		||||
					labelComponent = React.DOM.input({
 | 
			
		||||
						'autoFocus':true,
 | 
			
		||||
						'value':this.state.otpLabel,
 | 
			
		||||
						'onChange':MochiKit.Base.partial(this.updateOtpLabel, anOTP),
 | 
			
		||||
						'onKeyDown':MochiKit.Base.partial(this.handleKeyPressed, anOTP),
 | 
			
		||||
					});
 | 
			
		||||
				} else {
 | 
			
		||||
					labelComponent = React.DOM.span({'onClick':MochiKit.Base.partial(this.enableOtpLabelEditing, anOTP)}, (anOTP.label()) ? anOTP.label() : "---")
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return React.DOM.li({
 | 
			
		||||
					'key':'otp-' + reference,
 | 
			
		||||
					'className':Clipperz.PM.UI.Components.classNames(otpClasses)
 | 
			
		||||
				}, [
 | 
			
		||||
					React.DOM.div({'className':'otpAction'}, [
 | 
			
		||||
						React.DOM.a({'onClick':MochiKit.Base.partial(this.handleDelete, reference)}, 'remove OTP'),
 | 
			
		||||
					]),
 | 
			
		||||
					React.DOM.div({'className':'otpInfo'}, [
 | 
			
		||||
						React.DOM.div({'className':'otpPassword'}, anOTP.password()),
 | 
			
		||||
						React.DOM.div({'className':'otpLabel'}, labelComponent),
 | 
			
		||||
						otpStatusInfo,
 | 
			
		||||
					]),
 | 
			
		||||
				]);
 | 
			
		||||
			}, this), this.props.userInfo.otpList);
 | 
			
		||||
		} else {
 | 
			
		||||
			result = React.DOM.li({}, React.DOM.div({}, "..."));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function () {
 | 
			
		||||
		return	React.DOM.div({'className':'extraFeature OTP'}, [
 | 
			
		||||
			React.DOM.div({'className':'header'}, [
 | 
			
		||||
				React.DOM.h1({}, "One Time Passwords"),
 | 
			
		||||
				React.DOM.div({'className':'description'}, [
 | 
			
		||||
					React.DOM.p({}, "A one-time passphrase works like your regular passphrase, but can be used only once. This makes it expecially useful for using it in places where keyloggers may be installed."),
 | 
			
		||||
				]),
 | 
			
		||||
				React.DOM.a({'className':'button', 'onClick':this.handlePrint}, "Print")
 | 
			
		||||
			]),
 | 
			
		||||
			React.DOM.div({'className':'content'}, [
 | 
			
		||||
				React.DOM.ul({'className':'otpList'}, this.renderOtpRows()),
 | 
			
		||||
				React.DOM.div({'className':'actions'}, [
 | 
			
		||||
					React.DOM.a({'onClick': this.handleNew}, "create new OTP"),
 | 
			
		||||
				]),
 | 
			
		||||
			])
 | 
			
		||||
		]);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.ExtraFeatures.OTP = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.OTPClass);
 | 
			
		||||
@@ -100,7 +100,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	render: function () {
 | 
			
		||||
		return	React.DOM.div({className:'extraFeature passphrase'}, [
 | 
			
		||||
			React.DOM.h1({}, "Change Passphrase"),
 | 
			
		||||
			React.DOM.div({'className':'header'}, [
 | 
			
		||||
				React.DOM.h1({}, "Change Passphrase"),
 | 
			
		||||
			]),
 | 
			
		||||
			React.DOM.div({'className': 'content'}, [
 | 
			
		||||
				React.DOM.form({'key':'form', 'className':'changePassphraseForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleChangePassphrase}, [
 | 
			
		||||
					React.DOM.div({'key':'fields'},[
 | 
			
		||||
@@ -117,8 +119,8 @@ Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
 | 
			
		||||
						React.DOM.input({'key':'confirm-new-passphrase', 'className':this.state['confirm-new-passphrase'], 'type':'password', 'name':'confirm-new-passphrase', 'ref':'confirm-new-passphrase', 'placeholder':"confirm new passphrase"}),
 | 
			
		||||
 | 
			
		||||
						React.DOM.p({}, [
 | 
			
		||||
							React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
 | 
			
		||||
							React.DOM.span({}, "I understand that Clipperz will not be able to recover a lost passphrase.")
 | 
			
		||||
							React.DOM.input({'key':'confirm', 'id':'changePassphraseConfirmCheckbox', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
 | 
			
		||||
							React.DOM.label({'htmlFor':'changePassphraseConfirmCheckbox'}, "I understand that Clipperz will not be able to help me recovering a lost passphrase.")
 | 
			
		||||
						]),
 | 
			
		||||
					]),
 | 
			
		||||
					React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableChangePassphraseButton(), 'className':'button'}, "Change passphrase"),
 | 
			
		||||
 
 | 
			
		||||
@@ -34,16 +34,16 @@ Clipperz.PM.UI.Components.Pages.MainPageClass = React.createClass({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	propTypes: {
 | 
			
		||||
		'tags':						React.PropTypes.object,
 | 
			
		||||
		'allTags':					React.PropTypes.array,
 | 
			
		||||
		'messageBox':				React.PropTypes.object.isRequired,
 | 
			
		||||
		'featureSet':				React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
 | 
			
		||||
		'features':					React.PropTypes.array.isRequired,
 | 
			
		||||
		'userInfo':					React.PropTypes.object.isRequired,
 | 
			
		||||
		'accountInfo':				React.PropTypes.object.isRequired,
 | 
			
		||||
//		'mediaQueryStyle':	React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
 | 
			
		||||
		'tags':				React.PropTypes.object,
 | 
			
		||||
		'allTags':			React.PropTypes.array,
 | 
			
		||||
		'messageBox':		React.PropTypes.object.isRequired,
 | 
			
		||||
		'featureSet':		React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
 | 
			
		||||
		'features':			React.PropTypes.array.isRequired,
 | 
			
		||||
		'userInfo':			React.PropTypes.object.isRequired,
 | 
			
		||||
		'accountInfo':		React.PropTypes.object.isRequired,
 | 
			
		||||
		'style':			React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
 | 
			
		||||
		//		'cards':			React.PropTypes.deferred.isRequired
 | 
			
		||||
//		'mediaQueryStyle':	React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
 | 
			
		||||
//		'cards':			React.PropTypes.deferred.isRequired
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getInitialState: function () {
 | 
			
		||||
@@ -60,8 +60,7 @@ Clipperz.PM.UI.Components.Pages.MainPageClass = React.createClass({
 | 
			
		||||
		};
 | 
			
		||||
		classes[this.props['style']] = true;
 | 
			
		||||
 | 
			
		||||
//console.log("MAIN PAGE", this.props['showGlobalMask']);
 | 
			
		||||
		return	React.DOM.div({'key':'mainPage', 'className':Clipperz.PM.UI.Components.classNames(classes)/*Clipperz.PM.UI.Components.classNames(classes)*/}, [
 | 
			
		||||
		return	React.DOM.div({'key':'mainPage', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
 | 
			
		||||
			this.props['style'] != 'extra-wide' ? Clipperz.PM.UI.Components.Panels.SelectionPanel(this.props) : null,
 | 
			
		||||
			Clipperz.PM.UI.Components.Panels.MainPanel(this.props),
 | 
			
		||||
			Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanel(this.props),
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,10 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.Panels');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	componentDidMount: function () {
 | 
			
		||||
		MochiKit.Signal.connect(Clipperz.Signal.NotificationCenter, 'closeSettingsPanel', MochiKit.Base.method(this, 'hideExtraFeatureContent'));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	settingsToggleHandler: function (anEvent) {
 | 
			
		||||
//console.log("settingsToggleHandler");
 | 
			
		||||
		this.hideExtraFeatureContent();
 | 
			
		||||
@@ -40,6 +44,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
 | 
			
		||||
	propTypes: {
 | 
			
		||||
		'accountInfo':	React.PropTypes.object.isRequired,
 | 
			
		||||
		'userInfo':		React.PropTypes.object.isRequired
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getInitialState: function() {
 | 
			
		||||
@@ -81,6 +86,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	extraFeaturesProps: function () {
 | 
			
		||||
// console.log("ExtraFeaturesPanel, extraFeaturesProps:",this.props);
 | 
			
		||||
		return this.props;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -95,10 +101,16 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	showExtraFeatureContent: function (aComponent, aComponentName) {
 | 
			
		||||
// console.log("ExtraFeaturesPanel, showExtraFeatureContent")
 | 
			
		||||
		if (aComponentName == 'OTP') {
 | 
			
		||||
			MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'updateOTPListAndDetails');
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		this.setState({
 | 
			
		||||
			'isFullyOpen':true,
 | 
			
		||||
			'extraFeatureComponentName': aComponentName,
 | 
			
		||||
			'extraFeatureContent': aComponent(this.extraFeaturesProps())
 | 
			
		||||
			// 'extraFeatureContent': aComponent(this.extraFeaturesProps()),
 | 
			
		||||
			'extraFeatureContentComponent': aComponent // Trying to instantiate the component at every render
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
@@ -124,16 +136,17 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
						React.DOM.ul({'key':'accountUL'}, [
 | 
			
		||||
							React.DOM.li({'key':'account_1', 'onClick':this.toggleExtraFeatureComponent('Passphrase'), 'className':(this.state['extraFeatureComponentName'] == 'Passphrase') ? 'selected' : ''}, [
 | 
			
		||||
								React.DOM.h2({'key':'account_1_h2'}, "Passphrase"),
 | 
			
		||||
								React.DOM.div({'key':'account_1_div'}, [
 | 
			
		||||
									React.DOM.p({'key':'account_1_p'}, "Change your account passphrase.")
 | 
			
		||||
								])
 | 
			
		||||
//								React.DOM.div({'key':'account_1_div'}, [
 | 
			
		||||
//									React.DOM.p({'key':'account_1_p'}, "Change your account passphrase.")
 | 
			
		||||
//								])
 | 
			
		||||
							]),
 | 
			
		||||
							React.DOM.li({'key':'account_2'}, [
 | 
			
		||||
							React.DOM.li({'key':'account_2', 'onClick':this.toggleExtraFeatureComponent('OTP')}, [
 | 
			
		||||
								React.DOM.h2({}, "One Time Passwords"),
 | 
			
		||||
								React.DOM.div({}, [
 | 
			
		||||
									React.DOM.p({}, "")
 | 
			
		||||
								])
 | 
			
		||||
//								React.DOM.div({}, [
 | 
			
		||||
//									React.DOM.p({}, "Manage your OTPs.")
 | 
			
		||||
//								])
 | 
			
		||||
							]),
 | 
			
		||||
/*
 | 
			
		||||
							React.DOM.li({'key':'account_3', 'onClick':this.toggleExtraFeatureComponent('DevicePIN')}, [
 | 
			
		||||
								React.DOM.h2({}, "Device PIN"),
 | 
			
		||||
								React.DOM.div({}, [
 | 
			
		||||
@@ -146,14 +159,16 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
									React.DOM.p({}, "")
 | 
			
		||||
								])
 | 
			
		||||
							]),
 | 
			
		||||
*/
 | 
			
		||||
							React.DOM.li({'key':'account_5', 'onClick':this.toggleExtraFeatureComponent('DeleteAccount'), 'className':(this.state['extraFeatureComponentName'] == 'DeleteAccount') ? 'selected' : ''}, [
 | 
			
		||||
								React.DOM.h2({}, "Delete account"),
 | 
			
		||||
								React.DOM.div({}, [
 | 
			
		||||
									React.DOM.p({}, "Delete your account for good.")
 | 
			
		||||
								])
 | 
			
		||||
//								React.DOM.div({}, [
 | 
			
		||||
//									React.DOM.p({}, "Delete your account for good.")
 | 
			
		||||
//								])
 | 
			
		||||
							])
 | 
			
		||||
						])
 | 
			
		||||
					]),
 | 
			
		||||
/*
 | 
			
		||||
					React.DOM.li({'key':'subscription', 'className':this.state['index']['subscription'] ? 'open' : 'closed'}, [
 | 
			
		||||
						React.DOM.h1({'onClick':this.toggleIndexState('subscription')}, "Subscription"),
 | 
			
		||||
						React.DOM.ul({'key':'subscription'}, [
 | 
			
		||||
@@ -183,6 +198,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
							])
 | 
			
		||||
						])
 | 
			
		||||
					]),
 | 
			
		||||
*/
 | 
			
		||||
					React.DOM.li({'key':'data', 'className':this.state['index']['data'] ? 'open' : 'closed'}, [
 | 
			
		||||
						React.DOM.h1({'onClick':this.toggleIndexState('data')}, "Data"),
 | 
			
		||||
						React.DOM.ul({'key':'data'}, [
 | 
			
		||||
@@ -195,22 +211,24 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
//							]),
 | 
			
		||||
							React.DOM.li({'key':'data_2', 'onClick':this.toggleExtraFeatureComponent('DataImport'), 'className':(this.state['extraFeatureComponentName'] == 'DataImport') ? 'selected' : ''}, [
 | 
			
		||||
								React.DOM.h2({}, "Import"),
 | 
			
		||||
								React.DOM.div({}, [
 | 
			
		||||
									React.DOM.p({}, "CSV, JSON, …")
 | 
			
		||||
								])
 | 
			
		||||
//								React.DOM.div({}, [
 | 
			
		||||
//									React.DOM.p({}, "CSV, JSON, …")
 | 
			
		||||
//								])
 | 
			
		||||
							]),
 | 
			
		||||
							React.DOM.li({'key':'data_3', 'onClick':this.toggleExtraFeatureComponent('DataExport'), 'className':(this.state['extraFeatureComponentName'] == 'DataExport') ? 'selected' : ''}, [
 | 
			
		||||
								React.DOM.h2({}, "Export"),
 | 
			
		||||
								React.DOM.div({}, [
 | 
			
		||||
									React.DOM.p({}, "Offline copy, printable version, JSON, …")
 | 
			
		||||
								])
 | 
			
		||||
//								React.DOM.div({}, [
 | 
			
		||||
//									React.DOM.p({}, "Offline copy, printable version, JSON, …")
 | 
			
		||||
//								])
 | 
			
		||||
							]),
 | 
			
		||||
/*
 | 
			
		||||
							React.DOM.li({'key':'data_4'}, [
 | 
			
		||||
								React.DOM.h2({}, "Sharing"),
 | 
			
		||||
								React.DOM.div({}, [
 | 
			
		||||
									React.DOM.p({}, "Securely share cards with other users")
 | 
			
		||||
								])
 | 
			
		||||
							])
 | 
			
		||||
*/
 | 
			
		||||
						])
 | 
			
		||||
					])
 | 
			
		||||
				])
 | 
			
		||||
@@ -227,7 +245,8 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
			React.DOM.header({}, [
 | 
			
		||||
				React.DOM.div({'className':'button', 'onClick':this.hideExtraFeatureContent}, "close")
 | 
			
		||||
			]),
 | 
			
		||||
			this.state['extraFeatureContent']
 | 
			
		||||
			// this.state['extraFeatureContent']
 | 
			
		||||
			this.state['extraFeatureContentComponent'] ? this.state['extraFeatureContentComponent'](this.props) : null
 | 
			
		||||
		]);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -242,7 +261,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
 | 
			
		||||
			'open': isOpen,
 | 
			
		||||
			'fullOpen': isFullyOpen
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
		return	React.DOM.div({'key':'extraFeaturesPanel', 'id':'extraFeaturesPanel', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
 | 
			
		||||
			this.renderIndex(),
 | 
			
		||||
			this.renderContent(),
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,11 @@ Clipperz.PM.UI.ExportController = function(args) {
 | 
			
		||||
			"padding: 10px;" +
 | 
			
		||||
			"border-bottom: 2px solid black;" +
 | 
			
		||||
		"}" +
 | 
			
		||||
	
 | 
			
		||||
		"header p span {" +
 | 
			
		||||
//			"padding: 0px 4px;" +
 | 
			
		||||
			"font-weight: bold;" +
 | 
			
		||||
		"}" +
 | 
			
		||||
 | 
			
		||||
		"h1 {" +
 | 
			
		||||
			"margin: 0px;" +
 | 
			
		||||
@@ -102,6 +107,7 @@ Clipperz.PM.UI.ExportController = function(args) {
 | 
			
		||||
			"margin: 0px;" +
 | 
			
		||||
			"margin-bottom: 5px;" +
 | 
			
		||||
			"padding-left: 10px;" +
 | 
			
		||||
			"font-size: 13pt;" +
 | 
			
		||||
		"}" +
 | 
			
		||||
 | 
			
		||||
		"div > div {" +
 | 
			
		||||
@@ -110,9 +116,20 @@ Clipperz.PM.UI.ExportController = function(args) {
 | 
			
		||||
			"padding: 10px;" +
 | 
			
		||||
		"}" +
 | 
			
		||||
 | 
			
		||||
		"li p, dd.hidden {" +
 | 
			
		||||
			"white-space: pre-wrap;" +
 | 
			
		||||
			"word-wrap: break-word;" +
 | 
			
		||||
			"font-family: monospace;" +
 | 
			
		||||
		"}" +
 | 
			
		||||
	
 | 
			
		||||
		"textarea {" +
 | 
			
		||||
			"width: 100%;" +
 | 
			
		||||
			"height: 200px;" +
 | 
			
		||||
			"display: none" +
 | 
			
		||||
//			"width: 100%;" +
 | 
			
		||||
//			"height: 200px;" +
 | 
			
		||||
		"}" +
 | 
			
		||||
 | 
			
		||||
		"a {" +
 | 
			
		||||
			"color: white;" +
 | 
			
		||||
		"}" +
 | 
			
		||||
 | 
			
		||||
		"@media print {" +
 | 
			
		||||
@@ -120,6 +137,10 @@ Clipperz.PM.UI.ExportController = function(args) {
 | 
			
		||||
				"display: none !important;" +
 | 
			
		||||
			"}" +
 | 
			
		||||
 | 
			
		||||
			"div > ul > li.archived {" +
 | 
			
		||||
				"color: #ddd;" +
 | 
			
		||||
			"}" +
 | 
			
		||||
 | 
			
		||||
			"ul > li {" +
 | 
			
		||||
				"page-break-inside: avoid;" +
 | 
			
		||||
			"}	" +
 | 
			
		||||
@@ -175,9 +196,11 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
 | 
			
		||||
			MochiKit.DOM.DIV({},
 | 
			
		||||
				MochiKit.DOM.DL({},
 | 
			
		||||
					MochiKit.Base.map(function(key) {
 | 
			
		||||
						var	isHiddenField = jsonCardData.currentVersion.fields[key]['hidden'];
 | 
			
		||||
 | 
			
		||||
						return [
 | 
			
		||||
							MochiKit.DOM.DT(jsonCardData.currentVersion.fields[key].label),
 | 
			
		||||
							MochiKit.DOM.DD(jsonCardData.currentVersion.fields[key].value),
 | 
			
		||||
							MochiKit.DOM.DT({}, jsonCardData.currentVersion.fields[key]['label']),
 | 
			
		||||
							MochiKit.DOM.DD((isHiddenField ? {'class':'hidden'} : {}), jsonCardData.currentVersion.fields[key]['value']),
 | 
			
		||||
						];
 | 
			
		||||
					}, MochiKit.Base.keys(jsonCardData.currentVersion.fields))
 | 
			
		||||
				)
 | 
			
		||||
@@ -187,22 +210,30 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'renderToHtml': function (jsonData) {
 | 
			
		||||
		var title;
 | 
			
		||||
		var style;
 | 
			
		||||
		var date;
 | 
			
		||||
		var body;
 | 
			
		||||
		var	title;
 | 
			
		||||
		var	style;
 | 
			
		||||
		var	now;
 | 
			
		||||
		var	dateString;
 | 
			
		||||
		var	timeString
 | 
			
		||||
		var	body;
 | 
			
		||||
 | 
			
		||||
		title = "Clipperz data";
 | 
			
		||||
		style = this._style;
 | 
			
		||||
		date  = "dd/mm/yyyy";
 | 
			
		||||
		now  = new XDate();
 | 
			
		||||
		dateString = now.toString("MMM d, yyyy");
 | 
			
		||||
		timeString = now.toString("HH:mm");
 | 
			
		||||
 | 
			
		||||
		body = MochiKit.DOM.DIV({},
 | 
			
		||||
			MochiKit.DOM.HEADER({},
 | 
			
		||||
				MochiKit.DOM.H1({}, "Your data on Clipperz"),
 | 
			
		||||
				MochiKit.DOM.H5({}, "Export date: " + date),
 | 
			
		||||
				MochiKit.DOM.H5({}, "Export generated on " + dateString + " at " + timeString),
 | 
			
		||||
				MochiKit.DOM.DIV({},
 | 
			
		||||
					MochiKit.DOM.P({}, "Security warning - This file lists the content of all your cards in a printer-friendly format. At the very bottom, the same content is also available in JSON format."),
 | 
			
		||||
					MochiKit.DOM.P({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file. We recommend to delete it as soon as it is no longer needed."),
 | 
			
		||||
					MochiKit.DOM.P({}, "Security warning - This file lists the content of all your cards in a printer-friendly format"),
 | 
			
		||||
					MochiKit.DOM.P({}, [
 | 
			
		||||
						"Beware: ",
 | 
			
		||||
						MochiKit.DOM.SPAN({'class':'warning'}, "all data are unencrypted!"),
 | 
			
		||||
						" Therefore make sure to properly store and manage this file. We recommend to delete it as soon as it is no longer needed."
 | 
			
		||||
					]),
 | 
			
		||||
					MochiKit.DOM.P({}, "If you are going to print its content on paper, store the printout in a safe and private place!"),
 | 
			
		||||
					MochiKit.DOM.P({}, "And, if you need to access your data when no Internet connection is available, please consider the much safer option of creating an offline copy.")
 | 
			
		||||
				)
 | 
			
		||||
@@ -210,31 +241,33 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
 | 
			
		||||
 | 
			
		||||
			MochiKit.DOM.UL({}, MochiKit.Base.map(this.renderCardToHtml, jsonData)),
 | 
			
		||||
			MochiKit.DOM.DIV({},
 | 
			
		||||
				MochiKit.DOM.H3({}, "JSON content"),
 | 
			
		||||
				MochiKit.DOM.DIV({},
 | 
			
		||||
					MochiKit.DOM.P({}, "Instructions on how to use JSON content"),
 | 
			
		||||
					MochiKit.DOM.P({}, "The JSON version of your data may be useful if you want to move the whole content of your Clipperz account to a new Clipperz account or recover a card that has been accidentally deleted. Just follow these instructions:"),
 | 
			
		||||
					MochiKit.DOM.OL({},
 | 
			
		||||
						MochiKit.DOM.LI({}, "Login to your Clipperz account and go to \"Data > Import\"."),
 | 
			
		||||
						MochiKit.DOM.LI({}, "Select the JSON option."),
 | 
			
		||||
						MochiKit.DOM.LI({}, "Copy and paste the JSON content in the form.")
 | 
			
		||||
					),
 | 
			
		||||
					MochiKit.DOM.P({}, "Of course, the unencrypted JSON content won't be transmitted to the Clipperz server.")
 | 
			
		||||
				),
 | 
			
		||||
//				MochiKit.DOM.H3({}, "JSON content"),
 | 
			
		||||
//				MochiKit.DOM.DIV({},
 | 
			
		||||
//					MochiKit.DOM.P({}, "Instructions on how to use JSON content"),
 | 
			
		||||
//					MochiKit.DOM.P({}, "The JSON version of your data may be useful if you want to move the whole content of your Clipperz account to a new Clipperz account or recover a card that has been accidentally deleted. Just follow these instructions:"),
 | 
			
		||||
//					MochiKit.DOM.OL({},
 | 
			
		||||
//						MochiKit.DOM.LI({}, "Login to your Clipperz account and go to \"Data > Import\"."),
 | 
			
		||||
//						MochiKit.DOM.LI({}, "Select the JSON option."),
 | 
			
		||||
//						MochiKit.DOM.LI({}, "Copy and paste the JSON content in the form.")
 | 
			
		||||
//					),
 | 
			
		||||
//					MochiKit.DOM.P({}, "Of course, the unencrypted JSON content won't be transmitted to the Clipperz server.")
 | 
			
		||||
//				),
 | 
			
		||||
				MochiKit.DOM.TEXTAREA({}, Clipperz.Base.serializeJSON(jsonData)),
 | 
			
		||||
				MochiKit.DOM.FOOTER({},
 | 
			
		||||
					MochiKit.DOM.P({},
 | 
			
		||||
						"This file has been downloaded from clipperz.is, a service by Clipperz Srl. - ",
 | 
			
		||||
						"This file has been downloaded from ",
 | 
			
		||||
						MochiKit.DOM.A({'href':'https://clipperz.is'} ,"clipperz.is"),
 | 
			
		||||
						", a service by Clipperz Srl. - ",
 | 
			
		||||
						MochiKit.DOM.A({'href':'https://clipperz.is/terms_service/'}, "Terms of service"),
 | 
			
		||||
						" - ",
 | 
			
		||||
						MochiKit.DOM.A({'href':'https://clipperz.is/privacy_policy/'}, "Privacy policy")
 | 
			
		||||
					),
 | 
			
		||||
					MochiKit.DOM.H4({}, "Clipperz - keep it to yourself")
 | 
			
		||||
					)
 | 
			
		||||
//					MochiKit.DOM.H4({}, "Clipperz - keep it to yourself")
 | 
			
		||||
				)
 | 
			
		||||
			)
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		return '<html><head><title>' + title + '</title><style type="text/css">' + style + '</style></head><body>' + MochiKit.DOM.toHTML(body) + '</body></html>';
 | 
			
		||||
		return '<html><head><title>' + title + '</title><style type="text/css">' + style + '</style><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' + MochiKit.DOM.toHTML(body) + '</body></html>';
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//----------------------------------------------------------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -24,125 +24,422 @@ refer to http://www.clipperz.com.
 | 
			
		||||
"use strict";
 | 
			
		||||
Clipperz.Base.module('Clipperz.PM.UI');
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.UI.ImportContext = function(args) {
 | 
			
		||||
Clipperz.PM.UI.ImportContext = function(anInputComponent) {
 | 
			
		||||
	this._importComponent = anInputComponent;
 | 
			
		||||
 | 
			
		||||
	this._status = {
 | 
			
		||||
		'inputString': '',
 | 
			
		||||
		'isInputStringValid': false,
 | 
			
		||||
		'inputFormat': 'UNDEFINED',
 | 
			
		||||
		'currentStep': 'Input',
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	this.inputString = null;
 | 
			
		||||
		
 | 
			
		||||
	return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MochiKit.Base.update(Clipperz.PM.UI.ImportContext.prototype, {
 | 
			
		||||
 | 
			
		||||
	'toString': function() {
 | 
			
		||||
	toString: function() {
 | 
			
		||||
		return "Clipperz.PM.UI.ImportContext";
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
	
 | 
			
		||||
	'resetContext': function() {
 | 
			
		||||
		delete this.inputString;
 | 
			
		||||
		delete this.format;
 | 
			
		||||
		delete this.jsonToImport;
 | 
			
		||||
		delete this.recordsToImport;
 | 
			
		||||
	release: function () {
 | 
			
		||||
		this._importComponent = null;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	'getInitialJsonContext': function(aJsonList) {
 | 
			
		||||
		return {
 | 
			
		||||
			'format': 'json',
 | 
			
		||||
			'jsonToImport': aJsonList,
 | 
			
		||||
			'recordsToImport': aJsonList.map(function(d){return d._importId})
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	'getInitialCsvContext': function(aCsvTable) {
 | 
			
		||||
		var result;
 | 
			
		||||
		var nColumns;
 | 
			
		||||
		var defaultSelectedColumns;
 | 
			
		||||
		var defaultHiddenColumns;
 | 
			
		||||
		var defaultColumnLabels;
 | 
			
		||||
		var columnLabelsFirstrow;
 | 
			
		||||
		var i;
 | 
			
		||||
 | 
			
		||||
		nColumns = aCsvTable[0].length;
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	ensureStateConsistency: function () {
 | 
			
		||||
		var	csvData;
 | 
			
		||||
		
 | 
			
		||||
		defaultSelectedColumns = {};
 | 
			
		||||
		defaultHiddenColumns = {};
 | 
			
		||||
		defaultColumnLabels = {};
 | 
			
		||||
		columnLabelsFirstrow = {};
 | 
			
		||||
		for (i=0; i<nColumns; i++) {
 | 
			
		||||
			defaultSelectedColumns[i] = true;
 | 
			
		||||
			defaultHiddenColumns[i] = false;
 | 
			
		||||
			defaultColumnLabels[i] = "";
 | 
			
		||||
			columnLabelsFirstrow[i] = aCsvTable[0][i];
 | 
			
		||||
		}
 | 
			
		||||
	
 | 
			
		||||
		return {
 | 
			
		||||
			'format': 'csv',
 | 
			
		||||
			'parsedCsv': aCsvTable,
 | 
			
		||||
			'nColumns': nColumns,
 | 
			
		||||
			'selectedColumns': defaultSelectedColumns,
 | 
			
		||||
			'firstRowAsLabels': false,
 | 
			
		||||
			'columnLabels': defaultColumnLabels,
 | 
			
		||||
			'columnLabelsFirstrow': columnLabelsFirstrow,
 | 
			
		||||
			'titlesColumn': null,
 | 
			
		||||
			'notesColumn': null,
 | 
			
		||||
			'hiddenColumns': defaultHiddenColumns,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	'getCsvLabels': function() {
 | 
			
		||||
		return (this.firstRowAsLabels) ? this.columnLabelsFirstrow : this.columnLabels;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	'processCsv': function() {
 | 
			
		||||
		var jsonToImport;
 | 
			
		||||
		var recordsToImport;
 | 
			
		||||
		var columnLabels = this.getCsvLabels();
 | 
			
		||||
		
 | 
			
		||||
		jsonToImport = [];
 | 
			
		||||
		
 | 
			
		||||
		for (rowCount=0; rowCount<this.parsedCsv.length; rowCount++) {
 | 
			
		||||
			var rowCount,cellCount;
 | 
			
		||||
		csvData = this._status['csvData'];
 | 
			
		||||
		if (csvData != null) {
 | 
			
		||||
			if (csvData['titleIndex'] == csvData['notesIndex']) {
 | 
			
		||||
				csvData['notesIndex'] = null;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (rowCount != 0 || ! this.firstRowAsLabels) {
 | 
			
		||||
				var record;
 | 
			
		||||
				
 | 
			
		||||
				record = {};
 | 
			
		||||
				record._importId = rowCount;
 | 
			
		||||
				record.label = this.parsedCsv[rowCount][this.titlesColumn];
 | 
			
		||||
				record.data = {'notes': ""};
 | 
			
		||||
				record.currentVersion = {'fields': {}};
 | 
			
		||||
				
 | 
			
		||||
				for (cellCount=0; cellCount<this.parsedCsv[rowCount].length; cellCount++) {
 | 
			
		||||
					if (this.selectedColumns[cellCount] && cellCount != this.notesColumn && cellCount != this.titlesColumn) {
 | 
			
		||||
						var fieldKey = rowCount+"-"+cellCount;
 | 
			
		||||
						var field = {
 | 
			
		||||
							'label': columnLabels[cellCount],
 | 
			
		||||
							'value': this.parsedCsv[rowCount][cellCount],
 | 
			
		||||
							'hidden': this.hiddenColumns[cellCount]
 | 
			
		||||
						};
 | 
			
		||||
						record.currentVersion.fields[fieldKey] = field;
 | 
			
		||||
					} else if (cellCount == this.notesColumn) {
 | 
			
		||||
						record.data.notes = this.parsedCsv[rowCount][cellCount];
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
				jsonToImport.push(record);
 | 
			
		||||
			csvData['hiddenFields'][csvData['titleIndex']] = false;
 | 
			
		||||
			csvData['hiddenFields'][csvData['notesIndex']] = false;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	updateImportComponent: function () {
 | 
			
		||||
		this._importComponent.setState({'importContext': this});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	state: function (aKeyPath) {
 | 
			
		||||
		var	result;
 | 
			
		||||
		var	keys;
 | 
			
		||||
		var	i, c;
 | 
			
		||||
 | 
			
		||||
		result = this._status;
 | 
			
		||||
		keys = aKeyPath.split('.');
 | 
			
		||||
		c = keys.length;
 | 
			
		||||
		
 | 
			
		||||
		for (i=0; i<c; i++) {
 | 
			
		||||
			result = result[keys[i]];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	setState: function (aKeyPath, aValue) {
 | 
			
		||||
		var	object;
 | 
			
		||||
		var	keys;
 | 
			
		||||
		var i, c;
 | 
			
		||||
		
 | 
			
		||||
		object = this._status;
 | 
			
		||||
		keys = aKeyPath.split('.');
 | 
			
		||||
		c = keys.length - 1;
 | 
			
		||||
		
 | 
			
		||||
		for (i=0; i<c; i++) {
 | 
			
		||||
			object = object[keys[i]];
 | 
			
		||||
		}
 | 
			
		||||
		object[keys[c]] = aValue;
 | 
			
		||||
 | 
			
		||||
		this.ensureStateConsistency();
 | 
			
		||||
		this.updateImportComponent();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	stepStatus: function (aStep) {
 | 
			
		||||
		var result;
 | 
			
		||||
		
 | 
			
		||||
		if (aStep == this.currentStep()) {
 | 
			
		||||
			result = 'active';
 | 
			
		||||
		} else {
 | 
			
		||||
			result = 'disabled';
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	currentStep: function () {
 | 
			
		||||
		return this.state('currentStep');
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	setCurrentStep: function (aValue) {
 | 
			
		||||
		this.setState('currentStep', aValue);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	currentStepIndex: function () {
 | 
			
		||||
		return MochiKit.Base.findValue(this.steps(), this.currentStep());
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	steps: function () {
 | 
			
		||||
		var	result;
 | 
			
		||||
		
 | 
			
		||||
		if (this.inputFormat() == 'JSON') {
 | 
			
		||||
			result = ['Input', 'Preview', 'Import'];
 | 
			
		||||
		} else if (this.inputFormat() == 'CSV') {
 | 
			
		||||
			result = ['Input', 'CSV.Columns', 'CSV.Labels', 'CSV.Titles', 'CSV.Notes', 'CSV.Hidden', 'Preview', 'Import'];
 | 
			
		||||
		} else {
 | 
			
		||||
			result = ['Input'];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	inputFormat: function () {
 | 
			
		||||
		return this.state('inputFormat');
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	setInputFormat: function (aValue) {
 | 
			
		||||
		this.setState('inputFormat', aValue);
 | 
			
		||||
		
 | 
			
		||||
		if (aValue == 'UNDEFINED') {
 | 
			
		||||
			this.setIsInputStringValid(false);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.setIsInputStringValid(true);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//-----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	isInputStringValid: function () {
 | 
			
		||||
		return this.state('isInputStringValid');
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	setIsInputStringValid: function (aValue) {
 | 
			
		||||
		this.setState('isInputStringValid', aValue);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	showJsonPreview: function (jsonData) {
 | 
			
		||||
		MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'setCurrentStep', 'Preview'));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	setJsonData: function (someData) {
 | 
			
		||||
		if (someData != null) {
 | 
			
		||||
			this.setInputFormat('JSON');
 | 
			
		||||
		}
 | 
			
		||||
		this.setState('jsonData', someData);
 | 
			
		||||
		//	TODO: before setting 'recordsToImport', filter 'someData' to remove cards marked as ARCHIVED
 | 
			
		||||
		this.setState('recordsToImport', someData);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	enhanceJsonDataWithCardReferences: function (someJsonData) {
 | 
			
		||||
		return MochiKit.Base.map(function (item) {
 | 
			
		||||
			item['reference'] = Clipperz.PM.Crypto.randomKey();
 | 
			
		||||
			item['label'] = "COPY - " + item['label'];
 | 
			
		||||
			return item;
 | 
			
		||||
		}, someJsonData);
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//-----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	startCsvWizard: function (csvData) {
 | 
			
		||||
		MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'setCurrentStep', 'CSV.Columns'));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	setCsvData: function (someData) {
 | 
			
		||||
		if (someData != null) {
 | 
			
		||||
			this.setInputFormat('CSV');
 | 
			
		||||
 | 
			
		||||
			this.setState('csvData', {
 | 
			
		||||
				'data': someData,
 | 
			
		||||
				'selectedColumns': MochiKit.Base.map(function () { return true; }, someData[0]),
 | 
			
		||||
				'labels': MochiKit.Base.map(function () { return ""; }, someData[0]),
 | 
			
		||||
				'useFirstRowAsLabels': false,
 | 
			
		||||
				'titleIndex': null,
 | 
			
		||||
				'notesIndex': null,
 | 
			
		||||
				'hiddenFields': MochiKit.Base.map(function () { return false; }, someData[0]),
 | 
			
		||||
			});
 | 
			
		||||
		} else {
 | 
			
		||||
			this.setState('csvData', null);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	csvFillEmptyCells: function(table) {
 | 
			
		||||
		var result = [];
 | 
			
		||||
		var i,j;
 | 
			
		||||
 | 
			
		||||
		var maxColumns = MochiKit.Iter.reduce(function(prev,next) {
 | 
			
		||||
			return Math.max(prev,next)
 | 
			
		||||
		}, MochiKit.Base.map(function(row) {return row.length;}, table) );
 | 
			
		||||
 | 
			
		||||
		for (i=0; i<table.length; i++) {
 | 
			
		||||
			result[i] = [];
 | 
			
		||||
			for (j=0; j<maxColumns; j++) {
 | 
			
		||||
				result[i][j] = (typeof(table[i][j]) != "undefined") ? table[i][j] : "";
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (typeof(this.recordsToImport) == 'undefined') {
 | 
			
		||||
			recordsToImport = MochiKit.Base.map(function(r){return r._importId},jsonToImport);
 | 
			
		||||
		} else {
 | 
			
		||||
			recordsToImport = this.recordsToImport;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return {
 | 
			
		||||
			'jsonToImport': jsonToImport,
 | 
			
		||||
			'recordsToImport': recordsToImport
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	//............................................................................
 | 
			
		||||
 | 
			
		||||
	'createJsonDataFromCSV': function(csvData) {
 | 
			
		||||
		return MochiKit.Base.map(function (row) {
 | 
			
		||||
			var	fields;
 | 
			
		||||
 | 
			
		||||
			fields = MochiKit.Base.map(function (cellInfo) {
 | 
			
		||||
				return {
 | 
			
		||||
					'label': csvData['labels'][cellInfo[0]],
 | 
			
		||||
					'value': cellInfo[1],
 | 
			
		||||
					'hidden': csvData['hiddenFields'][cellInfo[0]],
 | 
			
		||||
				}
 | 
			
		||||
			},	MochiKit.Base.filter(function (cellInfo) {
 | 
			
		||||
					return ((csvData['titleIndex'] != cellInfo[0]) && (csvData['notesIndex'] != cellInfo[0]) && (csvData['selectedColumns'][cellInfo[0]]));
 | 
			
		||||
				}, Clipperz.Base.zipWithRange(row))
 | 
			
		||||
			);
 | 
			
		||||
			
 | 
			
		||||
			return {
 | 
			
		||||
				'reference': Clipperz.PM.Crypto.randomKey(),
 | 
			
		||||
				'label': row[csvData['titleIndex']],
 | 
			
		||||
				'data': {
 | 
			
		||||
					'notes': ((csvData['notesIndex'] != null) ? row[csvData['notesIndex']] : "")
 | 
			
		||||
				},
 | 
			
		||||
				'currentVersion': {
 | 
			
		||||
					'fields': MochiKit.Iter.reduce(function (accumulator, field) {
 | 
			
		||||
						accumulator[Clipperz.PM.Crypto.randomKey()] = field; return accumulator;
 | 
			
		||||
					}, fields, {})
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
		}, (csvData['useFirstRowAsLabels']) ? csvData['data'].slice(1) : csvData['data']);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	inputString: function () {
 | 
			
		||||
		return this.state('inputString');
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	setInputString: function (aValue, isUploadingFile) {
 | 
			
		||||
		var textarea;
 | 
			
		||||
		var result;
 | 
			
		||||
 | 
			
		||||
		result = aValue;
 | 
			
		||||
		this.setInputFormat('UNDEFINED');
 | 
			
		||||
		this.setJsonData(null);
 | 
			
		||||
		this.setCsvData(null);
 | 
			
		||||
 | 
			
		||||
		if (isUploadingFile) {
 | 
			
		||||
			var isExportContent;
 | 
			
		||||
			
 | 
			
		||||
			isExportContent = new RegExp('.*<textarea>(.*)<\/textarea>.*', 'g');
 | 
			
		||||
			if (isExportContent.test(aValue)) {
 | 
			
		||||
				textarea = MochiKit.DOM.TEXTAREA();
 | 
			
		||||
				textarea.innerHTML = aValue.replace(isExportContent, '$1');
 | 
			
		||||
				result = textarea.innerHTML;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			var	jsonData;
 | 
			
		||||
 | 
			
		||||
			jsonData = JSON.parse(result);
 | 
			
		||||
			jsonData = this.enhanceJsonDataWithCardReferences(jsonData);
 | 
			
		||||
 | 
			
		||||
			this.setJsonData(jsonData);
 | 
			
		||||
			if (isUploadingFile == true) {
 | 
			
		||||
				this.showJsonPreview();
 | 
			
		||||
			}
 | 
			
		||||
		} catch(e) {
 | 
			
		||||
			var parsedCsv;
 | 
			
		||||
			
 | 
			
		||||
			parsedCsv = Papa.parse(result);
 | 
			
		||||
			if (parsedCsv.errors.length == 0) {
 | 
			
		||||
				var csvData;
 | 
			
		||||
				csvData = this.csvFillEmptyCells(parsedCsv.data);
 | 
			
		||||
 | 
			
		||||
				this.setCsvData(csvData);
 | 
			
		||||
				if (isUploadingFile == true) {
 | 
			
		||||
					this.startCsvWizard(csvData);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.setState('inputString', result);
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	backButtonStatus: function () {
 | 
			
		||||
		var result;
 | 
			
		||||
		
 | 
			
		||||
		result = 'DISABLED';
 | 
			
		||||
		if (this.currentStepIndex() > 0) {
 | 
			
		||||
			result = 'ENABLED';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//.............................................................................
 | 
			
		||||
 | 
			
		||||
	forwardButtonStatus: function () {
 | 
			
		||||
		var result;
 | 
			
		||||
		
 | 
			
		||||
		result = 'DISABLED';
 | 
			
		||||
		
 | 
			
		||||
		if (this.currentStep() == 'Input') {
 | 
			
		||||
			if (this.isInputStringValid()) {
 | 
			
		||||
				result = 'ENABLED';
 | 
			
		||||
			}
 | 
			
		||||
		} else if (this.currentStep() == 'Preview') {
 | 
			
		||||
			if (this.state('recordsToImport').length > 0) {
 | 
			
		||||
				result = 'ENABLED';
 | 
			
		||||
			}
 | 
			
		||||
		} else if (this.currentStep() == 'CSV.Columns') {
 | 
			
		||||
			if (MochiKit.Iter.some(this.state('csvData.selectedColumns'), MochiKit.Base.operator.identity)) {
 | 
			
		||||
				result = 'ENABLED';
 | 
			
		||||
			}
 | 
			
		||||
		} else if (this.currentStep() == 'CSV.Labels') {
 | 
			
		||||
			var	selectedColumns = this.state('csvData.selectedColumns');
 | 
			
		||||
			if (MochiKit.Iter.every(Clipperz.Base.zipWithRange(this.state('csvData.labels')), function (labelInfo) { return (Clipperz.Base.trim(labelInfo[1]).length > 0) || (selectedColumns[labelInfo[0]] == false)})) {
 | 
			
		||||
				result = 'ENABLED';
 | 
			
		||||
			}
 | 
			
		||||
		} else if (this.currentStep() == 'CSV.Titles') {
 | 
			
		||||
			if ((this.state('csvData.titleIndex') != null) && (this.state('csvData.selectedColumns')[this.state('csvData.titleIndex')] == true)) {
 | 
			
		||||
				result = 'ENABLED';
 | 
			
		||||
			}
 | 
			
		||||
		} else if (this.currentStep() == 'CSV.Notes') {
 | 
			
		||||
			result = 'ENABLED';
 | 
			
		||||
		} else if (this.currentStep() == 'CSV.Hidden') {
 | 
			
		||||
			result = 'ENABLED';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	goBackHandler: function () {
 | 
			
		||||
		return MochiKit.Base.bind(function (anEvent) {
 | 
			
		||||
			if (this.backButtonStatus() == 'ENABLED') {
 | 
			
		||||
				this.goBack();
 | 
			
		||||
			}
 | 
			
		||||
		}, this);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	goForwardHandler: function () {
 | 
			
		||||
		return MochiKit.Base.bind(function (anEvent) {
 | 
			
		||||
			if (this.forwardButtonStatus() == 'ENABLED') {
 | 
			
		||||
				this.goForward();
 | 
			
		||||
			}
 | 
			
		||||
		}, this);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	goBack: function () {
 | 
			
		||||
		this.setCurrentStep(this.steps()[this.currentStepIndex() - 1]);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	goForward: function () {
 | 
			
		||||
		if (this.currentStep() == 'CSV.Hidden') {
 | 
			
		||||
			var	jsonData;
 | 
			
		||||
 | 
			
		||||
			jsonData = this.createJsonDataFromCSV(this.state('csvData'));
 | 
			
		||||
			this.setState('jsonData', jsonData);
 | 
			
		||||
			this.setState('recordsToImport', jsonData);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.setCurrentStep(this.steps()[this.currentStepIndex() + 1]);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
 | 
			
		||||
	renderCsvTableBody: function (hideDeselectedColumns) {
 | 
			
		||||
		var	importContext = this;
 | 
			
		||||
 | 
			
		||||
		return React.DOM.tbody({}, MochiKit.Base.map(function (rowInfo) {
 | 
			
		||||
			var result;
 | 
			
		||||
			var	rowIndex = rowInfo[0];
 | 
			
		||||
			var	row = rowInfo[1]
 | 
			
		||||
 | 
			
		||||
			result = React.DOM.tr({'key': 'csv-row-' + rowIndex},
 | 
			
		||||
				MochiKit.Base.map(function (cellInfo) {
 | 
			
		||||
					var result;
 | 
			
		||||
					var	columnIndex = cellInfo[0];
 | 
			
		||||
					var	columnValue = cellInfo[1];
 | 
			
		||||
				
 | 
			
		||||
					if (importContext.state('csvData.selectedColumns')[columnIndex] || !hideDeselectedColumns) {
 | 
			
		||||
						result = React.DOM.td({
 | 
			
		||||
							'key':'csv-cell-' + rowIndex + '-' + columnIndex,
 | 
			
		||||
							'className':(importContext.state('csvData.hiddenFields')[columnIndex]) ? 'PASSWORD' : null
 | 
			
		||||
						}, columnValue);
 | 
			
		||||
					} else{
 | 
			
		||||
						result = null;
 | 
			
		||||
					}
 | 
			
		||||
				
 | 
			
		||||
					return  result;
 | 
			
		||||
				}, Clipperz.Base.zipWithRange(row))
 | 
			
		||||
			);
 | 
			
		||||
			
 | 
			
		||||
			return result;
 | 
			
		||||
		}, Clipperz.Base.zipWithRange((importContext.state('csvData.useFirstRowAsLabels')) ? importContext.state('csvData.data').slice(1) : importContext.state('csvData.data'))))
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=============================================================================
 | 
			
		||||
	__syntaxFix__: "syntax fix"
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -63,6 +63,7 @@ Clipperz.PM.UI.MainController = function() {
 | 
			
		||||
	this.registerForNotificationCenterEvents([
 | 
			
		||||
		'doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack',
 | 
			
		||||
		'changePassphrase', 'deleteAccount',
 | 
			
		||||
		'updateOTPListAndDetails', 'createNewOTP', 'deleteOTPs', 'changeOTPLabel',
 | 
			
		||||
//		'export',
 | 
			
		||||
		'importCards',
 | 
			
		||||
		'downloadExport',
 | 
			
		||||
@@ -122,6 +123,16 @@ MochiKit.Base.update(Clipperz.PM.UI.MainController.prototype, {
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
/*
 | 
			
		||||
	proxy: function () {
 | 
			
		||||
		return this._proxy;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	setProxy: function (aValue) {
 | 
			
		||||
		this._proxy = aValue;
 | 
			
		||||
	},
 | 
			
		||||
*/
 | 
			
		||||
	//=========================================================================
 | 
			
		||||
 | 
			
		||||
	isOnline: function() {
 | 
			
		||||
		return navigator.onLine;
 | 
			
		||||
@@ -246,10 +257,8 @@ console.log("THE BROWSER IS OFFLINE");
 | 
			
		||||
		var	canRegisterNewUsers;
 | 
			
		||||
 | 
			
		||||
		canRegisterNewUsers = Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers();
 | 
			
		||||
//console.log("CAN REGISTER NEW USERS", canRegisterNewUsers);
 | 
			
		||||
		this.selectInitialProxy();
 | 
			
		||||
		shouldShowRegistrationForm = parameters['shouldShowRegistrationForm'] && canRegisterNewUsers;
 | 
			
		||||
//		this.pages()['loginPage'].setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable':canRegisterNewUsers});
 | 
			
		||||
 | 
			
		||||
		this.showLoginForm();
 | 
			
		||||
		if (shouldShowRegistrationForm) {
 | 
			
		||||
@@ -262,7 +271,7 @@ console.log("THE BROWSER IS OFFLINE");
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
	
 | 
			
		||||
	checkPassphrase: function (passphraseIn) {
 | 
			
		||||
	checkPassphrase: function( passphraseIn ) {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
		
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("MainController.checkPassphrase", {trace: false});
 | 
			
		||||
@@ -931,13 +940,12 @@ console.log("THE BROWSER IS OFFLINE");
 | 
			
		||||
 | 
			
		||||
	//.........................................................................
 | 
			
		||||
	
 | 
			
		||||
	userInfo: function() {
 | 
			
		||||
		userInfo: function () {
 | 
			
		||||
		var result;
 | 
			
		||||
		
 | 
			
		||||
		result = {
 | 
			
		||||
			'checkPassphraseCallback':	MochiKit.Base.bind(this.checkPassphrase,this)
 | 
			
		||||
		};
 | 
			
		||||
		result = {};
 | 
			
		||||
		
 | 
			
		||||
		result['checkPassphraseCallback'] = MochiKit.Base.bind(this.checkPassphrase,this);
 | 
			
		||||
		if (this.user() != null) {
 | 
			
		||||
			result['username'] = this.user().username();
 | 
			
		||||
		}
 | 
			
		||||
@@ -1116,6 +1124,9 @@ console.log("THE BROWSER IS OFFLINE");
 | 
			
		||||
	toggleSettingsPanel_handler: function (anEvent) {
 | 
			
		||||
		this._isSettingsPanelOpen = !this._isSettingsPanelOpen;
 | 
			
		||||
		this.setCloseMaskAction(MochiKit.Base.method(this, 'toggleSettingsPanel_handler'));
 | 
			
		||||
		if (this._isSettingsPanelOpen == false) {
 | 
			
		||||
			MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'closeSettingsPanel');
 | 
			
		||||
		}
 | 
			
		||||
		this.refreshCurrentPage();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -1246,7 +1257,7 @@ console.log("THE BROWSER IS OFFLINE");
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//----------------------------------------------------------------------------
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
//	export_handler: function(exportType) {
 | 
			
		||||
//		return Clipperz.PM.UI.ExportController.exportJSON( this.recordsInfo(), exportType );
 | 
			
		||||
//	},
 | 
			
		||||
@@ -1274,14 +1285,14 @@ console.log("THE BROWSER IS OFFLINE");
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
		var getPassphraseDelegate;
 | 
			
		||||
		var user;
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
		getPassphraseDelegate = MochiKit.Base.partial(MochiKit.Async.succeed, newPassphrase);
 | 
			
		||||
		user = new Clipperz.PM.DataModel.User({'username':this.user().username(), 'getPassphraseFunction':getPassphraseDelegate});
 | 
			
		||||
		
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("MainController.changePassphrase_handler", {trace: false});
 | 
			
		||||
//		deferredResult.addMethod(currentPage, 'setProps', {'showGlobalMask':true});
 | 
			
		||||
		deferredResult.addMethod(this.overlay(), 'show', "changing …", true);
 | 
			
		||||
		deferredResult.addMethod(this.user(), 'changePassphrase', newPassphrase);
 | 
			
		||||
		deferredResult.addMethod(this.user(), 'changePassphrase', getPassphraseDelegate);
 | 
			
		||||
		deferredResult.addMethod(user, 'login');
 | 
			
		||||
		deferredResult.addMethod(this, 'setUser', user);
 | 
			
		||||
//		deferredResult.addMethod(currentPage, 'setProps', {'mode':'view', 'showGlobalMask':false});
 | 
			
		||||
@@ -1290,12 +1301,12 @@ console.log("THE BROWSER IS OFFLINE");
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
		
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	}, 
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	deleteAccount_handler: function() {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
		var doneMessageDelay = 2;
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("MainController.deleteAccount_handler", {trace: false});
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.method(this, 'ask', {
 | 
			
		||||
			'question': "Do you really want to permanently delete your account?",
 | 
			
		||||
@@ -1313,23 +1324,22 @@ console.log("THE BROWSER IS OFFLINE");
 | 
			
		||||
		
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	importCards_handler: function(data) {
 | 
			
		||||
		return Clipperz.Async.callbacks("MainController.importCards_handler", [
 | 
			
		||||
			MochiKit.Base.method(this.overlay(), 'show', "importing …", true),
 | 
			
		||||
			function() { return data; },
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, function(recordData) {
 | 
			
		||||
				var newRecord;
 | 
			
		||||
				// I have the feeling this should be done in a more elegant way
 | 
			
		||||
				return Clipperz.Async.callbacks("MainController.importCards_handler-newRecord", [
 | 
			
		||||
					MochiKit.Base.method(this.user(), 'createNewRecord'),
 | 
			
		||||
					function (aValue) {
 | 
			
		||||
						newRecord = aValue;
 | 
			
		||||
						return newRecord;
 | 
			
		||||
					},
 | 
			
		||||
					MochiKit.Base.methodcaller('setUpWithJSON', recordData),
 | 
			
		||||
				])
 | 
			
		||||
			})),
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'toggleSettingsPanel'),
 | 
			
		||||
//			MochiKit.Base.method(this.pages()[this.currentPage()], 'setProps', {'mode':'view', 'showGlobalMask':false}),
 | 
			
		||||
			function () { return data; },
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this.user(), 'createNewRecordFromJSON')),
 | 
			
		||||
 | 
			
		||||
			// MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.bind(function (recordData) {
 | 
			
		||||
			// 	return Clipperz.Async.callbacks("MainController.importCards_handler-newRecord", [
 | 
			
		||||
			// 		MochiKit.Base.method(this.user(), 'createNewRecord'),
 | 
			
		||||
			// 		MochiKit.Base.methodcaller('setUpWithJSON', recordData),
 | 
			
		||||
			// 	], {trace:false})
 | 
			
		||||
			// }, this)),
 | 
			
		||||
			
 | 
			
		||||
			Clipperz.Async.collectAll,
 | 
			
		||||
			MochiKit.Base.method(this.user(), 'saveChanges'),
 | 
			
		||||
			MochiKit.Base.partial(MochiKit.Base.method(this, 'resetRecordsInfo')),
 | 
			
		||||
@@ -1339,6 +1349,61 @@ console.log("THE BROWSER IS OFFLINE");
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	//----------------------------------------------------------------------------
 | 
			
		||||
	
 | 
			
		||||
	updateOTPListAndDetails: function() {
 | 
			
		||||
 | 
			
		||||
		return Clipperz.Async.callbacks("MainController.updateOTPListAndDetails", [
 | 
			
		||||
			Clipperz.Async.collectResults("User.updateOTPListAndDetails <inner results>", {
 | 
			
		||||
				'userInfo':		MochiKit.Base.method(this, 'userInfo'),
 | 
			
		||||
				'otpDetails':	Clipperz.Async.collectResults("User.updateOTPListAndDetails <otpDetails>", {
 | 
			
		||||
					'otpList':		MochiKit.Base.method(this.user(),'getOneTimePasswords'),
 | 
			
		||||
					'otpsDetails':	MochiKit.Base.method(this.user(),'getOneTimePasswordsDetails'),
 | 
			
		||||
				}),
 | 
			
		||||
			}, {trace:false}),
 | 
			
		||||
			function (someData) {
 | 
			
		||||
				return MochiKit.Base.update(someData['userInfo'], someData['otpDetails']);
 | 
			
		||||
			},
 | 
			
		||||
			MochiKit.Base.bind(function(someUserInfo) {
 | 
			
		||||
				this.setPageProperties('mainPage', 'userInfo', someUserInfo);
 | 
			
		||||
			}, this)
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	/* Used only one time (the first time the OTP ExtraFeature loads), other times
 | 
			
		||||
	the list update is triggered by other operations. Maybe the first OTP list retrieval
 | 
			
		||||
	could be done during init, so that this would not be necessary. */
 | 
			
		||||
	updateOTPListAndDetails_handler: function () {
 | 
			
		||||
		return this.updateOTPListAndDetails();
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	createNewOTP_handler: function () {
 | 
			
		||||
		return Clipperz.Async.callbacks("MainController.createNewOTP_handler", [
 | 
			
		||||
			MochiKit.Base.method(this.overlay(), 'show', "", true),
 | 
			
		||||
			MochiKit.Base.method(this.user(), 'createNewOTP'),
 | 
			
		||||
			MochiKit.Base.method(this, 'updateOTPListAndDetails'),
 | 
			
		||||
			MochiKit.Base.method(this.overlay(), 'done', "", 0.5),
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	deleteOTPs_handler: function (aList) {
 | 
			
		||||
		return Clipperz.Async.callbacks("MainController.deleteOTPs_handler", [
 | 
			
		||||
			MochiKit.Base.method(this.overlay(), 'show', "", true),
 | 
			
		||||
			MochiKit.Base.method(this.user(), 'deleteOTPs', aList),
 | 
			
		||||
			MochiKit.Base.method(this, 'updateOTPListAndDetails'),
 | 
			
		||||
			MochiKit.Base.method(this.overlay(), 'done', "", 0.5),
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	changeOTPLabel_handler: function (aReference, aLabel) {
 | 
			
		||||
		return Clipperz.Async.callbacks("MainController.changeOTPLabel_handler", [
 | 
			
		||||
			MochiKit.Base.method(this.overlay(), 'show', "", true),
 | 
			
		||||
			MochiKit.Base.method(this.user(), 'changeOTPLabel', aReference, aLabel),
 | 
			
		||||
			MochiKit.Base.method(this, 'updateOTPListAndDetails'),
 | 
			
		||||
			MochiKit.Base.method(this.overlay(), 'done', "", 0.5),
 | 
			
		||||
		], {trace:false});
 | 
			
		||||
	},
 | 
			
		||||
	
 | 
			
		||||
	//----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	saveChanges: function () {
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										859
									
								
								frontend/delta/js/xDate/xdate.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										859
									
								
								frontend/delta/js/xDate/xdate.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,859 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright 2008-2015 Clipperz Srl
 | 
			
		||||
 | 
			
		||||
This file is part of Clipperz, the online password manager.
 | 
			
		||||
For further information about its features and functionalities please
 | 
			
		||||
refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
* Clipperz is free software: you can redistribute it and/or modify it
 | 
			
		||||
  under the terms of the GNU Affero General Public License as published
 | 
			
		||||
  by the Free Software Foundation, either version 3 of the License, or 
 | 
			
		||||
  (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
* Clipperz is distributed in the hope that it will be useful, but 
 | 
			
		||||
  WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
  See the GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
* You should have received a copy of the GNU Affero General Public
 | 
			
		||||
  License along with Clipperz. If not, see http://www.gnu.org/licenses/.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @preserve XDate v@VERSION
 | 
			
		||||
 * Docs & Licensing: http://arshaw.com/xdate/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Internal Architecture
 | 
			
		||||
 * ---------------------
 | 
			
		||||
 * An XDate wraps a native Date. The native Date is stored in the '0' property of the object.
 | 
			
		||||
 * UTC-mode is determined by whether the internal native Date's toString method is set to
 | 
			
		||||
 * Date.prototype.toUTCString (see getUTCMode).
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
var XDate = (function(Date, Math, Array, undefined) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/** @const */ var FULLYEAR     = 0;
 | 
			
		||||
/** @const */ var MONTH        = 1;
 | 
			
		||||
/** @const */ var DATE         = 2;
 | 
			
		||||
/** @const */ var HOURS        = 3;
 | 
			
		||||
/** @const */ var MINUTES      = 4;
 | 
			
		||||
/** @const */ var SECONDS      = 5;
 | 
			
		||||
/** @const */ var MILLISECONDS = 6;
 | 
			
		||||
/** @const */ var DAY          = 7;
 | 
			
		||||
/** @const */ var YEAR         = 8;
 | 
			
		||||
/** @const */ var WEEK         = 9;
 | 
			
		||||
/** @const */ var DAY_MS = 86400000;
 | 
			
		||||
var ISO_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss(.fff)";
 | 
			
		||||
var ISO_FORMAT_STRING_TZ = ISO_FORMAT_STRING + "zzz";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var methodSubjects = [
 | 
			
		||||
	'FullYear',     // 0
 | 
			
		||||
	'Month',        // 1
 | 
			
		||||
	'Date',         // 2
 | 
			
		||||
	'Hours',        // 3
 | 
			
		||||
	'Minutes',      // 4
 | 
			
		||||
	'Seconds',      // 5
 | 
			
		||||
	'Milliseconds', // 6
 | 
			
		||||
	'Day',          // 7
 | 
			
		||||
	'Year'          // 8
 | 
			
		||||
];
 | 
			
		||||
var subjectPlurals = [
 | 
			
		||||
	'Years',        // 0
 | 
			
		||||
	'Months',       // 1
 | 
			
		||||
	'Days'          // 2
 | 
			
		||||
];
 | 
			
		||||
var unitsWithin = [
 | 
			
		||||
	12,   // months in year
 | 
			
		||||
	31,   // days in month (sort of)
 | 
			
		||||
	24,   // hours in day
 | 
			
		||||
	60,   // minutes in hour
 | 
			
		||||
	60,   // seconds in minute
 | 
			
		||||
	1000, // milliseconds in second
 | 
			
		||||
	1     //
 | 
			
		||||
];
 | 
			
		||||
var formatStringRE = new RegExp(
 | 
			
		||||
	"(([a-zA-Z])\\2*)|" + // 1, 2
 | 
			
		||||
	"(\\(" + "(('.*?'|\\(.*?\\)|.)*?)" + "\\))|" + // 3, 4, 5 (allows for 1 level of inner quotes or parens)
 | 
			
		||||
	"('(.*?)')" // 6, 7
 | 
			
		||||
);
 | 
			
		||||
var UTC = Date.UTC;
 | 
			
		||||
var toUTCString = Date.prototype.toUTCString;
 | 
			
		||||
var proto = XDate.prototype;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// This makes an XDate look pretty in Firebug and Web Inspector.
 | 
			
		||||
// It makes an XDate seem array-like, and displays [ <internal-date>.toString() ]
 | 
			
		||||
proto.length = 1;
 | 
			
		||||
proto.splice = Array.prototype.splice;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Constructor
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
// TODO: in future, I'd change signature for the constructor regarding the `true` utc-mode param. ~ashaw
 | 
			
		||||
//   I'd move the boolean to be the *first* argument. Still optional. Seems cleaner.
 | 
			
		||||
//   I'd remove it from the `xdate`, `nativeDate`, and `milliseconds` constructors.
 | 
			
		||||
//      (because you can simply call .setUTCMode(true) after)
 | 
			
		||||
//   And I'd only leave it for the y/m/d/h/m/s/m and `dateString` constructors
 | 
			
		||||
//      (because those are the only constructors that need it for DST-gap data-loss reasons)
 | 
			
		||||
//   Should do this for 1.0
 | 
			
		||||
 | 
			
		||||
function XDate() {
 | 
			
		||||
	return init(
 | 
			
		||||
		(this instanceof XDate) ? this : new XDate(),
 | 
			
		||||
		arguments
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function init(xdate, args) {
 | 
			
		||||
	var len = args.length;
 | 
			
		||||
	var utcMode;
 | 
			
		||||
	if (isBoolean(args[len-1])) {
 | 
			
		||||
		utcMode = args[--len];
 | 
			
		||||
		args = slice(args, 0, len);
 | 
			
		||||
	}
 | 
			
		||||
	if (!len) {
 | 
			
		||||
		xdate[0] = new Date();
 | 
			
		||||
	}
 | 
			
		||||
	else if (len == 1) {
 | 
			
		||||
		var arg = args[0];
 | 
			
		||||
		if (arg instanceof Date || isNumber(arg)) {
 | 
			
		||||
			xdate[0] = new Date(+arg);
 | 
			
		||||
		}
 | 
			
		||||
		else if (arg instanceof XDate) {
 | 
			
		||||
			xdate[0] = _clone(arg);
 | 
			
		||||
		}
 | 
			
		||||
		else if (isString(arg)) {
 | 
			
		||||
			xdate[0] = new Date(0);
 | 
			
		||||
			xdate = parse(arg, utcMode || false, xdate);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		xdate[0] = new Date(UTC.apply(Date, args));
 | 
			
		||||
		if (!utcMode) {
 | 
			
		||||
			xdate[0] = coerceToLocal(xdate[0]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (isBoolean(utcMode)) {
 | 
			
		||||
		setUTCMode(xdate, utcMode);
 | 
			
		||||
	}
 | 
			
		||||
	return xdate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* UTC Mode Methods
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.getUTCMode = methodize(getUTCMode);
 | 
			
		||||
function getUTCMode(xdate) {
 | 
			
		||||
	return xdate[0].toString === toUTCString;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.setUTCMode = methodize(setUTCMode);
 | 
			
		||||
function setUTCMode(xdate, utcMode, doCoercion) {
 | 
			
		||||
	if (utcMode) {
 | 
			
		||||
		if (!getUTCMode(xdate)) {
 | 
			
		||||
			if (doCoercion) {
 | 
			
		||||
				xdate[0] = coerceToUTC(xdate[0]);
 | 
			
		||||
			}
 | 
			
		||||
			xdate[0].toString = toUTCString;
 | 
			
		||||
		}
 | 
			
		||||
	}else{
 | 
			
		||||
		if (getUTCMode(xdate)) {
 | 
			
		||||
			if (doCoercion) {
 | 
			
		||||
				xdate[0] = coerceToLocal(xdate[0]);
 | 
			
		||||
			}else{
 | 
			
		||||
				xdate[0] = new Date(+xdate[0]);
 | 
			
		||||
			}
 | 
			
		||||
			// toString will have been cleared
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return xdate; // for chaining
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.getTimezoneOffset = function() {
 | 
			
		||||
	if (getUTCMode(this)) {
 | 
			
		||||
		return 0;
 | 
			
		||||
	}else{
 | 
			
		||||
		return this[0].getTimezoneOffset();
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* get / set / add / diff Methods (except for week-related)
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
each(methodSubjects, function(subject, fieldIndex) {
 | 
			
		||||
 | 
			
		||||
	proto['get' + subject] = function() {
 | 
			
		||||
		return _getField(this[0], getUTCMode(this), fieldIndex);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	if (fieldIndex != YEAR) { // because there is no getUTCYear
 | 
			
		||||
	
 | 
			
		||||
		proto['getUTC' + subject] = function() {
 | 
			
		||||
			return _getField(this[0], true, fieldIndex);
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (fieldIndex != DAY) { // because there is no setDay or setUTCDay
 | 
			
		||||
	                         // and the add* and diff* methods use DATE instead
 | 
			
		||||
		
 | 
			
		||||
		proto['set' + subject] = function(value) {
 | 
			
		||||
			_set(this, fieldIndex, value, arguments, getUTCMode(this));
 | 
			
		||||
			return this; // for chaining
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		if (fieldIndex != YEAR) { // because there is no setUTCYear
 | 
			
		||||
		                          // and the add* and diff* methods use FULLYEAR instead
 | 
			
		||||
			
 | 
			
		||||
			proto['setUTC' + subject] = function(value) {
 | 
			
		||||
				_set(this, fieldIndex, value, arguments, true);
 | 
			
		||||
				return this; // for chaining
 | 
			
		||||
			};
 | 
			
		||||
			
 | 
			
		||||
			proto['add' + (subjectPlurals[fieldIndex] || subject)] = function(delta, preventOverflow) {
 | 
			
		||||
				_add(this, fieldIndex, delta, preventOverflow);
 | 
			
		||||
				return this; // for chaining
 | 
			
		||||
			};
 | 
			
		||||
			
 | 
			
		||||
			proto['diff' + (subjectPlurals[fieldIndex] || subject)] = function(otherDate) {
 | 
			
		||||
				return _diff(this, otherDate, fieldIndex);
 | 
			
		||||
			};
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _set(xdate, fieldIndex, value, args, useUTC) {
 | 
			
		||||
	var getField = curry(_getField, xdate[0], useUTC);
 | 
			
		||||
	var setField = curry(_setField, xdate[0], useUTC);
 | 
			
		||||
	var expectedMonth;
 | 
			
		||||
	var preventOverflow = false;
 | 
			
		||||
	if (args.length == 2 && isBoolean(args[1])) {
 | 
			
		||||
		preventOverflow = args[1];
 | 
			
		||||
		args = [ value ];
 | 
			
		||||
	}
 | 
			
		||||
	if (fieldIndex == MONTH) {
 | 
			
		||||
		expectedMonth = (value % 12 + 12) % 12;
 | 
			
		||||
	}else{
 | 
			
		||||
		expectedMonth = getField(MONTH);
 | 
			
		||||
	}
 | 
			
		||||
	setField(fieldIndex, args);
 | 
			
		||||
	if (preventOverflow && getField(MONTH) != expectedMonth) {
 | 
			
		||||
		setField(MONTH, [ getField(MONTH) - 1 ]);
 | 
			
		||||
		setField(DATE, [ getDaysInMonth(getField(FULLYEAR), getField(MONTH)) ]);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _add(xdate, fieldIndex, delta, preventOverflow) {
 | 
			
		||||
	delta = Number(delta);
 | 
			
		||||
	var intDelta = Math.floor(delta);
 | 
			
		||||
	xdate['set' + methodSubjects[fieldIndex]](
 | 
			
		||||
		xdate['get' + methodSubjects[fieldIndex]]() + intDelta,
 | 
			
		||||
		preventOverflow || false
 | 
			
		||||
	);
 | 
			
		||||
	if (intDelta != delta && fieldIndex < MILLISECONDS) {
 | 
			
		||||
		_add(xdate, fieldIndex+1, (delta-intDelta)*unitsWithin[fieldIndex], preventOverflow);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _diff(xdate1, xdate2, fieldIndex) { // fieldIndex=FULLYEAR is for years, fieldIndex=DATE is for days
 | 
			
		||||
	xdate1 = xdate1.clone().setUTCMode(true, true);
 | 
			
		||||
	xdate2 = XDate(xdate2).setUTCMode(true, true);
 | 
			
		||||
	var v = 0;
 | 
			
		||||
	if (fieldIndex == FULLYEAR || fieldIndex == MONTH) {
 | 
			
		||||
		for (var i=MILLISECONDS, methodName; i>=fieldIndex; i--) {
 | 
			
		||||
			v /= unitsWithin[i];
 | 
			
		||||
			v += _getField(xdate2, false, i) - _getField(xdate1, false, i);
 | 
			
		||||
		}
 | 
			
		||||
		if (fieldIndex == MONTH) {
 | 
			
		||||
			v += (xdate2.getFullYear() - xdate1.getFullYear()) * 12;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else if (fieldIndex == DATE) {
 | 
			
		||||
		var clear1 = xdate1.toDate().setUTCHours(0, 0, 0, 0); // returns an ms value
 | 
			
		||||
		var clear2 = xdate2.toDate().setUTCHours(0, 0, 0, 0); // returns an ms value
 | 
			
		||||
		v = Math.round((clear2 - clear1) / DAY_MS) + ((xdate2 - clear2) - (xdate1 - clear1)) / DAY_MS;
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		v = (xdate2 - xdate1) / [
 | 
			
		||||
			3600000, // milliseconds in hour
 | 
			
		||||
			60000,   // milliseconds in minute
 | 
			
		||||
			1000,    // milliseconds in second
 | 
			
		||||
			1        //
 | 
			
		||||
			][fieldIndex - 3];
 | 
			
		||||
	}
 | 
			
		||||
	return v;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Week Methods
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.getWeek = function() {
 | 
			
		||||
	return _getWeek(curry(_getField, this, false));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.getUTCWeek = function() {
 | 
			
		||||
	return _getWeek(curry(_getField, this, true));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.setWeek = function(n, year) {
 | 
			
		||||
	_setWeek(this, n, year, false);
 | 
			
		||||
	return this; // for chaining
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.setUTCWeek = function(n, year) {
 | 
			
		||||
	_setWeek(this, n, year, true);
 | 
			
		||||
	return this; // for chaining
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.addWeeks = function(delta) {
 | 
			
		||||
	return this.addDays(Number(delta) * 7);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.diffWeeks = function(otherDate) {
 | 
			
		||||
	return _diff(this, otherDate, DATE) / 7;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _getWeek(getField) {
 | 
			
		||||
	return getWeek(getField(FULLYEAR), getField(MONTH), getField(DATE));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function getWeek(year, month, date) {
 | 
			
		||||
	var d = new Date(UTC(year, month, date));
 | 
			
		||||
	var week1 = getWeek1(
 | 
			
		||||
		getWeekYear(year, month, date)
 | 
			
		||||
	);
 | 
			
		||||
	return Math.floor(Math.round((d - week1) / DAY_MS) / 7) + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function getWeekYear(year, month, date) { // get the year that the date's week # belongs to
 | 
			
		||||
	var d = new Date(UTC(year, month, date));
 | 
			
		||||
	if (d < getWeek1(year)) {
 | 
			
		||||
		return year - 1;
 | 
			
		||||
	}
 | 
			
		||||
	else if (d >= getWeek1(year + 1)) {
 | 
			
		||||
		return year + 1;
 | 
			
		||||
	}
 | 
			
		||||
	return year;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function getWeek1(year) { // returns Date of first week of year, in UTC
 | 
			
		||||
	var d = new Date(UTC(year, 0, 4));
 | 
			
		||||
	d.setUTCDate(d.getUTCDate() - (d.getUTCDay() + 6) % 7); // make it Monday of the week
 | 
			
		||||
	return d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _setWeek(xdate, n, year, useUTC) {
 | 
			
		||||
	var getField = curry(_getField, xdate, useUTC);
 | 
			
		||||
	var setField = curry(_setField, xdate, useUTC);
 | 
			
		||||
 | 
			
		||||
	if (year === undefined) {
 | 
			
		||||
		year = getWeekYear(
 | 
			
		||||
			getField(FULLYEAR),
 | 
			
		||||
			getField(MONTH),
 | 
			
		||||
			getField(DATE)
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var week1 = getWeek1(year);
 | 
			
		||||
	if (!useUTC) {
 | 
			
		||||
		week1 = coerceToLocal(week1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	xdate.setTime(+week1);
 | 
			
		||||
	setField(DATE, [ getField(DATE) + (n-1) * 7 ]); // would have used xdate.addUTCWeeks :(
 | 
			
		||||
		// n-1 because n is 1-based
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Parsing
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
XDate.parsers = [
 | 
			
		||||
	parseISO
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
XDate.parse = function(str) {
 | 
			
		||||
	return +XDate(''+str);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function parse(str, utcMode, xdate) {
 | 
			
		||||
	var parsers = XDate.parsers;
 | 
			
		||||
	var i = 0;
 | 
			
		||||
	var res;
 | 
			
		||||
	for (; i<parsers.length; i++) {
 | 
			
		||||
		res = parsers[i](str, utcMode, xdate);
 | 
			
		||||
		if (res) {
 | 
			
		||||
			return res;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	xdate[0] = new Date(str);
 | 
			
		||||
	return xdate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function parseISO(str, utcMode, xdate) {
 | 
			
		||||
	var m = str.match(/^(\d{4})(-(\d{2})(-(\d{2})([T ](\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/);
 | 
			
		||||
	if (m) {
 | 
			
		||||
		var d = new Date(UTC(
 | 
			
		||||
			m[1],
 | 
			
		||||
			m[3] ? m[3] - 1 : 0,
 | 
			
		||||
			m[5] || 1,
 | 
			
		||||
			m[7] || 0,
 | 
			
		||||
			m[8] || 0,
 | 
			
		||||
			m[10] || 0,
 | 
			
		||||
			m[12] ? Number('0.' + m[12]) * 1000 : 0
 | 
			
		||||
		));
 | 
			
		||||
		if (m[13]) { // has gmt offset or Z
 | 
			
		||||
			if (m[14]) { // has gmt offset
 | 
			
		||||
				d.setUTCMinutes(
 | 
			
		||||
					d.getUTCMinutes() +
 | 
			
		||||
					(m[15] == '-' ? 1 : -1) * (Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0))
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}else{ // no specified timezone
 | 
			
		||||
			if (!utcMode) {
 | 
			
		||||
				d = coerceToLocal(d);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return xdate.setTime(+d);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Formatting
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.toString = function(formatString, settings, uniqueness) {
 | 
			
		||||
	if (formatString === undefined || !valid(this)) {
 | 
			
		||||
		return this[0].toString(); // already accounts for utc-mode (might be toUTCString)
 | 
			
		||||
	}else{
 | 
			
		||||
		return format(this, formatString, settings, uniqueness, getUTCMode(this));
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.toUTCString = proto.toGMTString = function(formatString, settings, uniqueness) {
 | 
			
		||||
	if (formatString === undefined || !valid(this)) {
 | 
			
		||||
		return this[0].toUTCString();
 | 
			
		||||
	}else{
 | 
			
		||||
		return format(this, formatString, settings, uniqueness, true);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.toISOString = function() {
 | 
			
		||||
	return this.toUTCString(ISO_FORMAT_STRING_TZ);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
XDate.defaultLocale = '';
 | 
			
		||||
XDate.locales = {
 | 
			
		||||
	'': {
 | 
			
		||||
		monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
 | 
			
		||||
		monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
 | 
			
		||||
		dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
 | 
			
		||||
		dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
 | 
			
		||||
		amDesignator: 'AM',
 | 
			
		||||
		pmDesignator: 'PM'
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
XDate.formatters = {
 | 
			
		||||
	i: ISO_FORMAT_STRING,
 | 
			
		||||
	u: ISO_FORMAT_STRING_TZ
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function format(xdate, formatString, settings, uniqueness, useUTC) {
 | 
			
		||||
 | 
			
		||||
	var locales = XDate.locales;
 | 
			
		||||
	var defaultLocaleSettings = locales[XDate.defaultLocale] || {};
 | 
			
		||||
	var getField = curry(_getField, xdate, useUTC);
 | 
			
		||||
	
 | 
			
		||||
	settings = (isString(settings) ? locales[settings] : settings) || {};
 | 
			
		||||
	
 | 
			
		||||
	function getSetting(name) {
 | 
			
		||||
		return settings[name] || defaultLocaleSettings[name];
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	function getFieldAndTrace(fieldIndex) {
 | 
			
		||||
		if (uniqueness) {
 | 
			
		||||
			var i = (fieldIndex == DAY ? DATE : fieldIndex) - 1;
 | 
			
		||||
			for (; i>=0; i--) {
 | 
			
		||||
				uniqueness.push(getField(i));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return getField(fieldIndex);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	return _format(xdate, formatString, getFieldAndTrace, getSetting, useUTC);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _format(xdate, formatString, getField, getSetting, useUTC) {
 | 
			
		||||
	var m;
 | 
			
		||||
	var subout;
 | 
			
		||||
	var out = '';
 | 
			
		||||
	while (m = formatString.match(formatStringRE)) {
 | 
			
		||||
		out += formatString.substr(0, m.index);
 | 
			
		||||
		if (m[1]) { // consecutive alphabetic characters
 | 
			
		||||
			out += processTokenString(xdate, m[1], getField, getSetting, useUTC);
 | 
			
		||||
		}
 | 
			
		||||
		else if (m[3]) { // parenthesis
 | 
			
		||||
			subout = _format(xdate, m[4], getField, getSetting, useUTC);
 | 
			
		||||
			if (parseInt(subout.replace(/\D/g, ''), 10)) { // if any of the numbers are non-zero. or no numbers at all
 | 
			
		||||
				out += subout;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else { // else if (m[6]) { // single quotes
 | 
			
		||||
			out += m[7] || "'"; // if inner is blank, meaning 2 consecutive quotes = literal single quote
 | 
			
		||||
		}
 | 
			
		||||
		formatString = formatString.substr(m.index + m[0].length);
 | 
			
		||||
	}
 | 
			
		||||
	return out + formatString;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function processTokenString(xdate, tokenString, getField, getSetting, useUTC) {
 | 
			
		||||
	var end = tokenString.length;
 | 
			
		||||
	var replacement;
 | 
			
		||||
	var out = '';
 | 
			
		||||
	while (end > 0) {
 | 
			
		||||
		replacement = getTokenReplacement(xdate, tokenString.substr(0, end), getField, getSetting, useUTC);
 | 
			
		||||
		if (replacement !== undefined) {
 | 
			
		||||
			out += replacement;
 | 
			
		||||
			tokenString = tokenString.substr(end);
 | 
			
		||||
			end = tokenString.length;
 | 
			
		||||
		}else{
 | 
			
		||||
			end--;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return out + tokenString;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function getTokenReplacement(xdate, token, getField, getSetting, useUTC) {
 | 
			
		||||
	var formatter = XDate.formatters[token];
 | 
			
		||||
	if (isString(formatter)) {
 | 
			
		||||
		return _format(xdate, formatter, getField, getSetting, useUTC);
 | 
			
		||||
	}
 | 
			
		||||
	else if (isFunction(formatter)) {
 | 
			
		||||
		return formatter(xdate, useUTC || false, getSetting);
 | 
			
		||||
	}
 | 
			
		||||
	switch (token) {
 | 
			
		||||
		case 'fff'  : return zeroPad(getField(MILLISECONDS), 3);
 | 
			
		||||
		case 's'    : return getField(SECONDS);
 | 
			
		||||
		case 'ss'   : return zeroPad(getField(SECONDS));
 | 
			
		||||
		case 'm'    : return getField(MINUTES);
 | 
			
		||||
		case 'mm'   : return zeroPad(getField(MINUTES));
 | 
			
		||||
		case 'h'    : return getField(HOURS) % 12 || 12;
 | 
			
		||||
		case 'hh'   : return zeroPad(getField(HOURS) % 12 || 12);
 | 
			
		||||
		case 'H'    : return getField(HOURS);
 | 
			
		||||
		case 'HH'   : return zeroPad(getField(HOURS));
 | 
			
		||||
		case 'd'    : return getField(DATE);
 | 
			
		||||
		case 'dd'   : return zeroPad(getField(DATE));
 | 
			
		||||
		case 'ddd'  : return getSetting('dayNamesShort')[getField(DAY)] || '';
 | 
			
		||||
		case 'dddd' : return getSetting('dayNames')[getField(DAY)] || '';
 | 
			
		||||
		case 'M'    : return getField(MONTH) + 1;
 | 
			
		||||
		case 'MM'   : return zeroPad(getField(MONTH) + 1);
 | 
			
		||||
		case 'MMM'  : return getSetting('monthNamesShort')[getField(MONTH)] || '';
 | 
			
		||||
		case 'MMMM' : return getSetting('monthNames')[getField(MONTH)] || '';
 | 
			
		||||
		case 'yy'   : return (getField(FULLYEAR)+'').substring(2);
 | 
			
		||||
		case 'yyyy' : return getField(FULLYEAR);
 | 
			
		||||
		case 't'    : return _getDesignator(getField, getSetting).substr(0, 1).toLowerCase();
 | 
			
		||||
		case 'tt'   : return _getDesignator(getField, getSetting).toLowerCase();
 | 
			
		||||
		case 'T'    : return _getDesignator(getField, getSetting).substr(0, 1);
 | 
			
		||||
		case 'TT'   : return _getDesignator(getField, getSetting);
 | 
			
		||||
		case 'z'    :
 | 
			
		||||
		case 'zz'   :
 | 
			
		||||
		case 'zzz'  : return useUTC ? 'Z' : _getTZString(xdate, token);
 | 
			
		||||
		case 'w'    : return _getWeek(getField);
 | 
			
		||||
		case 'ww'   : return zeroPad(_getWeek(getField));
 | 
			
		||||
		case 'S'    :
 | 
			
		||||
			var d = getField(DATE);
 | 
			
		||||
			if (d > 10 && d < 20) return 'th';
 | 
			
		||||
			return ['st', 'nd', 'rd'][d % 10 - 1] || 'th';
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _getTZString(xdate, token) {
 | 
			
		||||
	var tzo = xdate.getTimezoneOffset();
 | 
			
		||||
	var sign = tzo < 0 ? '+' : '-';
 | 
			
		||||
	var hours = Math.floor(Math.abs(tzo) / 60);
 | 
			
		||||
	var minutes = Math.abs(tzo) % 60;
 | 
			
		||||
	var out = hours;
 | 
			
		||||
	if (token == 'zz') {
 | 
			
		||||
		out = zeroPad(hours);
 | 
			
		||||
	}
 | 
			
		||||
	else if (token == 'zzz') {
 | 
			
		||||
		out = zeroPad(hours) + ':' + zeroPad(minutes);
 | 
			
		||||
	}
 | 
			
		||||
	return sign + out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _getDesignator(getField, getSetting) {
 | 
			
		||||
	return getField(HOURS) < 12 ? getSetting('amDesignator') : getSetting('pmDesignator');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Misc Methods
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
each(
 | 
			
		||||
	[ // other getters
 | 
			
		||||
		'getTime',
 | 
			
		||||
		'valueOf',
 | 
			
		||||
		'toDateString',
 | 
			
		||||
		'toTimeString',
 | 
			
		||||
		'toLocaleString',
 | 
			
		||||
		'toLocaleDateString',
 | 
			
		||||
		'toLocaleTimeString',
 | 
			
		||||
		'toJSON'
 | 
			
		||||
	],
 | 
			
		||||
	function(methodName) {
 | 
			
		||||
		proto[methodName] = function() {
 | 
			
		||||
			return this[0][methodName]();
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.setTime = function(t) {
 | 
			
		||||
	this[0].setTime(t);
 | 
			
		||||
	return this; // for chaining
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.valid = methodize(valid);
 | 
			
		||||
function valid(xdate) {
 | 
			
		||||
	return !isNaN(+xdate[0]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.clone = function() {
 | 
			
		||||
	return new XDate(this);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.clearTime = function() {
 | 
			
		||||
	return this.setHours(0, 0, 0, 0); // will return an XDate for chaining
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
proto.toDate = function() {
 | 
			
		||||
	return new Date(+this[0]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Misc Class Methods
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
XDate.now = function() {
 | 
			
		||||
	return +new Date();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
XDate.today = function() {
 | 
			
		||||
	return new XDate().clearTime();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
XDate.UTC = UTC;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
XDate.getDaysInMonth = getDaysInMonth;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Internal Utilities
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _clone(xdate) { // returns the internal Date object that should be used
 | 
			
		||||
	var d = new Date(+xdate[0]);
 | 
			
		||||
	if (getUTCMode(xdate)) {
 | 
			
		||||
		d.toString = toUTCString;
 | 
			
		||||
	}
 | 
			
		||||
	return d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _getField(d, useUTC, fieldIndex) {
 | 
			
		||||
	return d['get' + (useUTC ? 'UTC' : '') + methodSubjects[fieldIndex]]();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function _setField(d, useUTC, fieldIndex, args) {
 | 
			
		||||
	d['set' + (useUTC ? 'UTC' : '') + methodSubjects[fieldIndex]].apply(d, args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Date Math Utilities
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function coerceToUTC(date) {
 | 
			
		||||
	return new Date(UTC(
 | 
			
		||||
		date.getFullYear(),
 | 
			
		||||
		date.getMonth(),
 | 
			
		||||
		date.getDate(),
 | 
			
		||||
		date.getHours(),
 | 
			
		||||
		date.getMinutes(),
 | 
			
		||||
		date.getSeconds(),
 | 
			
		||||
		date.getMilliseconds()
 | 
			
		||||
	));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function coerceToLocal(date) {
 | 
			
		||||
	return new Date(
 | 
			
		||||
		date.getUTCFullYear(),
 | 
			
		||||
		date.getUTCMonth(),
 | 
			
		||||
		date.getUTCDate(),
 | 
			
		||||
		date.getUTCHours(),
 | 
			
		||||
		date.getUTCMinutes(),
 | 
			
		||||
		date.getUTCSeconds(),
 | 
			
		||||
		date.getUTCMilliseconds()
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function getDaysInMonth(year, month) {
 | 
			
		||||
	return 32 - new Date(UTC(year, month, 32)).getUTCDate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* General Utilities
 | 
			
		||||
---------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function methodize(f) {
 | 
			
		||||
	return function() {
 | 
			
		||||
		return f.apply(undefined, [this].concat(slice(arguments)));
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function curry(f) {
 | 
			
		||||
	var firstArgs = slice(arguments, 1);
 | 
			
		||||
	return function() {
 | 
			
		||||
		return f.apply(undefined, firstArgs.concat(slice(arguments)));
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function slice(a, start, end) {
 | 
			
		||||
	return Array.prototype.slice.call(
 | 
			
		||||
		a,
 | 
			
		||||
		start || 0, // start and end cannot be undefined for IE
 | 
			
		||||
		end===undefined ? a.length : end
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function each(a, f) {
 | 
			
		||||
	for (var i=0; i<a.length; i++) {
 | 
			
		||||
		f(a[i], i);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function isString(arg) {
 | 
			
		||||
	return typeof arg == 'string';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function isNumber(arg) {
 | 
			
		||||
	return typeof arg == 'number';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function isBoolean(arg) {
 | 
			
		||||
	return typeof arg == 'boolean';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function isFunction(arg) {
 | 
			
		||||
	return typeof arg == 'function';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function zeroPad(n, len) {
 | 
			
		||||
	len = len || 2;
 | 
			
		||||
	n += '';
 | 
			
		||||
	while (n.length < len) {
 | 
			
		||||
		n = '0' + n;
 | 
			
		||||
	}
 | 
			
		||||
	return n;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Export for Node.js
 | 
			
		||||
if (typeof module !== 'undefined' && module.exports) {
 | 
			
		||||
	module.exports = XDate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AMD
 | 
			
		||||
if (typeof define === 'function' && define.amd) {
 | 
			
		||||
	define([], function() {
 | 
			
		||||
		return XDate;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
return XDate;
 | 
			
		||||
 | 
			
		||||
})(Date, Math, Array);
 | 
			
		||||
@@ -26,7 +26,11 @@
 | 
			
		||||
		
 | 
			
		||||
		"papaparse.repository":			"https://github.com/mholt/PapaParse",
 | 
			
		||||
		"papaparse.version":			"4.1.1",
 | 
			
		||||
		"papaparse.commit":				"1c64d5c098570f243911e920bf7cbe170f69a9eb"
 | 
			
		||||
		"papaparse.commit":				"1c64d5c098570f243911e920bf7cbe170f69a9eb",
 | 
			
		||||
		
 | 
			
		||||
		"xDate.repository":				"https://github.com/arshaw/xdate",
 | 
			
		||||
		"xDate.version":				"0.8",
 | 
			
		||||
		"xDate.commit":					"f83cd8d63fab8cfe6f00ccba9041d2591daedb74"
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"html.template": "index_template.html",
 | 
			
		||||
@@ -61,13 +65,15 @@
 | 
			
		||||
		"-- Cubiq/add2home.js",
 | 
			
		||||
		"-- Modernizr/modernizr-2.8.2.js",
 | 
			
		||||
		"OnMediaQuery/onmediaquery-0.2.0.js",
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
		"FileSaver/Blob.js",
 | 
			
		||||
		"FileSaver/FileSaver.js",
 | 
			
		||||
 | 
			
		||||
		"PapaParse/papaparse.js",
 | 
			
		||||
		"-- PapaParse/papaparse.min.js",
 | 
			
		||||
 | 
			
		||||
		"xDate/xdate.js",
 | 
			
		||||
 | 
			
		||||
		"-- IT WOULD BE NICE TO BE ABLE TO GET RID OF THESE IMPORTS",
 | 
			
		||||
		"Clipperz/YUI/Utils.js",
 | 
			
		||||
		"Clipperz/YUI/DomHelper.js",
 | 
			
		||||
@@ -158,7 +164,7 @@
 | 
			
		||||
		"Clipperz/PM/UI/Components.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/Overlay.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/Button.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/Checkbox.js",
 | 
			
		||||
		"-- Clipperz/PM/UI/Components/Checkbox.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/CardToolbar.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/MessageBox.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/DialogBox.js",
 | 
			
		||||
@@ -179,17 +185,20 @@
 | 
			
		||||
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DevicePIN.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/Passphrase.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/OTP.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DeleteAccount.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataExport.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport.js",
 | 
			
		||||
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/Input.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvColumns.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvLabels.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvTitles.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvNotes.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvHidden.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/Import.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/Preview.js",
 | 
			
		||||
		
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Columns.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Labels.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Titles.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Notes.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Hidden.js",
 | 
			
		||||
 | 
			
		||||
		"Clipperz/PM/UI/Components/Cards/FavIcon.js",
 | 
			
		||||
		"Clipperz/PM/UI/Components/Cards/List.js",
 | 
			
		||||
@@ -207,7 +216,6 @@
 | 
			
		||||
		"-- Clipperz/PM/UI/MainDesktopController.js",
 | 
			
		||||
		"Clipperz/PM/UI/DirectLoginController.js",
 | 
			
		||||
		"Clipperz/PM/UI/ExportController.js",
 | 
			
		||||
		"Clipperz/PM/UI/ImportController.js",
 | 
			
		||||
		"Clipperz/PM/UI/ImportContext.js",
 | 
			
		||||
		"main.js"
 | 
			
		||||
	],
 | 
			
		||||
 
 | 
			
		||||
@@ -68,5 +68,3 @@ textarea {
 | 
			
		||||
//@import "sizes/wide";
 | 
			
		||||
//@import "sizes/extra-wide";
 | 
			
		||||
//@import "sizes/extra-short";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -34,7 +34,7 @@ html {
 | 
			
		||||
//	@include user-select(none);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input {
 | 
			
		||||
input[type=text] {
 | 
			
		||||
	-webkit-appearance: none;	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -216,7 +216,7 @@ input {
 | 
			
		||||
		& > div {
 | 
			
		||||
			@include flex(auto);
 | 
			
		||||
		
 | 
			
		||||
			@include overflow-auto;
 | 
			
		||||
			@include overflow-auto();
 | 
			
		||||
//			overflow: auto;
 | 
			
		||||
		
 | 
			
		||||
		}
 | 
			
		||||
@@ -233,6 +233,23 @@ input {
 | 
			
		||||
		height: 100%;
 | 
			
		||||
//		background-color: rgba( 0, 0, 0, 0.95);
 | 
			
		||||
		background-color: black;
 | 
			
		||||
 | 
			
		||||
		.extraFeature {
 | 
			
		||||
			@include flexbox();
 | 
			
		||||
			@include flex-direction(column);
 | 
			
		||||
			height: 100%;
 | 
			
		||||
		
 | 
			
		||||
			.header {
 | 
			
		||||
				@include flex(none);
 | 
			
		||||
				padding: 20px;
 | 
			
		||||
			}
 | 
			
		||||
			.content {
 | 
			
		||||
				@include flex(auto);
 | 
			
		||||
//				height: 100%;
 | 
			
		||||
				padding: 0px 20px 20px 20px;
 | 
			
		||||
				@include overflow-auto();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -444,7 +461,7 @@ div.cardContent {
 | 
			
		||||
			@include flex(auto);
 | 
			
		||||
//			overflow-y: scroll;
 | 
			
		||||
//			overflow:auto;
 | 
			
		||||
			@include overflow-auto;
 | 
			
		||||
			@include overflow-auto();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ refer to http://www.clipperz.com.
 | 
			
		||||
						
 | 
			
		||||
						&.offlineCopy {
 | 
			
		||||
							cursor: default;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					&.open {
 | 
			
		||||
@@ -170,19 +170,25 @@ refer to http://www.clipperz.com.
 | 
			
		||||
	.extraFeatureContent {
 | 
			
		||||
		border-right: 1px solid #222;
 | 
			
		||||
		color: white;
 | 
			
		||||
//		padding: 20px;
 | 
			
		||||
 | 
			
		||||
		header {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		.extraFeature {
 | 
			
		||||
			padding: 20px;
 | 
			
		||||
			.header {
 | 
			
		||||
//				padding-bottom: 20px;
 | 
			
		||||
 | 
			
		||||
				h1 {
 | 
			
		||||
					font-size: 20pt;
 | 
			
		||||
				}
 | 
			
		||||
			
 | 
			
		||||
			h1 {
 | 
			
		||||
				font-size: 20pt;
 | 
			
		||||
				padding-bottom: 20px;
 | 
			
		||||
				p {
 | 
			
		||||
					padding: 10px 0px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
			form {
 | 
			
		||||
			
 | 
			
		||||
				label {
 | 
			
		||||
@@ -209,15 +215,19 @@ refer to http://www.clipperz.com.
 | 
			
		||||
				p {
 | 
			
		||||
					@include flexbox;
 | 
			
		||||
					@include flex-direction(row);
 | 
			
		||||
					padding-top: 8px;
 | 
			
		||||
 | 
			
		||||
					input {
 | 
			
		||||
						width: 30px;
 | 
			
		||||
						@include flex(auto);
 | 
			
		||||
					}
 | 
			
		||||
				
 | 
			
		||||
					span {
 | 
			
		||||
 | 
			
		||||
					label {
 | 
			
		||||
						@include flex(auto);
 | 
			
		||||
						font-size: 12pt;
 | 
			
		||||
						display: block;
 | 
			
		||||
						cursor: pointer;
 | 
			
		||||
						line-height: 1.5em;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
@@ -256,7 +266,7 @@ refer to http://www.clipperz.com.
 | 
			
		||||
				color: white;
 | 
			
		||||
				
 | 
			
		||||
				li {
 | 
			
		||||
					padding-bottom: 40px;
 | 
			
		||||
					padding-bottom: 30px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
@@ -265,8 +275,9 @@ refer to http://www.clipperz.com.
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			.description {
 | 
			
		||||
				max-width: 500px;
 | 
			
		||||
//				max-width: 500px;
 | 
			
		||||
				padding: 10px 0px 20px 0px;
 | 
			
		||||
//				padding-bottom: 20px;
 | 
			
		||||
				
 | 
			
		||||
				p {
 | 
			
		||||
					font-size: 10pt;
 | 
			
		||||
@@ -277,11 +288,16 @@ refer to http://www.clipperz.com.
 | 
			
		||||
					em {
 | 
			
		||||
						text-decoration: underline;
 | 
			
		||||
					}
 | 
			
		||||
					
 | 
			
		||||
					&.warning {
 | 
			
		||||
						font-weight: bold;
 | 
			
		||||
						color: white;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			.button {
 | 
			
		||||
				display: inline;
 | 
			
		||||
				display: inline-block;
 | 
			
		||||
 | 
			
		||||
				color: white;
 | 
			
		||||
				background-color: $main-color;
 | 
			
		||||
@@ -289,14 +305,462 @@ refer to http://www.clipperz.com.
 | 
			
		||||
				font-size: 14pt;
 | 
			
		||||
 | 
			
		||||
				border: 1px solid white;
 | 
			
		||||
				padding: 6px 10px;
 | 
			
		||||
				padding: 10px 14px;
 | 
			
		||||
				
 | 
			
		||||
				&:after {
 | 
			
		||||
				};
 | 
			
		||||
				
 | 
			
		||||
				&.disabled {
 | 
			
		||||
					background-color: #c0c0c0;
 | 
			
		||||
					cursor: default;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		.OTP {
 | 
			
		||||
			height: 100%;
 | 
			
		||||
 | 
			
		||||
			.header {
 | 
			
		||||
				.description {
 | 
			
		||||
					padding-bottom: 0px;
 | 
			
		||||
					
 | 
			
		||||
					p {
 | 
			
		||||
						margin-bottom: 0px;
 | 
			
		||||
						padding-bottom: 0px;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			.actions {
 | 
			
		||||
				padding-left: 9px;
 | 
			
		||||
				padding-top: 6px;
 | 
			
		||||
 | 
			
		||||
				a {
 | 
			
		||||
					@include icon-font();
 | 
			
		||||
					cursor: pointer;
 | 
			
		||||
					font-size: 18pt;
 | 
			
		||||
					line-height: 1.1em;
 | 
			
		||||
					
 | 
			
		||||
					&:hover {
 | 
			
		||||
						color: green;
 | 
			
		||||
					};
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			.selectMenu {
 | 
			
		||||
				margin-top: 1em;
 | 
			
		||||
 | 
			
		||||
				li {
 | 
			
		||||
					display: inline-block;
 | 
			
		||||
					margin-right: 1em;
 | 
			
		||||
					padding:1em 0;
 | 
			
		||||
 | 
			
		||||
					//					a {
 | 
			
		||||
//						text-decoration: underline;
 | 
			
		||||
//						cursor: pointer;
 | 
			
		||||
//					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			.otpList {
 | 
			
		||||
 | 
			
		||||
				.otpDetail {
 | 
			
		||||
					$detailPadding: 5px;
 | 
			
		||||
					$detailMargin: 4px;
 | 
			
		||||
					$labelPadding: 5px;
 | 
			
		||||
					
 | 
			
		||||
					@include flexbox;
 | 
			
		||||
					@include flex-direction(row);
 | 
			
		||||
 | 
			
		||||
					border: 1px solid #222;
 | 
			
		||||
					@include border-radius(5px);
 | 
			
		||||
 | 
			
		||||
					padding: $detailPadding 0;
 | 
			
		||||
					margin: $detailMargin 0;
 | 
			
		||||
 | 
			
		||||
					.otpAction {
 | 
			
		||||
						width: 40px;
 | 
			
		||||
						text-align: center;
 | 
			
		||||
//						@include align-self(center);
 | 
			
		||||
 | 
			
		||||
						a {
 | 
			
		||||
							@include icon-font();
 | 
			
		||||
							cursor: pointer;
 | 
			
		||||
							font-size: 16pt;
 | 
			
		||||
							line-height: 1.1em;
 | 
			
		||||
							
 | 
			
		||||
							&:hover {
 | 
			
		||||
								color: red;
 | 
			
		||||
							};
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					.otpInfo {
 | 
			
		||||
						.otpPassword {
 | 
			
		||||
							font-size: 14pt;
 | 
			
		||||
							line-height: 1.3em;
 | 
			
		||||
							@include user-select(text);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						.otpLabel {
 | 
			
		||||
							
 | 
			
		||||
							span {
 | 
			
		||||
								font-size: 12pt;
 | 
			
		||||
								color: gray;
 | 
			
		||||
								padding: 4px 0px 4px 0px;
 | 
			
		||||
								line-height: 1.3em;
 | 
			
		||||
								cursor:pointer;
 | 
			
		||||
								display: block;
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							input {
 | 
			
		||||
								font-size: 12pt;
 | 
			
		||||
								color: gray;
 | 
			
		||||
								width: 100%;
 | 
			
		||||
								border: 0px;
 | 
			
		||||
								padding: 2px;
 | 
			
		||||
								padding-left: 0px;
 | 
			
		||||
								margin: 0px;
 | 
			
		||||
								margin-bottom: 1px;
 | 
			
		||||
								background-color: #333;
 | 
			
		||||
							}
 | 
			
		||||
//							.undefinedLabel {
 | 
			
		||||
//								color: gray;
 | 
			
		||||
//							}
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						.otpStatusInfo {
 | 
			
		||||
							font-size: 8pt;
 | 
			
		||||
							color: gray;
 | 
			
		||||
							
 | 
			
		||||
							span {
 | 
			
		||||
								padding-right: 10px;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					&.REQUESTED, &.USED {
 | 
			
		||||
						background-color: #222;
 | 
			
		||||
 | 
			
		||||
						.otpPassword {
 | 
			
		||||
//							color: gray;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			.button {
 | 
			
		||||
				margin:1em 1em 0 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			table {
 | 
			
		||||
				tr {
 | 
			
		||||
					td {
 | 
			
		||||
						a {
 | 
			
		||||
							font-size: small;
 | 
			
		||||
							padding: 0 1em;
 | 
			
		||||
							text-decoration: underline;
 | 
			
		||||
							cursor: pointer;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		.dataImport {
 | 
			
		||||
			
 | 
			
		||||
			.content {
 | 
			
		||||
				display: block;
 | 
			
		||||
				height: 100%;
 | 
			
		||||
				@include flexbox;
 | 
			
		||||
				@include flex-direction(column);
 | 
			
		||||
 | 
			
		||||
				ul.stepNavbar {
 | 
			
		||||
					@include flex(none);
 | 
			
		||||
					padding-top: 5px;
 | 
			
		||||
					padding-bottom: 5px;
 | 
			
		||||
					text-align: center;
 | 
			
		||||
					font-size: 24pt;
 | 
			
		||||
 | 
			
		||||
					&.Input {
 | 
			
		||||
						display: none;
 | 
			
		||||
					}
 | 
			
		||||
				
 | 
			
		||||
					li {
 | 
			
		||||
						display: inline-block;
 | 
			
		||||
						padding: 0px;
 | 
			
		||||
						padding-right: 10px;
 | 
			
		||||
//						margin-right:1em;
 | 
			
		||||
					
 | 
			
		||||
						&.disabled {
 | 
			
		||||
							color: gray;
 | 
			
		||||
						}
 | 
			
		||||
					
 | 
			
		||||
						&.active {
 | 
			
		||||
//							text-decoration: underline;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			
 | 
			
		||||
				.step {
 | 
			
		||||
					@include flex(auto);
 | 
			
		||||
					@include overflow-auto();
 | 
			
		||||
 | 
			
		||||
					&.Input {
 | 
			
		||||
 | 
			
		||||
						.description {
 | 
			
		||||
							
 | 
			
		||||
							p {
 | 
			
		||||
								width: 100%;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						form {
 | 
			
		||||
							.dropArea {
 | 
			
		||||
								width: calc(100% - 6px);
 | 
			
		||||
								text-align: center;
 | 
			
		||||
								height: inherit;
 | 
			
		||||
								line-height: 2em;
 | 
			
		||||
				
 | 
			
		||||
								border: 3px dashed white;
 | 
			
		||||
								background: black;
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							p {
 | 
			
		||||
								margin: 10px 50%;
 | 
			
		||||
						
 | 
			
		||||
								&.error {
 | 
			
		||||
									color: red;
 | 
			
		||||
									font-size: 10pt;
 | 
			
		||||
									font-style: italic;
 | 
			
		||||
									margin: 0px 0px 10px 0px;
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						
 | 
			
		||||
 | 
			
		||||
							textarea {
 | 
			
		||||
								width:100%;
 | 
			
		||||
								min-height:100px;
 | 
			
		||||
								display: block;
 | 
			
		||||
								margin: 1em 0;
 | 
			
		||||
								border: 0;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					&.Preview {
 | 
			
		||||
						li.card {
 | 
			
		||||
							@include flexbox;
 | 
			
		||||
							@include flex-direction(row);
 | 
			
		||||
							padding-top: 15px;
 | 
			
		||||
							padding-bottom: 0px;
 | 
			
		||||
							border-bottom: 1px solid #333;
 | 
			
		||||
						
 | 
			
		||||
							&.archived {
 | 
			
		||||
								background-color: #333;
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							input {
 | 
			
		||||
								width: 30px;
 | 
			
		||||
								margin-top: 6px;
 | 
			
		||||
								@include flex(none);
 | 
			
		||||
							}
 | 
			
		||||
						
 | 
			
		||||
							div.cardContent {
 | 
			
		||||
								@include flex(auto);
 | 
			
		||||
								@include flexbox;
 | 
			
		||||
								@include flex-direction(column);
 | 
			
		||||
								height: auto;
 | 
			
		||||
							
 | 
			
		||||
								h3 {
 | 
			
		||||
									font-size: 24pt;
 | 
			
		||||
									padding-bottom: 6px;
 | 
			
		||||
								}
 | 
			
		||||
								
 | 
			
		||||
								ul.tagList {
 | 
			
		||||
									li {
 | 
			
		||||
										display: inline-block;
 | 
			
		||||
										padding-right: 10px;
 | 
			
		||||
										padding-bottom: 5px;
 | 
			
		||||
 | 
			
		||||
										&:before {
 | 
			
		||||
											content: 'tag';
 | 
			
		||||
											@include icon-font();
 | 
			
		||||
											font-size: 10pt;
 | 
			
		||||
											padding-right: 5px;
 | 
			
		||||
											line-height: 28px;
 | 
			
		||||
											color: #ccc;
 | 
			
		||||
										}
 | 
			
		||||
									}
 | 
			
		||||
								}
 | 
			
		||||
								
 | 
			
		||||
								dl {
 | 
			
		||||
									dt {
 | 
			
		||||
										font-size: 12pt;
 | 
			
		||||
										color: gray;
 | 
			
		||||
										line-height: 16pt;
 | 
			
		||||
									}
 | 
			
		||||
									
 | 
			
		||||
									dd {
 | 
			
		||||
										font-size: 16pt;
 | 
			
		||||
										color: white;
 | 
			
		||||
										line-height: 24pt;
 | 
			
		||||
										padding-bottom: 6pt;
 | 
			
		||||
										
 | 
			
		||||
										&.PASSWORD {
 | 
			
		||||
											font-family: clipperz-password;
 | 
			
		||||
//											color: red;
 | 
			
		||||
										}
 | 
			
		||||
									}
 | 
			
		||||
								}
 | 
			
		||||
 | 
			
		||||
								p {
 | 
			
		||||
									font-size: 10pt;
 | 
			
		||||
									padding-top: 10px;
 | 
			
		||||
									padding-bottom: 10px;
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					
 | 
			
		||||
					&.Import {
 | 
			
		||||
						h5 {
 | 
			
		||||
							padding-bottom: 15px;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					table.csvTable {
 | 
			
		||||
						width: 100%;
 | 
			
		||||
						color: white;
 | 
			
		||||
						border-collapse: collapse;
 | 
			
		||||
						
 | 
			
		||||
						thead {
 | 
			
		||||
							background-color: gray;
 | 
			
		||||
							
 | 
			
		||||
							th {
 | 
			
		||||
								align: left;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						tbody {
 | 
			
		||||
							td {
 | 
			
		||||
								align: right;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						td, th {
 | 
			
		||||
							border: 1px solid #999;
 | 
			
		||||
							padding: 0.5rem;
 | 
			
		||||
							
 | 
			
		||||
							&.PASSWORD {
 | 
			
		||||
								font-family: clipperz-password;
 | 
			
		||||
								font-size: 14.9pt;
 | 
			
		||||
								line-height: 10pt;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						
 | 
			
		||||
						th {
 | 
			
		||||
							background-color: #666;
 | 
			
		||||
 | 
			
		||||
							&.title {
 | 
			
		||||
								background-color: #888;
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							&.notes {
 | 
			
		||||
								background-color: #aaa;
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							input {
 | 
			
		||||
								color: white;
 | 
			
		||||
								border: 0px;
 | 
			
		||||
								padding: 0px;
 | 
			
		||||
								font-size: 12pt;
 | 
			
		||||
								font-weight: bold;
 | 
			
		||||
								background-color: inherit;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				
 | 
			
		||||
				}
 | 
			
		||||
			
 | 
			
		||||
				.buttons {
 | 
			
		||||
					@include flex(none);
 | 
			
		||||
					text-align: center;
 | 
			
		||||
					padding-top: 10px;
 | 
			
		||||
				
 | 
			
		||||
					&.Input {
 | 
			
		||||
						.button.back {
 | 
			
		||||
							visibility: hidden;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					&.Import {
 | 
			
		||||
						.button.next {
 | 
			
		||||
							visibility: hidden;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					.button {
 | 
			
		||||
//						border: 0px;
 | 
			
		||||
						margin: 0px 5px;
 | 
			
		||||
						span {
 | 
			
		||||
							display: none;
 | 
			
		||||
						}
 | 
			
		||||
					
 | 
			
		||||
						&.back {
 | 
			
		||||
							background-color: #c0c0c0;
 | 
			
		||||
							&:before {
 | 
			
		||||
								content: '<<';
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					
 | 
			
		||||
						&.next {
 | 
			
		||||
 | 
			
		||||
							&.DISABLED {
 | 
			
		||||
								background-color: #d3d3d3;
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							&:after {
 | 
			
		||||
								content: '>>';
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
/*
 | 
			
		||||
				.csvTable {
 | 
			
		||||
					background: white;
 | 
			
		||||
					margin: 1em 0;
 | 
			
		||||
				}
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
				.button {
 | 
			
		||||
					margin-right:1em;
 | 
			
		||||
				}
 | 
			
		||||
			
 | 
			
		||||
				.jsonPreview {
 | 
			
		||||
					width: 100%;
 | 
			
		||||
					height:80%;
 | 
			
		||||
					overflow: auto;
 | 
			
		||||
					margin-top:1em;
 | 
			
		||||
				
 | 
			
		||||
					h3 {
 | 
			
		||||
						font-weight:bold;
 | 
			
		||||
					}
 | 
			
		||||
				
 | 
			
		||||
					ul {
 | 
			
		||||
						margin-bottom:1em;
 | 
			
		||||
						padding-left:1em;
 | 
			
		||||
					
 | 
			
		||||
						li {
 | 
			
		||||
							.label {
 | 
			
		||||
								font-weight:bold;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
*/
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
		.changePassphraseForm {
 | 
			
		||||
			label {
 | 
			
		||||
@@ -327,11 +791,15 @@ refer to http://www.clipperz.com.
 | 
			
		||||
		}
 | 
			
		||||
*/
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		form {
 | 
			
		||||
			input.valid + .invalidMsg, input.empty + .invalidMsg, input:focus + .invalidMsg, input.invalid:focus + .invalidMsg {
 | 
			
		||||
				visibility: hidden;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}			
 | 
			
		||||
			
 | 
			
		||||
.mainPage.narrow {
 | 
			
		||||
	#extraFeaturesPanel {
 | 
			
		||||
		.extraFeatureContent {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ refer to http://www.clipperz.com.
 | 
			
		||||
    <script type='text/javascript' src='../../../../../js/Clipperz/Crypto/Base.js'></script>
 | 
			
		||||
    <script type='text/javascript' src='../../../../../js/Clipperz/Crypto/BigInt.js'></script>
 | 
			
		||||
    <script type='text/javascript' src='../../../../../js/Clipperz/Crypto/AES.js'></script>
 | 
			
		||||
    <script type='text/javascript' src='../../../../../js/Clipperz/Crypto/AES_2.js'></script>
 | 
			
		||||
    <script type='text/javascript' src='../../../../../js/Clipperz/Crypto/SHA.js'></script>
 | 
			
		||||
    <script type='text/javascript' src='../../../../../js/Clipperz/Crypto/PRNG.js'></script>
 | 
			
		||||
    <script type='text/javascript' src='../../../../../js/Clipperz/Crypto/SRP.js'></script>
 | 
			
		||||
 
 | 
			
		||||
@@ -95,8 +95,8 @@ var tests = {
 | 
			
		||||
 | 
			
		||||
		newPassphrase = 'tset';
 | 
			
		||||
		proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
 | 
			
		||||
		user = new Clipperz.PM.DataModel.User({username:'test', getPassphraseFunction:function () { return 'test';}});
 | 
			
		||||
		user2 = new Clipperz.PM.DataModel.User({username:'test', getPassphraseFunction:function () { return otp;}});
 | 
			
		||||
		user = new Clipperz.PM.DataModel.User({username:'test', getPassphraseFunction: MochiKit.Base.partial(MochiKit.Async.succeed, 'test')});
 | 
			
		||||
		user2 = new Clipperz.PM.DataModel.User({username:'test', getPassphraseFunction: MochiKit.Base.partial(MochiKit.Async.succeed, otp)});
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("changePassphraseAndLoginUsingOtp_test", someTestArgs);
 | 
			
		||||
		deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['test_test_with_otps']);
 | 
			
		||||
@@ -107,7 +107,7 @@ var tests = {
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(1, "This account has only a single card");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'changePassphrase', newPassphrase);
 | 
			
		||||
		deferredResult.addMethod(user, 'changePassphrase', MochiKit.Base.partial(MochiKit.Async.succeed, newPassphrase));
 | 
			
		||||
		deferredResult.addMethod(user, 'logout');
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user2, 'login');
 | 
			
		||||
@@ -181,7 +181,145 @@ var tests = {
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
    //-------------------------------------------------------------------------
 | 
			
		||||
	'loginWithANewOTP_test': function(someTestArgs) {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
		var proxy;
 | 
			
		||||
		var user;
 | 
			
		||||
		var user2;
 | 
			
		||||
		var user3;
 | 
			
		||||
		var username;
 | 
			
		||||
		var passphrase;
 | 
			
		||||
		
 | 
			
		||||
		username = "1";
 | 
			
		||||
		passphrase = "1";
 | 
			
		||||
 | 
			
		||||
		proxy =	new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
 | 
			
		||||
		user =	new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:MochiKit.Base.partial(MochiKit.Async.succeed, passphrase)});
 | 
			
		||||
		user2 =	new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:function () { return "WILL_BE_CHANGED_WITH_OTP";}});
 | 
			
		||||
		user3 =	new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:function () { return "WILL_BE_CHANGED_WITH_OTP";}});
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("loginUserWithANewOTP_test", someTestArgs);
 | 
			
		||||
		deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['1/1_data']);
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'login');
 | 
			
		||||
		deferredResult.addMethod(user, 'getRecords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(1, "This account has one single card");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(0, "There should be no OTPs initially");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'createNewOTP');
 | 
			
		||||
		deferredResult.addMethod(user, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.setValue('otpList');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(1, "There should be one single OTP now");
 | 
			
		||||
 | 
			
		||||
		deferredResult.getValue('otpList');
 | 
			
		||||
		deferredResult.addCallback(function(aList) {return MochiKit.Base.partial(MochiKit.Async.succeed, aList[0].password()); });
 | 
			
		||||
		deferredResult.addMethod(user2, 'setPassphraseFunction'),
 | 
			
		||||
		deferredResult.addMethod(user, 'logout');
 | 
			
		||||
		deferredResult.addMethod(user2, 'login');
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user2, 'getRecords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(1, "This account has one single card");
 | 
			
		||||
 | 
			
		||||
		deferredResult.getValue('otpList');
 | 
			
		||||
		deferredResult.addCallback(function(aList) {return MochiKit.Base.partial(MochiKit.Async.succeed, aList[0].password()); });
 | 
			
		||||
		deferredResult.addMethod(user3, 'setPassphraseFunction'),
 | 
			
		||||
		deferredResult.addMethod(user2, 'logout');
 | 
			
		||||
		deferredResult.addMethod(user3, 'login');
 | 
			
		||||
		deferredResult.shouldFail("Second login with the same OTP should fail");
 | 
			
		||||
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	'deleteOTP_test': function(someTestArgs) {
 | 
			
		||||
		var deferredResult;
 | 
			
		||||
		var proxy;
 | 
			
		||||
		var user;
 | 
			
		||||
		var user2;
 | 
			
		||||
		var username;
 | 
			
		||||
		var passphrase;
 | 
			
		||||
		
 | 
			
		||||
		username = "1";
 | 
			
		||||
		passphrase = "1";
 | 
			
		||||
 | 
			
		||||
		proxy =	new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
 | 
			
		||||
		user =	new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:MochiKit.Base.partial(MochiKit.Async.succeed, passphrase)});
 | 
			
		||||
		user2 =	new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:function () { return "WILL_BE_CHANGED_WITH_OTP";}});
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("deleteOTP_test", someTestArgs);
 | 
			
		||||
		deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['1/1_data']);
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'login');
 | 
			
		||||
		deferredResult.addMethod(user, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(0, "There should be no OTPs initially");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'createNewOTP');
 | 
			
		||||
		deferredResult.addMethod(user, 'createNewOTP');
 | 
			
		||||
		deferredResult.addMethod(user, 'createNewOTP');
 | 
			
		||||
		deferredResult.addMethod(user, 'createNewOTP');
 | 
			
		||||
		deferredResult.addMethod(user, 'createNewOTP');
 | 
			
		||||
		deferredResult.addMethod(user, 'createNewOTP');
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(6, "There should be 6 OTPs now");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(function(aList) {return [aList[0].reference(), aList[1].reference()];});
 | 
			
		||||
		deferredResult.addMethod(user, 'deleteOTPs');
 | 
			
		||||
		deferredResult.addMethod(user, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(4, "There should be 4 OTPs now");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(function(aList) {return [aList[0].reference(), aList[1].reference(), aList[2].reference()];});
 | 
			
		||||
		deferredResult.addMethod(user, 'deleteOTPs');
 | 
			
		||||
		deferredResult.addMethod(user, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(1, "There should be 1 OTP now");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(function(aList) {return MochiKit.Base.partial(MochiKit.Async.succeed, aList[0].password()); });
 | 
			
		||||
		deferredResult.addMethod(user2, 'setPassphraseFunction');
 | 
			
		||||
		deferredResult.addMethod(user, 'logout');
 | 
			
		||||
		deferredResult.addMethod(user2, 'login');
 | 
			
		||||
		deferredResult.addMethod(user2, 'getRecords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(1, "Login with the remaining OTP should work");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user2, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(1, "There should be 1 remaining OTP now");
 | 
			
		||||
 | 
			
		||||
		deferredResult.collectResults({
 | 
			
		||||
			'oneTimePasswords': MochiKit.Base.method(user2, 'getOneTimePasswords'),
 | 
			
		||||
			'oneTimePasswordsDetails': MochiKit.Base.method(user2, 'getOneTimePasswordsDetails')
 | 
			
		||||
		});
 | 
			
		||||
		deferredResult.addCallback(function(someData) {
 | 
			
		||||
			return someData['oneTimePasswordsDetails'][someData['oneTimePasswords'][0].reference()]['status'];
 | 
			
		||||
		});
 | 
			
		||||
		deferredResult.addTest('USED', "The remaining OTP should have 'USED' status");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user2, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(function(aList) {return [aList[0].reference()];});
 | 
			
		||||
		deferredResult.addMethod(user2, 'deleteOTPs');
 | 
			
		||||
		deferredResult.addMethod(user2, 'getOneTimePasswords');
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(0, "There should be no OTPs left after the deletion of the last one");
 | 
			
		||||
 | 
			
		||||
		deferredResult.callback();
 | 
			
		||||
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
    //-------------------------------------------------------------------------
 | 
			
		||||
    'syntaxFix': MochiKit.Base.noop
 | 
			
		||||
};
 | 
			
		||||
@@ -190,4 +328,4 @@ var tests = {
 | 
			
		||||
 | 
			
		||||
//#############################################################################
 | 
			
		||||
 | 
			
		||||
SimpleTest.runDeferredTests("Clipperz.PM.DataModel.OneTimePassword", tests, {trace:false});
 | 
			
		||||
SimpleTest.runDeferredTests("Clipperz.PM.DataModel.OneTimePassword", tests, {trace:true});
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ refer to http://www.clipperz.com.
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
Clipperz.PM.DataModel.Record.tagChar = '#';	//	Simplify tests using a 'regular' char instead of an UTF-8 reserved one
 | 
			
		||||
// Clipperz.PM.DataModel.Record.tagChar = '#';	//	Simplify tests using a 'regular' char instead of an UTF-8 reserved one
 | 
			
		||||
Clipperz.Crypto.PRNG.defaultRandomGenerator().fastEntropyAccumulationForTestingPurpose();
 | 
			
		||||
 | 
			
		||||
var tests = {
 | 
			
		||||
@@ -80,6 +80,33 @@ var tests = {
 | 
			
		||||
		return deferredResult;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
    //-------------------------------------------------------------------------
 | 
			
		||||
    'createNewRecord_test': function(someTestArgs) {
 | 
			
		||||
    	var deferredResult;
 | 
			
		||||
    	var proxy;
 | 
			
		||||
    	var user;
 | 
			
		||||
 | 
			
		||||
    	proxy =	new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
 | 
			
		||||
    	user =	new Clipperz.PM.DataModel.User({username:"1", getPassphraseFunction:MochiKit.Base.partial(MochiKit.Async.succeed, "1")});
 | 
			
		||||
 | 
			
		||||
    	deferredResult = new Clipperz.Async.Deferred("deleteOTP_test", someTestArgs);
 | 
			
		||||
    	deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['1/1_data']);
 | 
			
		||||
 | 
			
		||||
    	deferredResult.addMethod(user, 'login');
 | 
			
		||||
    	deferredResult.addMethod(user, 'createNewRecord');
 | 
			
		||||
    	deferredResult.setValue('newRecord');
 | 
			
		||||
    	deferredResult.addMethodcaller('setLabel', "A record");
 | 
			
		||||
    	deferredResult.getValue('newRecord');
 | 
			
		||||
    	deferredResult.addMethodcaller('addField', {'label':"Field 1", 'value':"Value 1", 'isHidden':false});
 | 
			
		||||
    	deferredResult.getValue('newRecord');
 | 
			
		||||
    	deferredResult.addMethodcaller('label');
 | 
			
		||||
    	deferredResult.addTest("A record", "Record returns the right value for label");
 | 
			
		||||
 | 
			
		||||
    	deferredResult.callback();
 | 
			
		||||
 | 
			
		||||
    	return deferredResult;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    //-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'createRecordWithoutAllRequiredParameters_test': function (someTestArgs) {
 | 
			
		||||
@@ -1434,6 +1461,8 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
 | 
			
		||||
			return filterRecordsWithRegExp(aUser, Clipperz.PM.DataModel.Record.regExpForSearch(aSearchTerm));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var tagChar = Clipperz.PM.DataModel.Record.tagChar;
 | 
			
		||||
 | 
			
		||||
		proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});
 | 
			
		||||
		user  = new Clipperz.PM.DataModel.User({username:'tag', getPassphraseFunction:function () { return 'tag';}});
 | 
			
		||||
 | 
			
		||||
@@ -1452,11 +1481,11 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
 | 
			
		||||
		deferredResult.addMethod(user, 'getRecordsInfo', Clipperz.PM.DataModel.Record.defaultCardInfo);
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.map, Clipperz.Base.itemgetter('_searchableContent'));
 | 
			
		||||
		deferredResult.addCallback(SimpleTest.eq, [
 | 
			
		||||
			'Card 1 #Tag1 #Tag2 ',
 | 
			
		||||
			'Card 2 #Tag1 #Tag3 ',
 | 
			
		||||
			'Card 3 #Tag1 ',
 | 
			
		||||
			'Card 4 #Tag3 ',
 | 
			
		||||
			'Card 5 #Tag4 ',
 | 
			
		||||
			'Card 1 '+tagChar+'Tag1 '+tagChar+'Tag2 ',
 | 
			
		||||
			'Card 2 '+tagChar+'Tag1 '+tagChar+'Tag3 ',
 | 
			
		||||
			'Card 3 '+tagChar+'Tag1 ',
 | 
			
		||||
			'Card 4 '+tagChar+'Tag3 ',
 | 
			
		||||
			'Card 5 '+tagChar+'Tag4 ',
 | 
			
		||||
			'Card 6 '
 | 
			
		||||
		]);
 | 
			
		||||
 | 
			
		||||
@@ -1920,7 +1949,7 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
 | 
			
		||||
		var	deferredResult;
 | 
			
		||||
		var	proxy;
 | 
			
		||||
		var	user;
 | 
			
		||||
		var recordID =		'eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01'
 | 
			
		||||
		var recordID =		'327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83'
 | 
			
		||||
		var originalFieldReference = 'bfd7624054e1eb6f1849082714f4016e300bce66645c7a7370276d82767cf125';
 | 
			
		||||
 | 
			
		||||
		proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});
 | 
			
		||||
@@ -2002,7 +2031,7 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
 | 
			
		||||
		var	deferredResult;
 | 
			
		||||
		var	proxy;
 | 
			
		||||
		var	user;
 | 
			
		||||
		var recordID =		'eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01'
 | 
			
		||||
		var recordID =		'327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83'
 | 
			
		||||
		var originalFieldReference = 'bfd7624054e1eb6f1849082714f4016e300bce66645c7a7370276d82767cf125';
 | 
			
		||||
 | 
			
		||||
		proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});
 | 
			
		||||
@@ -2066,7 +2095,7 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
 | 
			
		||||
		var	deferredResult;
 | 
			
		||||
		var	proxy;
 | 
			
		||||
		var	user;
 | 
			
		||||
		var recordID =		'eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01'
 | 
			
		||||
		var recordID =		'327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83'
 | 
			
		||||
//		var originalFieldReference = 'bfd7624054e1eb6f1849082714f4016e300bce66645c7a7370276d82767cf125';
 | 
			
		||||
 | 
			
		||||
		proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});
 | 
			
		||||
@@ -2132,7 +2161,7 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
 | 
			
		||||
		var	deferredResult;
 | 
			
		||||
		var	proxy;
 | 
			
		||||
		var	user;
 | 
			
		||||
		var recordID =		'eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01'
 | 
			
		||||
		var recordID =		'327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83'
 | 
			
		||||
//		var originalFieldReference = 'bfd7624054e1eb6f1849082714f4016e300bce66645c7a7370276d82767cf125';
 | 
			
		||||
 | 
			
		||||
		proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});
 | 
			
		||||
 
 | 
			
		||||
@@ -1832,14 +1832,14 @@ testData = {
 | 
			
		||||
		},
 | 
			
		||||
		'onetimePasswords': {
 | 
			
		||||
			//	OTP: 'yaxx k7ww - f8y6 tqz5 - 58b6 th44 - 9cwv q0fg',
 | 
			
		||||
			'7074103e8ce35f813dbfb9c90665bd66ba3f5b1c9e4fa7a3d8aee679b7a38102': {	// reference
 | 
			
		||||
			'c3664af5744319c6d3b874895f803df19cb0492acf27cb51912110d023ba9b38': {	// key
 | 
			
		||||
				'reference': "c3664af5744319c6d3b874895f803df19cb0492acf27cb51912110d023ba9b38",
 | 
			
		||||
				'user': "9a984e219b07f9b645ef35f4de938b4741abe2e0b4adc88b40e9367170c91cc8",
 | 
			
		||||
				'status': 'ACTIVE',	//	1: 'ACTIVE', 2: 'REQUESTED', 3: 'USED', 4: 'DISABLED'
 | 
			
		||||
				'creation_date': "2010-02-09 17:57:14",
 | 
			
		||||
				'request_date': "4001-01-01 09:00:00",
 | 
			
		||||
				'usage_date': "4001-01-01 09:00:00",
 | 
			
		||||
//				'key': "7074103e8ce35f813dbfb9c90665bd66ba3f5b1c9e4fa7a3d8aee679b7a38102",
 | 
			
		||||
				'key': "7074103e8ce35f813dbfb9c90665bd66ba3f5b1c9e4fa7a3d8aee679b7a38102",
 | 
			
		||||
				"key_checksum": "53739910c97d74c80c6028eb3293ffbc652def811d9aa11725fefa3139dfcf29",
 | 
			
		||||
				"data": "aN3rPl5rusBWXveUpjKqZNFLRPWJgH2Zs8HYQIaS65QObQFWFTZ8lRpBXFoPlvSOHcHQpEavZUuq31Y/2Y9sI/scvmZjQ8UEaT2GY9NiWJVswGq1W3AX8zs32jOgf1L5iBVxK54mfig2vXFoL8lG0JGGY1zHZXlkCvFPWuvwuCcH+uRE0oP3it0FvNFBV4+TiiGnGYgD9LPAVevzr/Doq5UXjn9VplVU+upeDTWY+7rlOdIOnZG/A9P9/dQtsyMb5c5zABD2FNpQQ40JDfG/nHt5WvfuWmPwUisW1181oBAd5BwF9LgVHdrhcSh8OuUL7rdbKTPTlWT826I6JNrFMzYGMY+NV6gllDvc6eCNrgI98ABhL1AoZNpAXXuCy4uQhEYmj+O71C/eXEDw+crMAXiCn6SZrbTM8GT5TQ5yF2NcxhudopO4qoILjnwEHZZ+i37kRDFg6oCBccCD67oHTPexUkSqnKIIYLli5CdmE7UdvX6LuVG/VYJKevOUgMf0UzHDPgvtlp3gsSo09TfNPOtoeAiogL6cAHb1seZwv+6E8Pz7WqkkOTsBQYeHIfPE0OnQPDtUjVRA5MTTX5zt6rCCNDKNbqfkPu8V4am26ykaWOSTXZYIcfnywkG0TfPzdAyQvyxdUyl/r1b36bclQFiXcRzkz9zS9xx14Il3QjYXRbIFWcwm/mEFltBFPdATKo5Zh+wcTLiFh56YEUVa9/h6oN8281X6zxH4DOw=",
 | 
			
		||||
				'version': "0.3"
 | 
			
		||||
@@ -1900,127 +1900,229 @@ testData = {
 | 
			
		||||
			},
 | 
			
		||||
			//	username: 'tag', passphrase: 'tag'
 | 
			
		||||
			"38d2354c878a06fbdcccc493a23fc6d9be06eebb4f66952bbc1b736824b123f9": {
 | 
			
		||||
				"s": "e332fc34a678a6be7f025403c8007c1dff962cbae8acd1a490018ffb7477536d",
 | 
			
		||||
				"v": "afa4836f22b8a1fbfb0b6a563c2356c0438ef9a9dd56877af8e8421d0706e300",
 | 
			
		||||
				"version": "0.2",
 | 
			
		||||
				"maxNumberOfRecords": "100",
 | 
			
		||||
				"userDetails": '{"records":{"index":{"021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d":"0","ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8":"1","bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb":"2","5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee":"3","501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f":"4","b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa":"5"},"data":"EPB52oY8PC/GYaSORI9ytiiPIoDrIQrgu02OpI168mnT0BRtonqfvMUxejJ/nvgWCNgx7lCYMmGzaDCQtvmAj7q8DQHjgcG1HJS/BjaEJdlK+mIa0WmVED1nGi7HzZDP8XgeR6FSAS0MsIXqBI1BtOhPizWaQxQ7q/wiZpGwSMu4R8I/16u6bWNSdsIR52GkI2RXrXxit0BSs/ZHefCAEQVprygBJYTAsuBlI9UdOrtOG3ODCMGzzY69kC10YBPxEHgVdsJ8xGlM4QxrDoYcAXdqmHVNc8X3fsOSN6CdC+TPOYQ7kp7RsiWlAy/a26ASmfDioXikiEqSZ/m7Hz5EYBfdPIKzTf1HaTaj1EXPFwQ16Kkdyjlrc3OKl79Pe2JZ5lAfmf2aqHXxRFGk4aedDvwhMENdZYgea8IwBmmnalF+5U67gSY0YYe2ZixTnFKg1MRHAaGZSCJhZlbmcTKRzI4crNrRs03F9V10EfaUyIQdktRBNsKexQaJr1dxq524XJDSdIzu3A++8ks/khLFzrAQySWcjv2PmaFq/wf19HzVLOwJAkYv7Kp8AGpXpdNrtm2wNTiQ84a6KOuV8YW3jz8/m0VrsVe8wImHrih58lWi7SOHZ1y/ZjtSXwa8OEo/vdR3+SOFJujgYaoDcxLknMxXvjYjSUViLmAcHkM3SRx4ucGPlL9EOTFHhJJI3pynhubXYME/ujlkLRZEPQoSRKUWHAQ5X+O+1SwgPVPcSKqeEhM6kWhnXXpBWmUE+sSXfC4CC4Jqw+Mfrx6Mdx9ayep8ssge4ZIaDIVDUu3p2aEAQehwOvGzYec9jZfvhdODO0MK9cCV6D3iv8I="},"directLogins":{"index":{},"data":"VXRt7prFeG9+iLmWhkJVGKi9"},"preferences":{"data":"xPvWlbPo8iwq4OiL6BhiHWCg"},"oneTimePasswords":{"data":"UFam/MJzsuh7hiMUXruQrI94"},"version":"0.1"}',
 | 
			
		||||
				"statistics": "Pf9V+hgwcEUMUbiYj7lhOvy/",
 | 
			
		||||
				"userDetailsVersion": "0.4",
 | 
			
		||||
				"records": {
 | 
			
		||||
					"021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d": {
 | 
			
		||||
						"data": "coBU7HDciqWdtPrGoRE8x307+S0fXZPjdxCypClm9qXdQIJCTEohg8E3OHWydsZMI852VNgoIfhjv5+E8qzxCAwb+eh/2Lsv4+VWTi7MxVAgsqHk+NGOlpTzlBOtq/V86lZHuZajy4UWsLhoJvQxMNlV4Gryk+9G+MQv/ITYi/d6mRNMNQ==",
 | 
			
		||||
						"version": "0.4",
 | 
			
		||||
						"creationDate": "Thu Jul 03 13:44:30 GMT 2014",
 | 
			
		||||
						"updateDate": "Thu Jul 03 13:44:30 GMT 2014",
 | 
			
		||||
						"accessDate": "Thu Jul 03 13:44:30 GMT 2014",
 | 
			
		||||
						"currentVersion": "d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a",
 | 
			
		||||
						"versions": {
 | 
			
		||||
							"d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a": {
 | 
			
		||||
								"header": "####",
 | 
			
		||||
								"data": "NXmNUj/dXJImYLfqLmCFJeJh7GI51jzCitJBZS6bDoD1YzNj98weM4fW7AweTpvloojR1/1bzrvuEk13YKRYeDqD3WLKDC1QpAhmBj7EPkYfmOFizPRTpb3i0CkH2HQ2+OqCi27BdrMbU2VTNgBAtGl2RLQYa9yLJpz6wD1Bone192X3BuxWp2/9h8gYVqrmfT/aN5UGZlGHtJscjoqQME0=",
 | 
			
		||||
								"version": "0.4",
 | 
			
		||||
								"creationDate": "Thu Jul 03 13:44:30 GMT 2014",
 | 
			
		||||
								"updateDate": "Thu Jul 03 13:44:30 GMT 2014",
 | 
			
		||||
								"accessDate": "Thu Jul 03 13:44:30 GMT 2014"
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					"ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8": {
 | 
			
		||||
						"data": "+vz60IxhoBYTxnCKoiEVmr9BVBKQ2vFtDyJqvpA468m6+ngCQZUAuyRvuTy7HGtHh/8Fc+VHZy6sjy6z5noVIhSG8uQnOpMj7lIxr471DC2suKgYzM3buiHehmzF8W+eNIOumyysAgQxvIXF/12zYVGiYUKBw9s9NgayeCXWZCktGyKp/w==",
 | 
			
		||||
						"version": "0.4",
 | 
			
		||||
						"creationDate": "Thu Jul 03 13:44:54 GMT 2014",
 | 
			
		||||
						"updateDate": "Thu Jul 03 13:44:54 GMT 2014",
 | 
			
		||||
						"accessDate": "Thu Jul 03 13:44:54 GMT 2014",
 | 
			
		||||
						"currentVersion": "23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc",
 | 
			
		||||
						"versions": {
 | 
			
		||||
							"23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc": {
 | 
			
		||||
								"header": "####",
 | 
			
		||||
								"data": "tw4PEJ2+bpv69m2JlKlUo2TXTSWzhd5qVuuEXa78Fzz2TAA+SLIAn/0YMLMZqJpT9/UX2A5llYIg4tasSTiScdKduvLqTwjZ/wQL9zkWw9vlb5DwUboLHYTD24rLQDKtEuZtQzkds7cKmTS8JocHiePtOnw4WYav7d1mNrEFFz6RJU4RklG5T0ATN59UTg9uRseN4zSSIl21Np3kb7/QSEI=",
 | 
			
		||||
								"version": "0.4",
 | 
			
		||||
								"creationDate": "Thu Jul 03 13:44:54 GMT 2014",
 | 
			
		||||
								"updateDate": "Thu Jul 03 13:44:54 GMT 2014",
 | 
			
		||||
								"accessDate": "Thu Jul 03 13:44:54 GMT 2014"
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					"bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb": {
 | 
			
		||||
						"data": "TGS6lm8w6OVFEnyQkjzJnS/FWx1h0LgrdpJwRKMz+/o5pJi0S9KGSzihtc6iek2/Dqixqh/Nxi8fZY2tNsB6n50aNHR+gVX4LHhgQOpw/3aYcWMd9WIuwdMPKDY6RiCBDLwxCDuhtKDCsGkd105+v58h08wYeCc8WDEg2vsR1bXQQZMYQw==",
 | 
			
		||||
						"version": "0.4",
 | 
			
		||||
						"creationDate": "Thu Jul 03 13:45:16 GMT 2014",
 | 
			
		||||
						"updateDate": "Thu Jul 03 13:45:16 GMT 2014",
 | 
			
		||||
						"accessDate": "Thu Jul 03 13:45:16 GMT 2014",
 | 
			
		||||
						"currentVersion": "a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d",
 | 
			
		||||
						"versions": {
 | 
			
		||||
							"a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d": {
 | 
			
		||||
								"header": "####",
 | 
			
		||||
								"data": "iNlrPeH/d9jtI91isY3r8n1QUtUYXVT1mhrRisyGwrpQo+4XCqMYattH31MrFh1hZCmplL0+vq8+bWo1JqA1HclSpAetyDIQ7NnlqGlnZYMBII3GgBfknHJRIlnm+RNFFQeOHWjhyL7YQVpmmC0ONJ++HaAmPxvOWzP1g/rKbsEoQyIBqqG32H3nxSU/bUVo+F5K5sfSNWnayPsDlEv5pNM=",
 | 
			
		||||
								"version": "0.4",
 | 
			
		||||
								"creationDate": "Thu Jul 03 13:45:16 GMT 2014",
 | 
			
		||||
								"updateDate": "Thu Jul 03 13:45:16 GMT 2014",
 | 
			
		||||
								"accessDate": "Thu Jul 03 13:45:16 GMT 2014"
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					"5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee": {
 | 
			
		||||
						"data": "vEEFjeDfMPr1ahbjPA56Su31uJvyXcOAfJ2SfCdd0xTld6tj7iRsh81g1UYw8W1nvyRg4ymSiXIwEmx5cyOa/lj7eWRM0AqFy3qfqSUJze8Xn8u9RYiE0rt4LZLSDk9FogvvMIXKDbCvUbmyPSA83bmjCvDkiCvhqY4MhwGE8Jk/liE3YQ==",
 | 
			
		||||
						"version": "0.4",
 | 
			
		||||
						"creationDate": "Thu Jul 03 13:45:38 GMT 2014",
 | 
			
		||||
						"updateDate": "Thu Jul 03 13:45:38 GMT 2014",
 | 
			
		||||
						"accessDate": "Thu Jul 03 13:45:38 GMT 2014",
 | 
			
		||||
						"currentVersion": "211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626",
 | 
			
		||||
						"versions": {
 | 
			
		||||
							"211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626": {
 | 
			
		||||
								"header": "####",
 | 
			
		||||
								"data": "Ouu/BSyDHstyLcddZd1EsSQRWKTjQUt9kFNSYoxQOUBXWw+ukqDPPMXYZWggjrGAbT5hFf4yoEqi2VCCAdZT5juwmMrSEGZjoFSSw/e5OYH3ptoZAQ4ThNo75R2oJfI7/kUMKBeeXE4zykRYWv4aEOGHtSKpnzydGHnvDFfpxInFx2MH1eIYH+BpCujMDN0aDNFLRWl9isZ070DioTNgvI8=",
 | 
			
		||||
								"version": "0.4",
 | 
			
		||||
								"creationDate": "Thu Jul 03 13:45:38 GMT 2014",
 | 
			
		||||
								"updateDate": "Thu Jul 03 13:45:38 GMT 2014",
 | 
			
		||||
								"accessDate": "Thu Jul 03 13:45:38 GMT 2014"
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					"501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f": {
 | 
			
		||||
						"data": "7aTkEoBButGoPiXCpAfn+CrJpzfw5zau1kEBY2uWBJ3cAT3XbkDgAodPmU/HDTV1pb7+2a2sAnwThDWCSkJfMGG6i3eRlQJj9WZfIEIv0W7UDMGbZdhuweSEPTf7GMR4BtHPMgVgLToSn+YOc0tasavVGz3+rV9qNFf9Rf2PrGKFQkY7Kg==",
 | 
			
		||||
						"version": "0.4",
 | 
			
		||||
						"creationDate": "Thu Jul 03 13:46:07 GMT 2014",
 | 
			
		||||
						"updateDate": "Thu Jul 03 13:46:07 GMT 2014",
 | 
			
		||||
						"accessDate": "Thu Jul 03 13:46:07 GMT 2014",
 | 
			
		||||
						"currentVersion": "a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b",
 | 
			
		||||
						"versions": {
 | 
			
		||||
							"a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b": {
 | 
			
		||||
								"header": "####",
 | 
			
		||||
								"data": "blB9bmxoEh//V40FoD9tLbQGyHiFAcLn9Rj4KDOp5DQRiLLlxqgvShShXEVtjUmiTjnHGlkNm6RQtZTaJrG84nV29QncBxLKMNnZKmqW2fXp2uyd4k+zzg4r7ilC29Vh1WP6bNxapwivDUw1n1Y9bEsX8LSAtCSIseIXszciSnLQ6ktyzBGzuDppc/cQ94TfGFKTSwZdts3d34Kxh8q0NTE=",
 | 
			
		||||
								"version": "0.4",
 | 
			
		||||
								"creationDate": "Thu Jul 03 13:46:07 GMT 2014",
 | 
			
		||||
								"updateDate": "Thu Jul 03 13:46:07 GMT 2014",
 | 
			
		||||
								"accessDate": "Thu Jul 03 13:46:07 GMT 2014"
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					"b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa": {
 | 
			
		||||
						"data": "cZQZboAoipwpOdCuvyXfS1T0ul0FnoWBAB0jqwQ282mWHjOBM/JI/7jk4z20qaYp/3XTGpVvbYPqvQn/+KyYiLT8aR/JkiFQ38wioaaq6X0Qg38Y2itPXMZjTnSGnf0boPf6mP8s9k8GGubuk4Gi1n4uJECLESX4In3VGq02hHcoxfZwMQ==",
 | 
			
		||||
						"version": "0.4",
 | 
			
		||||
						"creationDate": "Thu Jul 03 13:46:28 GMT 2014",
 | 
			
		||||
						"updateDate": "Thu Jul 03 13:46:28 GMT 2014",
 | 
			
		||||
						"accessDate": "Thu Jul 03 13:46:28 GMT 2014",
 | 
			
		||||
						"currentVersion": "b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088",
 | 
			
		||||
						"versions": {
 | 
			
		||||
							"b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088": {
 | 
			
		||||
								"header": "####",
 | 
			
		||||
								"data": "zOv15MnYpRcPyUoig13mGdWktiSLStCla0RVOp3laAEBBxplEao1RXWd/FOjKRmf2rx1Ma+s1s+CB1r2Z8t8VFRLDLQQk+m4LuGzY/QvHXbvcWMtNFxb8Ax464lX2w8FHZDBbcN29nfroeMX0Cq9oD6BKVrRdouUESHpX/oMDZ6VJRfZhv7ZIqdEET3+6u8Ub+OHdYyktYH4OHJTpW5eRc8=",
 | 
			
		||||
								"version": "0.4",
 | 
			
		||||
								"creationDate": "Thu Jul 03 13:46:28 GMT 2014",
 | 
			
		||||
								"updateDate": "Thu Jul 03 13:46:28 GMT 2014",
 | 
			
		||||
								"accessDate": "Thu Jul 03 13:46:28 GMT 2014"
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			    "userDetailsVersion": "0.4",
 | 
			
		||||
			    "accountInfo": {
 | 
			
		||||
			        "features": [
 | 
			
		||||
			            "UPDATE_CREDENTIALS",
 | 
			
		||||
			            "EDIT_CARD",
 | 
			
		||||
			            "CARD_DETAILS",
 | 
			
		||||
			            "ADD_CARD",
 | 
			
		||||
			            "DELETE_CARD",
 | 
			
		||||
			            "OFFLINE_COPY",
 | 
			
		||||
			            "LIST_CARDS"
 | 
			
		||||
			        ],
 | 
			
		||||
			        "paymentVerificationPending": false,
 | 
			
		||||
			        "currentSubscriptionType": "EARLY_ADOPTER",
 | 
			
		||||
			        "isExpiring": false,
 | 
			
		||||
			        "latestActiveLevel": "EARLY_ADOPTER",
 | 
			
		||||
			        "payments": [],
 | 
			
		||||
			        "featureSet": "FULL",
 | 
			
		||||
			        "latestActiveThreshold": "-1.00000000",
 | 
			
		||||
			        "referenceDate": "Wed, 17 June 2015 12:44:29 UTC",
 | 
			
		||||
			        "isExpired": false,
 | 
			
		||||
			        "expirationDate": "Mon, 01 January 4001 00:00:00 UTC"
 | 
			
		||||
			    },
 | 
			
		||||
			    "s": "e332fc34a678a6be7f025403c8007c1dff962cbae8acd1a490018ffb7477536d",
 | 
			
		||||
			    "records": {
 | 
			
		||||
			        "501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f": {
 | 
			
		||||
			            "reference": "501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f",
 | 
			
		||||
			            "updateDate": "Thu, 03 July 2014 13:46:07 UTC",
 | 
			
		||||
			            "data": "7aTkEoBButGoPiXCpAfn+CrJpzfw5zau1kEBY2uWBJ3cAT3XbkDgAodPmU/HDTV1pb7+2a2sAnwThDWCSkJfMGG6i3eRlQJj9WZfIEIv0W7UDMGbZdhuweSEPTf7GMR4BtHPMgVgLToSn+YOc0tasavVGz3+rV9qNFf9Rf2PrGKFQkY7Kg==",
 | 
			
		||||
			            "accessDate": "Wed, 17 June 2015 12:44:07 UTC",
 | 
			
		||||
			            "versions": {"a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b": {
 | 
			
		||||
			                "reference": "a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b",
 | 
			
		||||
			                "updateDate": "Thu, 03 July 2014 13:46:07 UTC",
 | 
			
		||||
			                "data": "blB9bmxoEh//V40FoD9tLbQGyHiFAcLn9Rj4KDOp5DQRiLLlxqgvShShXEVtjUmiTjnHGlkNm6RQtZTaJrG84nV29QncBxLKMNnZKmqW2fXp2uyd4k+zzg4r7ilC29Vh1WP6bNxapwivDUw1n1Y9bEsX8LSAtCSIseIXszciSnLQ6ktyzBGzuDppc/cQ94TfGFKTSwZdts3d34Kxh8q0NTE=",
 | 
			
		||||
			                "accessDate": "Wed, 17 June 2015 12:44:07 UTC",
 | 
			
		||||
			                "header": "####",
 | 
			
		||||
			                "creationDate": "Thu, 03 July 2014 13:46:07 UTC",
 | 
			
		||||
			                "version": "0.4"
 | 
			
		||||
			            }},
 | 
			
		||||
			            "creationDate": "Thu, 03 July 2014 13:46:07 UTC",
 | 
			
		||||
			            "version": "0.4",
 | 
			
		||||
			            "currentVersion": "a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b",
 | 
			
		||||
			            "oldestUsedEncryptedVersion": "0.4"
 | 
			
		||||
			        },
 | 
			
		||||
			        "5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee": {
 | 
			
		||||
			            "reference": "5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee",
 | 
			
		||||
			            "updateDate": "Thu, 03 July 2014 13:45:38 UTC",
 | 
			
		||||
			            "data": "vEEFjeDfMPr1ahbjPA56Su31uJvyXcOAfJ2SfCdd0xTld6tj7iRsh81g1UYw8W1nvyRg4ymSiXIwEmx5cyOa/lj7eWRM0AqFy3qfqSUJze8Xn8u9RYiE0rt4LZLSDk9FogvvMIXKDbCvUbmyPSA83bmjCvDkiCvhqY4MhwGE8Jk/liE3YQ==",
 | 
			
		||||
			            "accessDate": "Wed, 17 June 2015 12:43:49 UTC",
 | 
			
		||||
			            "versions": {"211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626": {
 | 
			
		||||
			                "reference": "211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626",
 | 
			
		||||
			                "updateDate": "Thu, 03 July 2014 13:45:38 UTC",
 | 
			
		||||
			                "data": "Ouu/BSyDHstyLcddZd1EsSQRWKTjQUt9kFNSYoxQOUBXWw+ukqDPPMXYZWggjrGAbT5hFf4yoEqi2VCCAdZT5juwmMrSEGZjoFSSw/e5OYH3ptoZAQ4ThNo75R2oJfI7/kUMKBeeXE4zykRYWv4aEOGHtSKpnzydGHnvDFfpxInFx2MH1eIYH+BpCujMDN0aDNFLRWl9isZ070DioTNgvI8=",
 | 
			
		||||
			                "accessDate": "Wed, 17 June 2015 12:43:49 UTC",
 | 
			
		||||
			                "header": "####",
 | 
			
		||||
			                "creationDate": "Thu, 03 July 2014 13:45:38 UTC",
 | 
			
		||||
			                "version": "0.4"
 | 
			
		||||
			            }},
 | 
			
		||||
			            "creationDate": "Thu, 03 July 2014 13:45:38 UTC",
 | 
			
		||||
			            "version": "0.4",
 | 
			
		||||
			            "currentVersion": "211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626",
 | 
			
		||||
			            "oldestUsedEncryptedVersion": "0.4"
 | 
			
		||||
			        },
 | 
			
		||||
			        "b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa": {
 | 
			
		||||
			            "reference": "b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa",
 | 
			
		||||
			            "updateDate": "Thu, 03 July 2014 13:46:28 UTC",
 | 
			
		||||
			            "data": "cZQZboAoipwpOdCuvyXfS1T0ul0FnoWBAB0jqwQ282mWHjOBM/JI/7jk4z20qaYp/3XTGpVvbYPqvQn/+KyYiLT8aR/JkiFQ38wioaaq6X0Qg38Y2itPXMZjTnSGnf0boPf6mP8s9k8GGubuk4Gi1n4uJECLESX4In3VGq02hHcoxfZwMQ==",
 | 
			
		||||
			            "accessDate": "Fri, 10 April 2015 09:27:54 UTC",
 | 
			
		||||
			            "versions": {"b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088": {
 | 
			
		||||
			                "reference": "b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088",
 | 
			
		||||
			                "updateDate": "Thu, 03 July 2014 13:46:28 UTC",
 | 
			
		||||
			                "data": "zOv15MnYpRcPyUoig13mGdWktiSLStCla0RVOp3laAEBBxplEao1RXWd/FOjKRmf2rx1Ma+s1s+CB1r2Z8t8VFRLDLQQk+m4LuGzY/QvHXbvcWMtNFxb8Ax464lX2w8FHZDBbcN29nfroeMX0Cq9oD6BKVrRdouUESHpX/oMDZ6VJRfZhv7ZIqdEET3+6u8Ub+OHdYyktYH4OHJTpW5eRc8=",
 | 
			
		||||
			                "accessDate": "Fri, 10 April 2015 09:27:54 UTC",
 | 
			
		||||
			                "header": "####",
 | 
			
		||||
			                "creationDate": "Thu, 03 July 2014 13:46:28 UTC",
 | 
			
		||||
			                "version": "0.4"
 | 
			
		||||
			            }},
 | 
			
		||||
			            "creationDate": "Thu, 03 July 2014 13:46:28 UTC",
 | 
			
		||||
			            "version": "0.4",
 | 
			
		||||
			            "currentVersion": "b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088",
 | 
			
		||||
			            "oldestUsedEncryptedVersion": "0.4"
 | 
			
		||||
			        },
 | 
			
		||||
			        "ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8": {
 | 
			
		||||
			            "reference": "ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8",
 | 
			
		||||
			            "updateDate": "Thu, 03 July 2014 13:44:54 UTC",
 | 
			
		||||
			            "data": "+vz60IxhoBYTxnCKoiEVmr9BVBKQ2vFtDyJqvpA468m6+ngCQZUAuyRvuTy7HGtHh/8Fc+VHZy6sjy6z5noVIhSG8uQnOpMj7lIxr471DC2suKgYzM3buiHehmzF8W+eNIOumyysAgQxvIXF/12zYVGiYUKBw9s9NgayeCXWZCktGyKp/w==",
 | 
			
		||||
			            "accessDate": "Wed, 17 June 2015 12:43:13 UTC",
 | 
			
		||||
			            "versions": {"23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc": {
 | 
			
		||||
			                "reference": "23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc",
 | 
			
		||||
			                "updateDate": "Thu, 03 July 2014 13:44:54 UTC",
 | 
			
		||||
			                "data": "tw4PEJ2+bpv69m2JlKlUo2TXTSWzhd5qVuuEXa78Fzz2TAA+SLIAn/0YMLMZqJpT9/UX2A5llYIg4tasSTiScdKduvLqTwjZ/wQL9zkWw9vlb5DwUboLHYTD24rLQDKtEuZtQzkds7cKmTS8JocHiePtOnw4WYav7d1mNrEFFz6RJU4RklG5T0ATN59UTg9uRseN4zSSIl21Np3kb7/QSEI=",
 | 
			
		||||
			                "accessDate": "Wed, 17 June 2015 12:43:13 UTC",
 | 
			
		||||
			                "header": "####",
 | 
			
		||||
			                "creationDate": "Thu, 03 July 2014 13:44:54 UTC",
 | 
			
		||||
			                "version": "0.4"
 | 
			
		||||
			            }},
 | 
			
		||||
			            "creationDate": "Thu, 03 July 2014 13:44:54 UTC",
 | 
			
		||||
			            "version": "0.4",
 | 
			
		||||
			            "currentVersion": "23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc",
 | 
			
		||||
			            "oldestUsedEncryptedVersion": "0.4"
 | 
			
		||||
			        },
 | 
			
		||||
			        "bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb": {
 | 
			
		||||
			            "reference": "bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb",
 | 
			
		||||
			            "updateDate": "Thu, 03 July 2014 13:45:16 UTC",
 | 
			
		||||
			            "data": "TGS6lm8w6OVFEnyQkjzJnS/FWx1h0LgrdpJwRKMz+/o5pJi0S9KGSzihtc6iek2/Dqixqh/Nxi8fZY2tNsB6n50aNHR+gVX4LHhgQOpw/3aYcWMd9WIuwdMPKDY6RiCBDLwxCDuhtKDCsGkd105+v58h08wYeCc8WDEg2vsR1bXQQZMYQw==",
 | 
			
		||||
			            "accessDate": "Wed, 17 June 2015 12:43:36 UTC",
 | 
			
		||||
			            "versions": {"a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d": {
 | 
			
		||||
			                "reference": "a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d",
 | 
			
		||||
			                "updateDate": "Thu, 03 July 2014 13:45:16 UTC",
 | 
			
		||||
			                "data": "iNlrPeH/d9jtI91isY3r8n1QUtUYXVT1mhrRisyGwrpQo+4XCqMYattH31MrFh1hZCmplL0+vq8+bWo1JqA1HclSpAetyDIQ7NnlqGlnZYMBII3GgBfknHJRIlnm+RNFFQeOHWjhyL7YQVpmmC0ONJ++HaAmPxvOWzP1g/rKbsEoQyIBqqG32H3nxSU/bUVo+F5K5sfSNWnayPsDlEv5pNM=",
 | 
			
		||||
			                "accessDate": "Wed, 17 June 2015 12:43:36 UTC",
 | 
			
		||||
			                "header": "####",
 | 
			
		||||
			                "creationDate": "Thu, 03 July 2014 13:45:16 UTC",
 | 
			
		||||
			                "version": "0.4"
 | 
			
		||||
			            }},
 | 
			
		||||
			            "creationDate": "Thu, 03 July 2014 13:45:16 UTC",
 | 
			
		||||
			            "version": "0.4",
 | 
			
		||||
			            "currentVersion": "a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d",
 | 
			
		||||
			            "oldestUsedEncryptedVersion": "0.4"
 | 
			
		||||
			        },
 | 
			
		||||
			        "021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d": {
 | 
			
		||||
			            "reference": "021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d",
 | 
			
		||||
			            "updateDate": "Thu, 03 July 2014 13:44:30 UTC",
 | 
			
		||||
			            "data": "coBU7HDciqWdtPrGoRE8x307+S0fXZPjdxCypClm9qXdQIJCTEohg8E3OHWydsZMI852VNgoIfhjv5+E8qzxCAwb+eh/2Lsv4+VWTi7MxVAgsqHk+NGOlpTzlBOtq/V86lZHuZajy4UWsLhoJvQxMNlV4Gryk+9G+MQv/ITYi/d6mRNMNQ==",
 | 
			
		||||
			            "accessDate": "Wed, 17 June 2015 12:42:51 UTC",
 | 
			
		||||
			            "versions": {"d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a": {
 | 
			
		||||
			                "reference": "d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a",
 | 
			
		||||
			                "updateDate": "Thu, 03 July 2014 13:44:30 UTC",
 | 
			
		||||
			                "data": "NXmNUj/dXJImYLfqLmCFJeJh7GI51jzCitJBZS6bDoD1YzNj98weM4fW7AweTpvloojR1/1bzrvuEk13YKRYeDqD3WLKDC1QpAhmBj7EPkYfmOFizPRTpb3i0CkH2HQ2+OqCi27BdrMbU2VTNgBAtGl2RLQYa9yLJpz6wD1Bone192X3BuxWp2/9h8gYVqrmfT/aN5UGZlGHtJscjoqQME0=",
 | 
			
		||||
			                "accessDate": "Wed, 17 June 2015 12:42:51 UTC",
 | 
			
		||||
			                "header": "####",
 | 
			
		||||
			                "creationDate": "Thu, 03 July 2014 13:44:30 UTC",
 | 
			
		||||
			                "version": "0.4"
 | 
			
		||||
			            }},
 | 
			
		||||
			            "creationDate": "Thu, 03 July 2014 13:44:30 UTC",
 | 
			
		||||
			            "version": "0.4",
 | 
			
		||||
			            "currentVersion": "d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a",
 | 
			
		||||
			            "oldestUsedEncryptedVersion": "0.4"
 | 
			
		||||
			        }
 | 
			
		||||
			    },
 | 
			
		||||
			    "v": "afa4836f22b8a1fbfb0b6a563c2356c0438ef9a9dd56877af8e8421d0706e300",
 | 
			
		||||
			    "version": "0.2",
 | 
			
		||||
			    "userDetails": "{\"records\":{\"index\":{\"021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d\":\"0\",\"ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8\":\"1\",\"bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb\":\"2\",\"5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee\":\"3\",\"501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f\":\"4\",\"b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa\":\"5\"},\"data\":\"5oWOZGW\/lnLtQ\/JzAiZCP6gDN8A3Vpnona1uCdNl5j2n8Q5JHOrxG2hYbIpO1pQ0H3PrKsYrMqstuHggaTB0\/Rzm\/ficHaUWhRJqmQrrqu3S14UQ6Ts7ZE+me+JHC67dEMBxxSRvw9DMErR2ph1O1AUWlEqjqDt99UU6T\/mA\/0aqfJu9fsFC5AVk\/QH6Pzp8xMK1oX4AhB9H3hVZzWvIr2jYFTyVRldSfD5h1HKNt2cCyEWvAzSvBmIsTcTJIyah7Uv8ZVRk1zFTvdD2eWgNkKrQo8fMdCalw1cT7lQ3esccKllp1ydQZSNZ6365HYu1fC4negehCzMz5eu3Y\/Q236F2rZo63cXHQgDi38GWM+cdXL7JgXdPMfMudyCKIxJnFVFK5VzghMU\/n7i\/nYZD1K04Q\/E6fKP4kKkJlNd3JEpr\/nM9cVpWXB5ghrI4pvo3kxNy9DWVL5mD8eANmqZyNZqii2jTpPF+oZS8NtKWPSwLs8HbcnS\/eRVgv0pE8mLCp0nPxqfBHrlORX5phzSURONZZIvqxh\/s0uxYEQFuzFl2G+NhtbhL25Q4rLLUZrFAnlp1qgoH1sUl0B4WzEN8qw56uOtNesRv63NoETIhvOYNU89t4CsCnRznuWm774KWNnbuBXTiDw\/rreQU59PgXgr7BPuJFvzonqiDTiqRHVWDYnTP5YSWBwtEEr24vfCjWx1BmoxytH0hRhWcJOqJGW3rhLHkBTLg9rDUFBKR5HJbjzW1Htw\/Yhq9PzhRVbkTNdvVzVv6cMiFNeuJG2LyBeSe2ioDcyWSYmHedEYnn3PRXEJhD3AAdpANj2MGP5yvZ7dHNXGXz2DgkhRO24PoiHuehe2jMx9Zkw==\"},\"directLogins\":{\"index\":{},\"data\":\"Ud\/RyTy7CT291MeUshmhjoT6\"},\"preferences\":{\"data\":\"hseSN5pYGCAs144bQVlVHtop\"},\"oneTimePasswords\":{\"data\":\"2GS0OVjaU42kt4yinfQUXK6c\"},\"version\":\"0.1\"}",
 | 
			
		||||
			    "maxNumberOfRecords": 100,
 | 
			
		||||
			    "statistics": "Pf9V+hgwcEUMUbiYj7lhOvy/"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};}(),
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
	'1/1_data': function () { return {
 | 
			
		||||
		users:{
 | 
			
		||||
			'catchAllUser': {
 | 
			
		||||
				__masterkey_test_value__: 'masterkey',
 | 
			
		||||
				s: '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00',
 | 
			
		||||
				v: '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00'
 | 
			
		||||
			},
 | 
			
		||||
			//	username: '1', passphrase: '1'
 | 
			
		||||
			"3073058ba04e7d35a161af27f07ddb24d603d2a6418b4140010b7cdb24ff4774": {
 | 
			
		||||
				"userDetailsVersion": "0.4",
 | 
			
		||||
				"accountInfo": {
 | 
			
		||||
					"features": [
 | 
			
		||||
						"UPDATE_CREDENTIALS",
 | 
			
		||||
						"EDIT_CARD",
 | 
			
		||||
						"CARD_DETAILS",
 | 
			
		||||
						"ADD_CARD",
 | 
			
		||||
						"DELETE_CARD",
 | 
			
		||||
						"OFFLINE_COPY",
 | 
			
		||||
						"LIST_CARDS"
 | 
			
		||||
					],
 | 
			
		||||
					"paymentVerificationPending": false,
 | 
			
		||||
					"currentSubscriptionType": "EARLY_ADOPTER",
 | 
			
		||||
					"isExpiring": false,
 | 
			
		||||
					"latestActiveLevel": "EARLY_ADOPTER",
 | 
			
		||||
					"payments": [],
 | 
			
		||||
					"featureSet": "FULL",
 | 
			
		||||
					"latestActiveThreshold": "-1.00000000",
 | 
			
		||||
					"referenceDate": "Wed, 17 June 2015 13:43:24 UTC",
 | 
			
		||||
					"isExpired": false,
 | 
			
		||||
					"expirationDate": "Mon, 01 January 4001 00:00:00 UTC"
 | 
			
		||||
				},
 | 
			
		||||
				"s": "a363479cf2f229466e01f32bfb783800745c740b4a955a83c0fbf561028bdba7",
 | 
			
		||||
				"records": {
 | 
			
		||||
					"327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83": {
 | 
			
		||||
						"reference": "327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83",
 | 
			
		||||
						"updateDate": "Wed, 17 June 2015 13:42:54 UTC",
 | 
			
		||||
						"data": "SPU205djKKDJ7mg6txo5B9z0myET/8lcwjuT+IEMJVIw4JdBq+gXQ9cxESKQjvuuGnZyNRPzOKgfXlJTtgUwtPSwmpigrnUMilxcBtcpOkVe+kZXe+N1J7iMli1xVHfpmrhSEA8E+zZ4OEnGuHiJgX9qzNix15rbeZLX7bXBsWUowHuIvw==",
 | 
			
		||||
						"accessDate": "Wed, 17 June 2015 13:42:54 UTC",
 | 
			
		||||
						"versions": {
 | 
			
		||||
							"eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01": {
 | 
			
		||||
								"reference": "eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01",
 | 
			
		||||
								"updateDate": "Wed, 17 June 2015 13:42:54 UTC",
 | 
			
		||||
								"data": "Em0rLpHXv9IKtyVLWPGXeM6erk52v/1nYyh0DqT/UnL2pZjU4PRAPvKYZa5yhkx7QqgfWJNtdMUKbGN4DAfmKYcaAZZl0fcYRddAAduWUdG4Zuwk41hJ5gzomf0oqAtQU8lPK4VQFo1iyOctWVJ19h+/fZbUvLVzWOWXBMvItu054w+jHMDWF/hFuL09HQoyMz1JeXJroRTVx47pLbVVkUYNBOCz5FSTIRUqdmeSkaSiLtrLI0349SVrvPEXWPghGgKdWBiJCn/lFivmRKenJtGpxJwiV1CfQIS00JeyEFg+zqWBg31A8cp70NSHXVuprYbruWaMRdwZlI4PNl5JuzGdhL/z3iR+3TevcOIrHkvgH4/zVV+BHYlIPMpwj1zs4C0XNr+DF7F3FX3gCuU4CbMdzt6GoPYJnKAAfM+bZGQpAZp2sdzuZWdGi2EuHNicjLhdZF/TcbeGNjVG6RdliFvrqC+o0wet3OO7MRyHDU1AqWNPTgAo7gIp33DH0lf2HOtXJH8HjOXNSa7fgW5JhyfUxyddEONJwXUOL9KbkXsSElJjk+W0/QiWqw==",
 | 
			
		||||
								"accessDate": "Wed, 17 June 2015 13:42:54 UTC",
 | 
			
		||||
								"header": "####",
 | 
			
		||||
								"creationDate": "Wed, 17 June 2015 13:42:54 UTC",
 | 
			
		||||
								"version": "0.4"
 | 
			
		||||
							}
 | 
			
		||||
						},
 | 
			
		||||
						"creationDate": "Wed, 17 June 2015 13:42:54 UTC",
 | 
			
		||||
						"version": "0.4",
 | 
			
		||||
						"currentVersion": "eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01",
 | 
			
		||||
						"oldestUsedEncryptedVersion": "0.4"
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				"v": "baef74012d85813dca18ab0218bb27c63ffabc414223f05c015b4b73485b8fed",
 | 
			
		||||
				"version": "0.2",
 | 
			
		||||
				"userDetails": "{\"records\":{\"index\":{\"327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83\":\"0\"},\"data\":\"HF+LSoX668EqFNltHvK7vaiO4srKLI5AzxXmlTssJNUCgSoWcvyE3psARFbnRamrUKHsFXipQypQjLH3W9eHNCt1im4GnxLGJskFJx0IjfYwrIJP1MNThUoz2EMMGAJiZS4WAFxnpMj3pD1JpFvNVtC+5B1NNQ==\"},\"directLogins\":{\"index\":{},\"data\":\"AJvILR1GywXePCwZd6HE27im\"},\"preferences\":{\"data\":\"5sK4mjoBQ3ONd4oKpxB9yWcV\"},\"oneTimePasswords\":{\"data\":\"J1yST9vO+PGreat6f8W7McqF\"},\"version\":\"0.1\"}",
 | 
			
		||||
				"maxNumberOfRecords": 100,
 | 
			
		||||
				"statistics": "h4SscRLQf8X+q5+Qhfs6MG44"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};}(),	
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	//-------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
	'tt/tt_data': function () { return {
 | 
			
		||||
 
 | 
			
		||||
@@ -2022,8 +2022,8 @@ console.log("PROXY", proxy);
 | 
			
		||||
		
 | 
			
		||||
		newPassphrase = 'zreppilc';
 | 
			
		||||
		proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
 | 
			
		||||
		user = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return 'clipperz';}});
 | 
			
		||||
		user2 = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return newPassphrase;}});
 | 
			
		||||
		user = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction: MochiKit.Base.partial(MochiKit.Async.succeed, 'clipperz')});
 | 
			
		||||
		user2 = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction: MochiKit.Base.partial(MochiKit.Async.succeed, newPassphrase)});
 | 
			
		||||
 | 
			
		||||
		deferredResult = new Clipperz.Async.Deferred("changePassphrase_test", someTestArgs);
 | 
			
		||||
		deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['joe_clipperz_offline_copy_data']);
 | 
			
		||||
@@ -2034,7 +2034,7 @@ console.log("PROXY", proxy);
 | 
			
		||||
		deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
 | 
			
		||||
		deferredResult.addTest(20, "This account has oly a single card");
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user, 'changePassphrase', newPassphrase);
 | 
			
		||||
		deferredResult.addMethod(user, 'changePassphrase', MochiKit.Base.partial(MochiKit.Async.succeed, newPassphrase));
 | 
			
		||||
		deferredResult.addMethod(user, 'logout');
 | 
			
		||||
 | 
			
		||||
		deferredResult.addMethod(user2, 'login');
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user