1
0
mirror of http://git.whoc.org.uk/git/password-manager.git synced 2025-10-24 17:27:35 +02:00

Implemented Attachments in client

This commit is contained in:
Dario Chiappetta
2015-11-23 16:10:44 +01:00
parent 8608fb4253
commit 8c59393433
50 changed files with 4862 additions and 272 deletions

View File

@@ -703,6 +703,91 @@ MochiKit.Base.update(Clipperz.Async, {
'clearResult': function () {},
//=========================================================================
'doXHR': function (url, opts) {
// ALMOST COPYCAT OF MochiKit version
/*
Work around a Firefox bug by dealing with XHR during
the next event loop iteration. Maybe it's this one:
https://bugzilla.mozilla.org/show_bug.cgi?id=249843
*/
return MochiKit.Async.callLater(0, Clipperz.Async._doXHR, url, opts);
},
_doXHR: function (url, opts) {
// ALMOST COPYCAT OF MochiKit version
var m = MochiKit.Base;
opts = m.update({
method: 'GET',
sendContent: ''
/*
queryString: undefined,
username: undefined,
password: undefined,
headers: undefined,
mimeType: undefined,
responseType: undefined,
withCredentials: undefined
*/
}, opts);
var self = MochiKit.Async;
var req = self.getXMLHttpRequest();
// Extra options not handled by MochiKit
if (req.upload && opts.uploadProgress) {
req.upload.onprogress = opts.uploadProgress;
}
if (opts.downloadProgress) {
req.onprogress = opts.downloadProgress;
}
if (opts.queryString) {
var qs = m.queryString(opts.queryString);
if (qs) {
url += "?" + qs;
}
}
// Safari will send undefined:undefined, so we have to check.
// We can't use apply, since the function is native.
if ('username' in opts) {
req.open(opts.method, url, true, opts.username, opts.password);
} else {
req.open(opts.method, url, true);
}
if (req.overrideMimeType && opts.mimeType) {
req.overrideMimeType(opts.mimeType);
}
req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
if (opts.headers) {
var headers = opts.headers;
if (!m.isArrayLike(headers)) {
headers = m.items(headers);
}
for (var i = 0; i < headers.length; i++) {
var header = headers[i];
var name = header[0];
var value = header[1];
req.setRequestHeader(name, value);
}
}
if ("responseType" in opts && "responseType" in req) {
req.responseType = opts.responseType;
}
if (opts.withCredentials) {
req.withCredentials = 'true';
}
return self.sendXMLHttpRequest(req, opts.sendContent);
},
//=========================================================================
__syntaxFix__: "syntax fix"
});

View File

@@ -36,6 +36,44 @@ if (typeof(Clipperz.Crypto.AES_2) == 'undefined') { Clipperz.Crypto.AES_2 = {};
//#############################################################################
Clipperz.Crypto.AES_2.EncryptionStreamingExecutionContext = function(args) {
args = args || {};
this._key = args.key || Clipperz.Base.exception.raise('MandatoryParameter');
this._nonce = args.nonce || Clipperz.Base.exception.raise('MandatoryParameter');
return this;
}
Clipperz.Crypto.AES_2.EncryptionStreamingExecutionContext.prototype = MochiKit.Base.update(null, {
key: function () {
return this._key;
},
nonce: function () {
return this._nonce;
},
deferredProcessBlock: function (someData) {
var deferredResult
var executionContext;
executionContext = new Clipperz.Crypto.AES_2.DeferredExecutionContext({key:this.key(), message:someData, nonce:this.nonce()});
deferredResult = new Clipperz.Async.Deferred("AES.deferredDecrypt", {trace:false});
deferredResult.addCallback(Clipperz.Crypto.AES_2.deferredEncryptBlocks);
deferredResult.addCallback(MochiKit.Base.bind(function (anExecutionContext) {
this._nonce = new Clipperz.ByteArray(anExecutionContext.nonceArray());
return anExecutionContext.result();
}, this));
deferredResult.callback(executionContext);
return deferredResult;
},
});
//#############################################################################
Clipperz.Crypto.AES_2.DeferredExecutionContext = function(args) {
args = args || {};
@@ -110,8 +148,9 @@ Clipperz.Crypto.AES_2.DeferredExecutionContext.prototype = MochiKit.Base.update(
//var originalChunks = this._elaborationChunks;
if (anElapsedTime > 0) {
this._elaborationChunks = Math.round(this._elaborationChunks * ((anElapsedTime + 1000)/(anElapsedTime * 2)));
//console.log("tuneExecutionParameter", this._elaborationChunks);
}
//Clipperz.log("tuneExecutionParameters - elapsedTime: " + anElapsedTime + /*originalChunks,*/ " chunks # " + this._elaborationChunks + " [" + this._executionStep + " / " + this._messageLength + "]");
//console.log("tuneExecutionParameters - elapsedTime: " + anElapsedTime + /*originalChunks,*/ " chunks # " + this._elaborationChunks + " [" + this._executionStep + " / " + this._messageLength + "]");
},
'pause': function(aValue) {
@@ -718,7 +757,8 @@ MochiKit.Base.update(Clipperz.Crypto.AES_2, {
self = Clipperz.Crypto.AES_2;
startTime = new Date();
blockSize = 128/8;
messageLength = anExecutionContext.messageArray().length;
// messageLength = anExecutionContext.messageArray().length;
messageLength = anExecutionContext.messageLength();
nonce = anExecutionContext.nonceArray();
result = anExecutionContext.resultArray();

View File

@@ -21,6 +21,8 @@ refer to http://www.clipperz.com.
*/
"use strict";
if (typeof(Clipperz) == 'undefined') { Clipperz = {}; }
if (typeof(Clipperz.PM) == 'undefined') { Clipperz.PM = {}; }
@@ -126,6 +128,16 @@ Clipperz.PM.Connection.prototype = MochiKit.Base.update(null, {
throw Clipperz.Base.exception.AbstractMethod;
},
//-------------------------------------------------------------------------
'uploadAttachment': function(someArguments, aProgressCallback) {
throw Clipperz.Base.exception.AbstractMethod;
},
'downloadAttachment': function(someArguments, aProgressCallback) {
throw Clipperz.Base.exception.AbstractMethod;
},
//=========================================================================
'sharedSecret': function () {
@@ -433,7 +445,7 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
//=========================================================================
'message': function(aMessageName, someParameters) {
'message': function(aMessageName, someParameters, someOptionalParameters) {
var args;
var parameters;
@@ -449,16 +461,16 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
parameters: parameters
}
return this.sendMessage(args);
return this.sendMessage(args, someOptionalParameters);
},
//-------------------------------------------------------------------------
'sendMessage': function(someArguments) {
'sendMessage': function(someArguments, someOptionalParameters) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("Connection.sendMessage", {trace:false});
deferredResult.addMethod(this.proxy(), 'message', someArguments);
deferredResult.addMethod(this.proxy(), 'message', someArguments, someOptionalParameters);
deferredResult.addCallback(MochiKit.Base.bind(function(res) {
if (typeof(res['lock']) != 'undefined') {
this.setServerLockValue(res['lock']);
@@ -498,6 +510,30 @@ Clipperz.log("<<< Connection.messageExceptionHandler")
//=========================================================================
// 'uploadAttachment': function(someArguments, aProgressCallback) {
// return this.message('uploadAttachment', someArguments, {'uploadProgressCallback': aProgressCallback});
// },
'uploadAttachment': function(someArguments, aProgressCallback) {
return Clipperz.Async.callbacks("Connction.uploadAttachment", [
MochiKit.Base.method(this, 'message', 'knock', {'requestType':'MESSAGE'}),
MochiKit.Base.method(this.proxy(), 'uploadAttachment', someArguments, aProgressCallback, this.sharedSecret()),
], {trace:false});
// return this.proxy().uploadAttachment(someArguments, aProgressCallback, this.sharedSecret());
},
'downloadAttachment': function(someArguments, aProgressCallback) {
return Clipperz.Async.callbacks("Connction.uploadAttachment", [
MochiKit.Base.method(this, 'message', 'knock', {'requestType':'MESSAGE'}),
MochiKit.Base.method(this.proxy(), 'downloadAttachment', someArguments, aProgressCallback, this.sharedSecret()),
], {trace:false});
// return this.proxy().downloadAttachment(someArguments, aProgressCallback, this.sharedSecret());
},
//=========================================================================
'reestablishConnection': function(anOriginalMessageArguments) {
var deferredResult;

View File

@@ -434,7 +434,7 @@ MochiKit.Base.update(Clipperz.PM.Crypto, {
deferredResult.addErrback(function(anError) {
Clipperz.logError("Error while decrypting data [4]");
throw Clipperz.Crypto.Base.exception.CorruptedMessage;
})
});
} else {
deferredResult.addCallback(function() {
return null;

View File

@@ -0,0 +1,374 @@
/*
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/.
*/
Clipperz.Base.module('Clipperz.PM.DataModel');
Clipperz.PM.DataModel.Attachment = function(args) {
args = args || {};
Clipperz.PM.DataModel.Attachment.superclass.constructor.apply(this, arguments);
this._reference = args.reference
|| Clipperz.PM.Crypto.randomKey();
this._record = args.record
|| Clipperz.Base.exception.raise('MandatoryParameter');
// this._retrieveIndexDataFunction = args.retrieveIndexDataFunction
// || this.record().retrieveAttachmentIndexDataFunction()
// || Clipperz.Base.exception.raise('MandatoryParameter');
// this._setIndexDataFunction = args.setIndexDataFunction
// || this.record().setAttachmentIndexDataFunction()
// || Clipperz.Base.exception.raise('MandatoryParameter');
// this._removeIndexDataFunction = args.removeIndexDataFunction
// || this.record().removeAttachmentIndexDataFunction()
// || Clipperz.Base.exception.raise('MandatoryParameter');
// this.setFile(args.file);
this._transientState = null;
this._isBrandNew = MochiKit.Base.isUndefinedOrNull(args.reference);
this.record().bindAttachment(this);
if (this._isBrandNew) {
this.setKey(Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(256/8));
this.setNonce(Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(128/8));
}
return this;
}
Clipperz.Base.extend(Clipperz.PM.DataModel.Attachment, Object, {
'toString': function() {
return "Attachment (" + this.reference() + ")";
},
//=========================================================================
'reference': function () {
return this._reference;
},
//-------------------------------------------------------------------------
'record': function () {
return this._record;
},
//=========================================================================
'isBrandNew': function () {
return this._isBrandNew;
},
//=========================================================================
'removeIndexDataFunction': function () {
return this._removeIndexDataFunction;
},
'remove': function () {
return Clipperz.Async.callbacks("DirectLogin.remove", [
MochiKit.Base.partial(this.removeIndexDataFunction(), this.reference()),
MochiKit.Base.method(this.record(), 'removeAttachment', this)
], {trace:false});
},
//=========================================================================
'file': function () {
// return this.getValue('name');
return MochiKit.Async.succeed(this._file);
},
'setFile': function (aFile) {
this._file = aFile || null;
/* These ones will disappear when the application is closed */
this._name = aFile ? aFile['name'] : null;
this._contentType = aFile ? aFile['type'] : null;
this._size = aFile ? aFile['size'] : null;
/* These ones will be saved in the Record */
return Clipperz.Async.callbacks("Attachment.setFile", [
MochiKit.Base.method(this, 'setValue', 'name', aFile['name']),
MochiKit.Base.method(this, 'setValue', 'contentType', aFile['type']),
MochiKit.Base.method(this, 'setValue', 'size', aFile['size']),
MochiKit.Base.partial(MochiKit.Async.succeed, this),
], {trace:false});
},
//-------------------------------------------------------------------------
'name': function () {
return this.getValue('name');
},
'contentType': function () {
return this.getValue('contentType');
},
'size': function () {
return this.getValue('size');
},
'metadata': function () {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("Attachment.metadata [collect results]", {trace:false});
deferredResult.collectResults({
'name': MochiKit.Base.method(this, 'name'),
'type': MochiKit.Base.method(this, 'contentType'),
'size': MochiKit.Base.method(this, 'size'),
}, {trace:false});
deferredResult.callback();
return deferredResult;
// return {
// 'name': this._name,
// 'type': this._type,
// 'size': this._size,
// }
},
//-------------------------------------------------------------------------
'key': function () {
var byteArray;
byteArray = new Clipperz.ByteArray();
return Clipperz.Async.callbacks("Attachment.key", [
MochiKit.Base.method(this, 'getValue', 'key'),
MochiKit.Base.method(byteArray, 'appendBase64String'),
function(aByteArray) { return new Uint8Array(aByteArray.arrayValues()); }
], {trace:false});
},
// 'key': function () {
// var result;
// result = new Clipperz.ByteArray();
// return Clipperz.Async.callbacks("Attachment.key", [
// MochiKit.Base.method(this, 'getValue', 'key'),
// MochiKit.Base.method(result, 'appendBase64String'),
// function(aByteArray) { return new Clipperz.Crypto.AES.Key({key: aByteArray}); }
// ], {trace:false});
// },
'nonce': function () {
var byteArray;
byteArray = new Clipperz.ByteArray();
return Clipperz.Async.callbacks("Attachment.nonce", [
MochiKit.Base.method(this, 'getValue', 'nonce'),
MochiKit.Base.method(byteArray, 'appendBase64String'),
function(aByteArray) { return new Uint8Array(aByteArray.arrayValues()); }
], {trace:false});
},
// 'nonce': function () {
// var result;
// result = new Clipperz.ByteArray();
// return Clipperz.Async.callbacks("Attachment.nonce", [
// MochiKit.Base.method(this, 'getValue', 'nonce'),
// MochiKit.Base.method(result, 'appendBase64String')
// ], {trace:false});
// },
'setKey': function (aByteArray) {
this.setValue('key', aByteArray.toBase64String());
},
// 'setKey': function (aKey) {
// var byteArray = aKey.key();
// var serializedData = byteArray.toBase64String();
// this.setValue('key', serializedData);
// },
'setNonce': function (aByteArray) {
this.setValue('nonce', aByteArray.toBase64String());
},
//=========================================================================
'serializedData': function () {
return Clipperz.Async.collectResults("Attachment.serializedData", {
'name': MochiKit.Base.method(this, 'name'),
'contentType': MochiKit.Base.method(this, 'contentType'),
'size': MochiKit.Base.method(this, 'size'),
}, {trace:false})()
},
//=========================================================================
'hasPendingChanges': function () {
var result;
// var deferredResult;
result = false;
result = result || this.isBrandNew();
return MochiKit.Async.succeed(result);
},
//-------------------------------------------------------------------------
'revertChanges': function () {
return MochiKit.Async.succeed();
},
//=========================================================================
'transientState': function () {
if (this._transientState == null) {
this._transientState = {}
}
return this._transientState;
},
'resetTransientState': function (isCommitting) {
this._transientState = null;
},
'commitTransientState': function (isCommitting) {
this._transientState = null;
this._isBrandNew = false;
},
//=========================================================================
'actualKey': function (aValueKey) {
var actualKey;
actualKey = 'attachments' + '.' + this.reference();
if (aValueKey != '') {
actualKey = actualKey + '.' + aValueKey;
}
return actualKey;
},
//-------------------------------------------------------------------------
'getValue': function (aValueKey) {
return this.record().getValue(this.actualKey(aValueKey));
},
'setValue': function (aValueKey, aValue) {
return Clipperz.Async.callbacks("Attachment.setValue", [
// MochiKit.Base.method(this, 'getValue', ''),
// MochiKit.Base.bind(function (aValue) {
// if (this.originalConfiguration() == null) {
// this.setOriginalConfiguration(aValue);
// }
// }, this),
MochiKit.Base.method(this.record(), 'setValue', this.actualKey(aValueKey), aValue)
], {trace:false});
},
'removeValue': function (aValueKey) {
return this.record().removeValue(this.actualKey(aValueKey));
},
//=========================================================================
'content': function () {
// return this.serializedData();
// return MochiKit.Async.succeed(this);
var deferredResult;
var fieldValues;
fieldValues = {};
deferredResult = new Clipperz.Async.Deferred("Attachment.content", {trace:false});
deferredResult.addMethod(this, 'reference');
deferredResult.addCallback(function (aValue) { fieldValues['reference'] = aValue; });
deferredResult.callback();
return deferredResult;
},
//=========================================================================
'deleteAllCleanTextData': function () {
this._name = null;
this._contentType = null;
this._size = null;
this.resetTransientState();
},
//-------------------------------------------------------------------------
'hasAnyCleanTextData': function () {
var result;
result = false;
result = result || (this._name != null);
result = result || (this._contentType != null);
result = result || (this._size != null);
result = result || (MochiKit.Base.keys(this.transientState()).length != 0);
return MochiKit.Async.succeed(result);
},
//=========================================================================
__syntaxFix__: "syntax fix"
});
Clipperz.PM.DataModel.Attachment.MAX_ATTACHMENT_SIZE = 50*1024*1024;
Clipperz.PM.DataModel.Attachment.contentTypeIcon = function (aContentType) {
var result;
result = 'other file';
if (aContentType == "application/pdf") {
result = 'pdf file';
} else if (aContentType.indexOf('image/') == 0) {
result = 'image file';
} else if (aContentType.indexOf('model/') == 0) {
result = 'other file';
} else if (aContentType.indexOf('audio/') == 0) {
result = 'audio file';
} else if (aContentType.indexOf('text/') == 0) {
result = 'text file';
} else if (aContentType.indexOf('video/') == 0) {
result = 'video file';
}
return result;
};

View File

@@ -55,7 +55,7 @@ Clipperz.PM.DataModel.DirectLogin = function(args) {
this._isBrandNew = MochiKit.Base.isUndefinedOrNull(args.reference);
this.record().addDirectLogin(this);
this.record().bindDirectLogin(this);
return this;
}
@@ -1018,7 +1018,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.DirectLogin, Object, {
'removeValue': function (aValueKey) {
// return this.record().removeValue(this.actualKey(aValueKey));
return Clipperz.Async.callbacks("DirectLogin.setValue", [
return Clipperz.Async.callbacks("DirectLogin.removeValue", [
MochiKit.Base.method(this, 'originalConfiguration'),
Clipperz.Async.deferredIf("originalConfiguration has been set", [
], [

View File

@@ -192,7 +192,7 @@ Clipperz.PM.DataModel.EncryptedRemoteObject.prototype = MochiKit.Base.update(nul
innerDeferredResult = MochiKit.Async.succeed(this._remoteData);
} else {
innerDeferredResult = Clipperz.Async.callbacks("EncryptedRemoteObjects.getRemoteData <inner deferred>", [
MochiKit.Base.partial(this.retrieveRemoteDataFunction(), this.reference()),
MochiKit.Base.partial(this.retrieveRemoteDataFunction(), this),
MochiKit.Base.method(this, 'setRemoteData'),
], {trace:false});
}
@@ -542,3 +542,19 @@ Clipperz.PM.DataModel.EncryptedRemoteObject.prototype = MochiKit.Base.update(nul
//-------------------------------------------------------------------------
__syntaxFix__: "syntax fix"
});
/*
Clipperz.PM.DataModel.EncryptedRemoteObject.emptyObjectWithKey = function (aKey) {
var key = "clipperz";
var value = new Clipperz.ByteArray();
var version = Clipperz.PM.Crypto.encryptingFunctions.currentVersion;
// var remoteData = Clipperz.PM.Crypto.encrypt({'key':key, 'value':value, 'version':version});
var remoteData = Clipperz.PM.Crypto.encryptingFunctions.versions[version].encrypt(key, value);
return new Clipperz.PM.DataModel.EncryptedRemoteObject({
'reference': Clipperz.PM.Crypto.randomKey(),
'remoteData': remoteData,
'retrieveKeyFunction': function () { return key; },
});
};
*/

View File

@@ -41,11 +41,18 @@ Clipperz.PM.DataModel.Record = function(args) {
this._retrieveDirectLoginIndexDataFunction = args.retrieveDirectLoginIndexDataFunction || null;
this._setDirectLoginIndexDataFunction = args.setDirectLoginIndexDataFunction || null;
this._removeDirectLoginIndexDataFunction = args.removeDirectLoginIndexDataFunction || null;
this._createNewDirectLoginFunction = args.createNewDirectLoginFunction || null;
this._retrieveAttachmentIndexDataFunction = args.retrieveAttachmentIndexDataFunction || null;
this._setAttachmentIndexDataFunction = args.setAttachmentIndexDataFunction || null;
this._removeAttachmentIndexDataFunction = args.removeAttachmentIndexDataFunction || null;
this._createNewAttachmentFunction = args.createNewAttachmentFunction || null;
this._attachmentServerStatus = {};
this._tags = [];
this._directLogins = {};
this._attachments = {};
this._versions = {};
this._currentRecordVersion = null;
@@ -132,6 +139,16 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
return deferredResult;
},
//----------------------------------------------------------------------------
setAttachmentServerStatus: function(anObject) {
this._attachmentServerStatus = anObject;
},
getAttachmentServerStatus: function() {
return this._attachmentServerStatus;
},
//============================================================================
/*
'key': function () {
@@ -283,6 +300,12 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
return MochiKit.Async.succeed(this._accessDate);
},
//=========================================================================
attachmentsCount: function() {
return MochiKit.Base.keys(this.attachments()).length;
},
//=========================================================================
'favicon': function () {
@@ -374,6 +397,13 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
deferredResult.addCallback(MochiKit.Base.map, function (aValue) { result['directLogins'].push(aValue); });
deferredResult.addCallback(function () { return result; });
deferredResult.addMethod(this, 'attachments');
deferredResult.addCallback(MochiKit.Base.values);
deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.methodcaller('content'));
deferredResult.addCallback(Clipperz.Async.collectAll);
deferredResult.addCallback(MochiKit.Base.map, function (aValue) { result['directLogins'].push(aValue); });
deferredResult.addCallback(function () { return result; });
deferredResult.callback();
return deferredResult;
@@ -385,7 +415,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
return this._directLogins;
},
'addDirectLogin': function (aDirectLogin) {
'bindDirectLogin': function (aDirectLogin) {
this._directLogins[aDirectLogin.reference()] = aDirectLogin;
},
@@ -400,12 +430,13 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
'saveOriginalDirectLoginStatusToTransientState': function () {
if (this.transientState().getValue('directLogins') == null) {
// this.transientState().setValue('directLogins', this._directLogins)
this.transientState().setValue('directLogins', {});
MochiKit.Iter.forEach(MochiKit.Base.keys(this._directLogins), MochiKit.Base.bind(function(aKey) {
this.transientState().setValue('directLogins' + '.' + aKey, this._directLogins[aKey])
}, this))
}
},
'createNewDirectLogin': function () {
this.saveOriginalDirectLoginStatusToTransientState();
@@ -453,6 +484,81 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
return result;
},
//=========================================================================
// TODO: !!!!
'attachments': function () {
return this._attachments;
},
'bindAttachment': function (anAttachment) {
this._attachments[anAttachment.reference()] = anAttachment;
},
'attachmentWithReference': function (anAttachmentReference) {
return this._attachments[anAttachmentReference];
},
'createNewAttachmentFunction': function () {
return this._createNewAttachmentFunction;
},
'saveOriginalAttachmentStatusToTransientState': function () {
if (this.transientState().getValue('attachments') == null) {
// console.log("saving attachments to transient state: ts:", this.transientState());
this.transientState().setValue('attachments', {});
MochiKit.Iter.forEach(MochiKit.Base.keys(this._attachments), MochiKit.Base.bind(function(aKey) {
// console.log("saving attachment to transient state:", this._attachments[aKey]);
this.transientState().setValue('attachments' + '.' + aKey, this._attachments[aKey])
}, this))
}
},
'createNewAttachment': function () {
this.saveOriginalAttachmentStatusToTransientState();
return this.createNewAttachmentFunction()(this);
},
'removeAttachment': function(anAttachment) {
this.saveOriginalAttachmentStatusToTransientState();
return Clipperz.Async.callbacks("Record.removeAttachment", [
MochiKit.Base.method(this, 'removeValue', 'attachments' + '.' + anAttachment.reference()),
MochiKit.Base.bind(function () {
this._removeAttachmentIndexDataFunction(anAttachment.reference());
delete this._attachments[anAttachment.reference()]
}, this)
], {trace:false});
},
'attachmentReferences': function () {
var result;
result = Clipperz.Async.callbacks("Record.attachmentReferences", [
MochiKit.Base.method(this, 'attachments'),
MochiKit.Base.values,
function (someAttachments) {
var result;
var i,c;
result = [];
c = someAttachments.length;
for (i=0; i<c; i++) {
result.push(Clipperz.Async.collectResults("Record.attachmentReferences - collectResults", {
'_rowObject': MochiKit.Async.succeed,
'_reference': MochiKit.Base.methodcaller('reference'),
// 'label': MochiKit.Base.methodcaller('label'),
// 'favicon': MochiKit.Base.methodcaller('favicon')
}, {trace:false})(someAttachments[i]));
};
return result;
},
Clipperz.Async.collectAll
], {trace:false});
return result;
},
//=========================================================================
'unpackRemoteData': function (someData) {
@@ -508,6 +614,8 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
newVersionKey = Clipperz.PM.Crypto.randomKey();
result = {};
result['attachments'] = MochiKit.Base.keys(this.attachments());
deferredResult = new Clipperz.Async.Deferred("Record.prepareRemoteDataWithKey", {trace:false});
deferredResult.addCallbackList([
Clipperz.Async.collectResults("Record.prepareRemoteDataWithKey - collect results", {
@@ -581,7 +689,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
result = someFilteredResults[0];
break;
default:
Clipperz.log("Warning: Record.fieldWithLabel('" + aLabel + "') is returning more than one result: " + someFilteredResults.length);
//console.log("Warning: Record.fieldWithLabel('" + aLabel + "') is returning more than one result: " + someFilteredResults.length);
result = someFilteredResults[0];
break;
}
@@ -607,7 +715,7 @@ Clipperz.log("Warning: Record.fieldWithLabel('" + aLabel + "') is returning more
var transientStateKey;
if (typeof(aVersionReference) == 'undefined') {
Clipperz.log("ERROR; getVersionKey aVersionReference is undefined");
console.log("ERROR; getVersionKey aVersionReference is undefined");
}
transientStateKey = 'versionKeys' + '.' + aVersionReference;
@@ -757,60 +865,6 @@ Clipperz.log("Warning: Record.fieldWithLabel('" + aLabel + "') is returning more
},
//=========================================================================
/*
'hasPendingChanges': function () {
var deferredResult;
if (this.hasInitiatedObjectDataStore()) {
deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.DataModel.Record.hasPendingChanges", {trace:false});
deferredResult.collectResults({
'super': MochiKit.Base.bind(Clipperz.PM.DataModel.Record.superclass.hasPendingChanges, this),
'currentVersion': [
// MochiKit.Base.method(this, 'getCurrentRecordVersion'),
// MochiKit.Base.methodcaller('hasPendingChanges')
MochiKit.Base.method(this, 'invokeCurrentRecordVersionMethod', 'hasPendingChanges')
],
'directLogins': [
MochiKit.Base.method(this, 'directLogins'),
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('hasPendingChanges')),
Clipperz.Async.collectAll,
Clipperz.Async.or
// function(someValues) {
// return MochiKit.Iter.some(someValues, MochiKit.Base.operator.identity);
// }
]
});
deferredResult.addCallback(function (aValue) { console.log("Record.hasPendingChanges", aValue); return aValue; });
deferredResult.addCallback(MochiKit.Base.values);
deferredResult.addCallback(MochiKit.Base.bind(function(someValues) {
var result;
result = MochiKit.Iter.some(someValues, MochiKit.Base.operator.identity);
if ((result == false) && (this.isBrandNew() == false)) {
result = MochiKit.Iter.some(MochiKit.Base.values(this.transientState().getValue('hasPendingChanges.indexData')), MochiKit.Base.operator.identity);
}
return result;
}, this));
deferredResult.callback();
} else {
deferredResult = Clipperz.Async.callbacks("Recrod.hasPendingChanges [hasInitiatedObjectDataStore == false]", [
MochiKit.Base.method(this, 'directLogins'),
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('hasPendingChanges')),
Clipperz.Async.collectAll,
Clipperz.Async.or
// function(someValues) {
// return MochiKit.Iter.some(someValues, MochiKit.Base.operator.identity);
// }
], {trace:false})
}
return deferredResult;
},
*/
'hasPendingChanges': function () {
var deferredResult;
@@ -836,27 +890,18 @@ deferredResult.addCallback(function (aValue) { console.log("Record.hasPendingCha
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('hasPendingChanges')),
Clipperz.Async.collectAll,
Clipperz.Async.or
],
'attachments': [
MochiKit.Base.method(this, 'attachments'),
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('hasPendingChanges')),
Clipperz.Async.collectAll,
Clipperz.Async.or
]
});
//deferredResult.addCallback(function (someValues) {
// if (recordReference == 'd620764a656bfd4e1d3758500d5db72e460a0cf729d56ed1a7755b5725c50045') {
// console.log("Record.hasPendingChanges VALUES", someValues);
// }
// return someValues;
//})
}, {trace:true});
deferredResult.addCallback(MochiKit.Base.values);
deferredResult.addCallback(MochiKit.Base.bind(function(someValues) {
var result;
result = MochiKit.Iter.some(someValues, MochiKit.Base.operator.identity);
/*
if ((result == false) && (this.isBrandNew() == false)) {
console.log("TRANSIENT STATE", this.transientState());
console.log("TRANSIENT STATE - hasPendingChanges", this.transientState().getValue('hasPendingChanges.indexData'));
result = MochiKit.Iter.some(MochiKit.Base.values(this.transientState().getValue('hasPendingChanges.indexData')), MochiKit.Base.operator.identity);
}
console.log("Record.hasPendingChanges RESULT", result);
*/
return result;
deferredResult.addCallback(MochiKit.Base.bind(function (someValues) {
return MochiKit.Iter.some(someValues, MochiKit.Base.operator.identity);
}, this));
deferredResult.callback();
@@ -911,6 +956,8 @@ console.log("Record.hasPendingChanges RESULT", result);
//=========================================================================
'revertChanges': function () {
// console.log("Revert changes: attachments in transient state", this.transientState().getValue('attachments'));
var deferredResult;
// var recordReference = this.reference();
@@ -926,19 +973,19 @@ console.log("Record.hasPendingChanges RESULT", result);
// return true;
//});
deferredResult.addIf([
//function (aValue) { console.log("Record.revertChanges - 1"); return aValue; },
MochiKit.Base.method(this, 'cancelRevertedAttachmentsUpload'),
MochiKit.Base.method(this, 'invokeCurrentRecordVersionMethod', 'revertChanges'),
//function (aValue) { console.log("Record.revertChanges - 2"); return aValue; },
MochiKit.Base.method(this, 'directLogins'),
//function (aValue) { console.log("Record.revertChanges - 3"); return aValue; },
MochiKit.Base.values,
//function (aValue) { console.log("Record.revertChanges - 4"); return aValue; },
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('revertChanges')),
//function (aValue) { console.log("Record.revertChanges - 5"); return aValue; },
MochiKit.Base.method(this, 'attachments'),
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('revertChanges')),
MochiKit.Base.bind(Clipperz.PM.DataModel.Record.superclass.revertChanges, this),
//function (aValue) { console.log("Record.revertChanges - 6"); return aValue; },
MochiKit.Base.method(this, '_getObjectDataStore'),
], [
MochiKit.Async.succeed
@@ -962,6 +1009,19 @@ console.log("Record.hasPendingChanges RESULT", result);
return deferredResult;
},
cancelRevertedAttachmentsUpload: function() {
var reference;
var transientStateAttachments = (this.transientState() ? this.transientState().values()['attachments'] : {}) || {};
var savedReferences = MochiKit.Base.keys(transientStateAttachments);
for (reference in this.attachments()) {
if (savedReferences.indexOf(reference) < 0) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'cancelAttachment', this.attachments()[reference]);
}
}
},
//-------------------------------------------------------------------------
'resetTransientState': function (isCommitting) {
@@ -969,6 +1029,8 @@ console.log("Record.hasPendingChanges RESULT", result);
// this._directLogins = this.transientState().getValue('directLogins');
// }
// console.log("Reset transient state: attachments:", this.transientState().getValue('directLogins'));
return Clipperz.Async.callbacks("Record.resetTransientState", [
//- MochiKit.Base.method(this, 'getCurrentRecordVersion'),
//- MochiKit.Base.methodcaller('resetTransientState'),
@@ -979,9 +1041,18 @@ console.log("Record.hasPendingChanges RESULT", result);
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('resetTransientState')),
MochiKit.Base.method(this, 'attachments'),
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('resetTransientState')),
MochiKit.Base.bind(function () {
if ((isCommitting == false) && (this.transientState().getValue('directLogins') != null)) {
this._directLogins = this.transientState().getValue('directLogins');
if (isCommitting == false) {
if (this.transientState().getValue('directLogins') != null) {
this._directLogins = this.transientState().getValue('directLogins');
}
if (this.transientState().getValue('attachments') != null) {
this._attachments = this.transientState().getValue('attachments');
}
}
}, this),
@@ -1003,6 +1074,10 @@ console.log("Record.hasPendingChanges RESULT", result);
MochiKit.Base.method(this, 'invokeCurrentRecordVersionMethod', 'commitTransientState'),
MochiKit.Base.method(this, 'directLogins'),
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('commitTransientState')),
MochiKit.Base.method(this, 'attachments'),
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('commitTransientState'))
], [
MochiKit.Async.succeed
@@ -1028,6 +1103,20 @@ console.log("Record.hasPendingChanges RESULT", result);
//=========================================================================
'retrieveAttachmentIndexDataFunction': function () {
return this._retrieveAttachmentIndexDataFunction;
},
'setAttachmentIndexDataFunction': function () {
return this._setAttachmentIndexDataFunction;
},
'removeAttachmentIndexDataFunction': function () {
return this._removeAttachmentIndexDataFunction;
},
//=========================================================================
'deleteAllCleanTextData': function () {
// return Clipperz.PM.DataModel.Record.superclass.deleteAllCleanTextData.apply(this, arguments);
@@ -1040,6 +1129,10 @@ console.log("Record.hasPendingChanges RESULT", result);
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('deleteAllCleanTextData')),
MochiKit.Base.method(this, 'attachments'),
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('deleteAllCleanTextData')),
MochiKit.Base.bind(Clipperz.PM.DataModel.Record.superclass.deleteAllCleanTextData, this)
], {trace:false});
},
@@ -1061,6 +1154,12 @@ console.log("Record.hasPendingChanges RESULT", result);
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('hasAnyCleanTextData')),
Clipperz.Async.collectAll
],
'attachments': [
MochiKit.Base.method(this, 'attachments'),
MochiKit.Base.values,
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('hasAnyCleanTextData')),
Clipperz.Async.collectAll
],
'super': [
MochiKit.Base.bind(Clipperz.PM.DataModel.Record.superclass.hasAnyCleanTextData, this)
]
@@ -1131,10 +1230,12 @@ console.log("Record.hasPendingChanges RESULT", result);
MochiKit.Base.method(aRecord, 'directLogins'), MochiKit.Base.values,
//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')),
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'bindDirectLogin')),
//function (aValue) { console.log("-> DirectLogin Values", aValue); return aValue; },
// Clipperz.Async.collectAll,
// TODO: attachments
MochiKit.Base.bind(function () { return this; }, this)
], {trace:false});
},
@@ -1230,7 +1331,8 @@ Clipperz.PM.DataModel.Record.defaultCardInfo = {
'_isArchived': MochiKit.Base.methodcaller('isArchived'),
'_isBrandNew': MochiKit.Base.methodcaller('isBrandNew'),
'label': MochiKit.Base.methodcaller('label'),
'favicon': MochiKit.Base.methodcaller('favicon')
'favicon': MochiKit.Base.methodcaller('favicon'),
'attachmentsCount': MochiKit.Base.methodcaller('attachmentsCount'),
};
Clipperz.PM.DataModel.Record.defaultSearchField = '_searchableContent';

View File

@@ -23,7 +23,7 @@ refer to http://www.clipperz.com.
try { if (typeof(Clipperz.PM.DataModel.User) == 'undefined') { throw ""; }} catch (e) {
throw "Clipperz.PM.DataModel.User.Header.RecordIndex depends on Clipperz.PM.DataModel.User!";
}
}
if (typeof(Clipperz.PM.DataModel.User.Header) == 'undefined') { Clipperz.PM.DataModel.User.Header = {}; }
@@ -49,13 +49,25 @@ Clipperz.PM.DataModel.User.Header.RecordIndex = function(args) {
}
});
//console.log("RECORD INDEX args", args);
this._attachmentsData = new Clipperz.PM.DataModel.EncryptedRemoteObject({
'name': 'attachmentsData',
'retrieveKeyFunction': args.retrieveKeyFunction,
'remoteData': {
'data': args.attachmentsData['data'],
'version': args.encryptedDataVersion,
}
});
this._tagsData =
this._lock = new MochiKit.Async.DeferredLock();
this._transientState = null;
this._retrieveRecordDetailFunction = args.retrieveRecordDetailFunction || Clipperz.Base.exception.raise('MandatoryParameter');
this._recordsIndex = args.recordsData['index'] || Clipperz.Base.exception.raise('MandatoryParameter');
this._directLoginsIndex = args.directLoginsData['index'] || Clipperz.Base.exception.raise('MandatoryParameter');
this._attachmentsIndex = args.attachmentsData['index'] || Clipperz.Base.exception.raise('MandatoryParameter');
this._records = null;
@@ -97,6 +109,16 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.RecordIndex, Object, {
//-------------------------------------------------------------------------
'attachmentsIndex': function () {
return this._attachmentsIndex;
},
'attachmentsData': function () {
return this._attachmentsData;
},
//-------------------------------------------------------------------------
'lock': function () {
return this._lock;
},
@@ -164,6 +186,28 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.RecordIndex, Object, {
//-------------------------------------------------------------------------
'getAttachmentIndexData': function (anAttachmentReference) {
// TODO:
return this.attachmentsData().getValue(this.attachmentsIndex()[anAttachmentReference]);
},
'setAttachmentIndexData': function (anAttachmentReference, aKey, aValue) {
// TODO:
return this.attachmentsData().setValue(this.attachmentsIndex()[anAttachmentReference] + '.' + aKey, aValue);
},
'addAttachmentIndexData': function (anAttachmentReference) {
// TODO:
return this.attachmentsData().setValue(this.attachmentsIndex()[anAttachmentReference], {});
},
'removeAttachmentIndexData': function (anAttachmentReference) {
// TODO:
return this.attachmentsData().removeValue(this.attachmentsIndex()[anAttachmentReference])
},
//-------------------------------------------------------------------------
'records': function () {
var deferredResult;
@@ -184,6 +228,9 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.RecordIndex, Object, {
],
'directLogins': [
MochiKit.Base.method(this.directLoginsData(), 'values')
],
'attachments': [
MochiKit.Base.method(this.attachmentsData(), 'values')
]
})
innerDeferredResult.addCallback(MochiKit.Base.bind(function (someData) {
@@ -193,6 +240,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.RecordIndex, Object, {
recordsInvertedIndex = Clipperz.PM.DataModel.User.Header.RecordIndex.invertIndex(this.recordsIndex());
directLoginsInvertedIndex = Clipperz.PM.DataModel.User.Header.RecordIndex.invertIndex(this.directLoginsIndex());
attachmentsInvertedIndex = Clipperz.PM.DataModel.User.Header.RecordIndex.invertIndex(this.attachmentsIndex());
this._records = {};
@@ -201,13 +249,15 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.RecordIndex, Object, {
var reference;
var updateDate;
var accessDate;
var attachmentsCount;
reference = recordsInvertedIndex[indexReference];
if (typeof(someData['recordsStats'][reference]) != 'undefined') {
updateDate = someData['recordsStats'][reference]['updateDate'];
accessDate = someData['recordsStats'][reference]['accessDate'];
// attachmentsCount = (someData['attachmentsCount'][reference]) ? someData['attachmentsCount'][reference] : 0;
record = new Clipperz.PM.DataModel.Record({
'reference': reference,
'retrieveKeyFunction': MochiKit.Base.method(this, 'getRecordKey'),
@@ -221,8 +271,12 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.RecordIndex, Object, {
'retrieveDirectLoginIndexDataFunction': MochiKit.Base.method(this, 'getDirectLoginIndexData'),
'setDirectLoginIndexDataFunction': MochiKit.Base.method(this, 'setDirectLoginIndexData'),
'removeDirectLoginIndexDataFunction': MochiKit.Base.method(this, 'removeDirectLoginIndexData'),
'createNewDirectLoginFunction': MochiKit.Base.method(this, 'createNewDirectLogin')
'createNewDirectLoginFunction': MochiKit.Base.method(this, 'createNewDirectLogin'),
'retrieveAttachmentIndexDataFunction': MochiKit.Base.method(this, 'getAttachmentIndexData'),
'setAttachmentIndexDataFunction': MochiKit.Base.method(this, 'setAttachmentIndexData'),
'removeAttachmentIndexDataFunction': MochiKit.Base.method(this, 'removeAttachmentIndexData'),
'createNewAttachmentFunction': MochiKit.Base.method(this, 'createNewAttachment'),
});
this._records[reference] = record;
@@ -248,6 +302,24 @@ Clipperz.log("SKIPPING record " + reference + " as there are no stats associated
}
}
//console.log("RecordIndex.Records: attachments data:", someData['attachments']);
for (indexReference in someData['attachments']) {
var reference;
var record;
reference = attachmentsInvertedIndex[indexReference];
record = this._records[recordsInvertedIndex[someData['attachments'][indexReference]['record']]];
if (record != null) {
new Clipperz.PM.DataModel.Attachment({
'reference': reference,
'record': record
});
} else {
Clipperz.logWarning("WARNING: Attachment without a matching RECORD!!");
}
}
return this._records;
}, this));
innerDeferredResult.callback();
@@ -295,8 +367,12 @@ Clipperz.log("SKIPPING record " + reference + " as there are no stats associated
'retrieveDirectLoginIndexDataFunction': MochiKit.Base.method(this, 'getDirectLoginIndexData'),
'setDirectLoginIndexDataFunction': MochiKit.Base.method(this, 'setDirectLoginIndexData'),
'removeDirectLoginIndexDataFunction': MochiKit.Base.method(this, 'removeDirectLoginIndexData'),
'createNewDirectLoginFunction': MochiKit.Base.method(this, 'createNewDirectLogin'),
'createNewDirectLoginFunction': MochiKit.Base.method(this, 'createNewDirectLogin')
'retrieveAttachmentIndexDataFunction': MochiKit.Base.method(this, 'getAttachmentIndexData'),
'setAttachmentIndexDataFunction': MochiKit.Base.method(this, 'setAttachmentIndexData'),
'removeAttachmentIndexDataFunction': MochiKit.Base.method(this, 'removeAttachmentIndexData'),
'createNewAttachmentFunction': MochiKit.Base.method(this, 'createNewAttachment'),
});
this.transientState().setValue('newRecordsReferences' + '.' + newRecord.reference(), newRecord);
@@ -372,6 +448,40 @@ Clipperz.log("SKIPPING record " + reference + " as there are no stats associated
//=========================================================================
'removeAttachment': function (anAttachment) {
// TODO:
// this.directLoginsData().removeValue(this.directLoginsIndex()[aDirectLogin.reference()]);
},
//-------------------------------------------------------------------------
'createNewAttachment': function (aRecord) {
// TODO:
// var newDirectLogin;
var newAttachment;
// var newDirectLoginIndexValue;
var newAttachmentIndexValue;
// newDirectLogin = new Clipperz.PM.DataModel.DirectLogin({record:aRecord});
newAttachment = new Clipperz.PM.DataModel.Attachment({'record':aRecord});
// newDirectLoginIndexValue = MochiKit.Base.listMax(MochiKit.Base.map(function (aValue) { return aValue * 1; }, MochiKit.Base.values(this.directLoginsIndex()))) + 1;
newAttachmentIndexValue = MochiKit.Base.listMax(MochiKit.Base.map(function (aValue) { return aValue * 1; }, MochiKit.Base.values(this.attachmentsIndex()))) + 1;
// this.transientState().setValue('newDirectLoginReferences' + '.' + newDirectLogin.reference(), newDirectLogin);
this.transientState().setValue('newAttachmentReferences' + '.' + newAttachment.reference(), newAttachment);
// this.directLoginsIndex()[newDirectLogin.reference()] = newDirectLoginIndexValue;
this.attachmentsIndex()[newAttachment.reference()] = newAttachmentIndexValue;
// this.directLoginsData().setValue(this.directLoginsIndex()[newDirectLogin.reference()], {'record': this.recordsIndex()[aRecord.reference()]});
this.attachmentsData().setValue(this.attachmentsIndex()[newAttachment.reference()], {'record': this.recordsIndex()[aRecord.reference()]});
// return newDirectLogin;
return newAttachment;
},
//=========================================================================
'deleteAllCleanTextData': function () {
return Clipperz.Async.callbacks("User.Header.RecordIndex.deleteAllCleanTextData", [
MochiKit.Base.method(this, 'recordsData'),
@@ -396,6 +506,10 @@ Clipperz.log("SKIPPING record " + reference + " as there are no stats associated
MochiKit.Base.method(this, 'directLoginsData'),
MochiKit.Base.methodcaller('hasAnyCleanTextData')
],
'attachmentsData': [
MochiKit.Base.method(this, 'attachmentsData'),
MochiKit.Base.methodcaller('hasAnyCleanTextData')
],
});
deferredResult.addCallback(Clipperz.Async.or);
@@ -418,8 +532,13 @@ Clipperz.log("SKIPPING record " + reference + " as there are no stats associated
'directLoginsData': [
MochiKit.Base.method(this, 'directLoginsData'),
MochiKit.Base.methodcaller('hasPendingChanges')
],
'attachments': [
MochiKit.Base.method(this, 'attachmentsData'),
MochiKit.Base.methodcaller('hasPendingChanges')
]
});
//deferredResult.addCallback(function (aValue) { console.log("HAS PENDING CHANGES", aValue); return aValue; });
deferredResult.addCallback(Clipperz.Async.or);
deferredResult.callback();
@@ -437,6 +556,9 @@ Clipperz.log("SKIPPING record " + reference + " as there are no stats associated
MochiKit.Base.method(this, 'directLoginsData'),
MochiKit.Base.methodcaller('commitTransientState'),
MochiKit.Base.method(this, 'attachmentsData'),
MochiKit.Base.methodcaller('commitTransientState'),
MochiKit.Base.method(this, 'resetTransientState', true)
], {trace:false});
@@ -474,9 +596,21 @@ Clipperz.log("SKIPPING record " + reference + " as there are no stats associated
}
}, this),
MochiKit.Base.bind(function () {
// TODO:
var attachmentReference;
for (attachmentReference in this.transientState().getValue('newAttachmentReferences')) {
delete this.attachmentsIndex()[attachmentReference];
}
}, this),
MochiKit.Base.method(this, 'directLoginsData'),
MochiKit.Base.methodcaller('revertChanges'),
MochiKit.Base.method(this, 'attachmentsData'),
MochiKit.Base.methodcaller('revertChanges'),
MochiKit.Base.method(this, 'resetTransientState', false)
], {trace:false});
},
@@ -528,6 +662,15 @@ Clipperz.log("SKIPPING record " + reference + " as there are no stats associated
]
});
deferredResult.addCallback(Clipperz.Async.setItem, result, 'directLogins');
deferredResult.collectResults({
'index': MochiKit.Base.partial(MochiKit.Async.succeed, this.attachmentsIndex()),
'data': [
MochiKit.Base.method(this.attachmentsData(), 'prepareRemoteDataWithKey', aKey),
MochiKit.Base.itemgetter('data')
]
});
deferredResult.addCallback(Clipperz.Async.setItem, result, 'attachments');
deferredResult.addCallback(MochiKit.Async.succeed, result);

View File

@@ -254,6 +254,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
'recordsData': {'data':null, 'index':{}},
'recordsStats': null,
'directLoginsData': {'data':null, 'index':{}},
'attachmentsData': {'data':null, 'index':{}},
'encryptedDataVersion': Clipperz.PM.Crypto.encryptingFunctions.currentVersion,
'retrieveRecordDetailFunction': MochiKit.Base.method(this, 'getRecordDetail')
}),
@@ -465,13 +466,16 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
case '0.1':
var headerData;
//console.log("Server data", someServerData);
headerData = Clipperz.Base.evalJSON(someServerData['header']);
//console.log("headerData", headerData);
recordsIndex = new Clipperz.PM.DataModel.User.Header.RecordIndex({
'retrieveKeyFunction': MochiKit.Base.method(this, 'getPassphrase'),
'recordsData': headerData['records'],
'recordsStats': someServerData['recordsStats'],
'directLoginsData': headerData['directLogins'],
'attachmentsData': headerData['attachments'] || {'data': null, 'index':{}},
'encryptedDataVersion': someServerData['version'],
'retrieveRecordDetailFunction': MochiKit.Base.method(this, 'getRecordDetail')
});
@@ -695,7 +699,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
result = someFilteredResults[0];
break;
default:
Clipperz.log("Warning: User.recordWithLabel('" + aLabel + "') is returning more than one result: " + someFilteredResults.length);
//console.log("Warning: User.recordWithLabel('" + aLabel + "') is returning more than one result: " + someFilteredResults.length);
result = someFilteredResults[0];
break;
}
@@ -723,8 +727,13 @@ Clipperz.log("Warning: User.recordWithLabel('" + aLabel + "') is returning more
//-------------------------------------------------------------------------
'getRecordDetail': function (aRecordReference) {
return this.connection().message('getRecordDetail', {reference: aRecordReference});
'getRecordDetail': function (aRecord) {
// return this.connection().message('getRecordDetail', {reference: aRecordReference});
return Clipperz.Async.callbacks("User.getRecordDetail", [
MochiKit.Base.method(this.connection(), 'message', 'getRecordDetail', {reference: aRecord.reference()}),
function(someInfo) { aRecord.setAttachmentServerStatus(someInfo['attachmentStatus']); return someInfo;}, // Couldn't find a better way...
], {trace:false});
},
//-------------------------------------------------------------------------
@@ -750,6 +759,7 @@ Clipperz.log("Warning: User.recordWithLabel('" + aLabel + "') is returning more
'createNewRecordFromJSON': function(someJSON) {
var deferredResult;
// TODO: how do we handle attachments?
deferredResult = new Clipperz.Async.Deferred("User.createNewRecordFromJSON", {trace:false});
deferredResult.collectResults({
'recordIndex': MochiKit.Base.method(this, 'getHeaderIndex', 'recordsIndex'),
@@ -770,6 +780,7 @@ Clipperz.log("Warning: User.recordWithLabel('" + aLabel + "') is returning more
});
return Clipperz.Async.callbacks("User.createNewRecordFromJSON__inner", [
// TODO: check if we should invoke the create new direct login methon on Record instead of calling it directly on the index
MochiKit.Base.method(recordIndex, 'createNewDirectLogin', record),
MochiKit.Base.methodcaller('setLabel', aDirectLogin['label']),
MochiKit.Base.methodcaller('setBookmarkletConfiguration', configuration),
@@ -937,6 +948,22 @@ Clipperz.log("Warning: User.recordWithLabel('" + aLabel + "') is returning more
], {trace:false});
},
//-------------------------------------------------------------------------
/*
'addNewAttachment': function(anAttachment) {
console.log("Adding attachment", anAttachment);
},
*/
'uploadAttachment': function(aRecordReference, anAttachmentReference, someData) {
// TODO: pass a callback to handle onProgress events (and modify Connection accordingly)
this.connection().message('uploadAttachment', {
'recordReference': aRecordReference,
'attachmentReference': anAttachmentReference,
'data': someData
});
},
//=========================================================================
'hasPendingChanges': function () {
@@ -1035,6 +1062,7 @@ Clipperz.log("Warning: User.recordWithLabel('" + aLabel + "') is returning more
header = {};
header['records'] = someHeaderPackedData['recordIndex']['records'];
header['directLogins'] = someHeaderPackedData['recordIndex']['directLogins'];
header['attachments'] = someHeaderPackedData['recordIndex']['attachments'];
header['preferences'] = {'data': someHeaderPackedData['preferences']['data']};
header['oneTimePasswords'] = {'data': someHeaderPackedData['oneTimePasswords']['data']};
header['version'] = '0.1';

View File

@@ -95,6 +95,27 @@ Clipperz.PM.Proxy.prototype = MochiKit.Base.update(null, {
return deferredResult;
},
'justPayToll': function(aRequestType) {
var deferredResult;
if (this.shouldPayTolls()) {
deferredResult = new Clipperz.Async.Deferred("Proxy.justPayToll", {trace:false});
if (this.tolls()[aRequestType].length == 0) {
deferredResult.addMethod(this, 'sendMessage', 'knock', {requestType:aRequestType});
deferredResult.addMethod(this, 'setTollCallback');
}
deferredResult.addMethod(this.tolls()[aRequestType], 'pop');
deferredResult.addCallback(MochiKit.Base.methodcaller('deferredPay'));
deferredResult.callback();
} else {
deferredResult = MochiKit.Async.succeed(null);
}
return deferredResult;
},
//-------------------------------------------------------------------------
'addToll': function(aToll) {
@@ -108,7 +129,7 @@ Clipperz.PM.Proxy.prototype = MochiKit.Base.update(null, {
this.addToll(new Clipperz.PM.Toll(someParameters['toll']));
}
return someParameters['result'];
return someParameters['result']; // <- This is what will be returned by the message call
},
//=========================================================================
@@ -121,8 +142,8 @@ Clipperz.PM.Proxy.prototype = MochiKit.Base.update(null, {
return this.processMessage('handshake', someParameters, 'CONNECT');
},
'message': function (someParameters) {
return this.processMessage('message', someParameters, 'MESSAGE');
'message': function (someParameters, someOptionalParameters) {
return this.processMessage('message', someParameters, 'MESSAGE', someOptionalParameters);
},
'logout': function (someParameters) {
@@ -145,33 +166,65 @@ Clipperz.PM.Proxy.prototype = MochiKit.Base.update(null, {
//=========================================================================
'processMessage': function (aFunctionName, someParameters, aRequestType) {
'processMessage': function (aFunctionName, someParameters, aRequestType, someOptionalParameters) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("Proxy.processMessage", {trace:false});
deferredResult.addMethod(this, 'payToll', aRequestType);
deferredResult.addMethod(this, 'sendMessage', aFunctionName);
// deferredResult.addMethod(this, 'sendMessage', aFunctionName);
deferredResult.addCallback(MochiKit.Base.bind(function(aResult){
return this.sendMessage(aFunctionName, aResult, someOptionalParameters);
}, this));
deferredResult.addMethod(this, 'setTollCallback');
deferredResult.callback(someParameters);
return deferredResult;
},
'processAttachmentMessage': function(aMessageCallback) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("Proxy.uploadAttachment", {trace:false});
deferredResult.addMethod(this, 'justPayToll', 'MESSAGE');
deferredResult.addCallback(aMessageCallback);
deferredResult.addErrback(MochiKit.Base.method(this, 'handleError'));
deferredResult.addMethod(this, 'setTollCallback');
deferredResult.callback();
return deferredResult;
},
'uploadAttachment': function(someArguments, aProgressCallback, aSharedSecret) {
var messageCallback = MochiKit.Base.method(this, '_uploadAttachment', someArguments, aProgressCallback, aSharedSecret);
return this.processAttachmentMessage(messageCallback);
},
'downloadAttachment': function(someArguments, aProgressCallback, aSharedSecret) {
var messageCallback = MochiKit.Base.method(this, '_downloadAttachment', someArguments, aProgressCallback, aSharedSecret);
return this.processAttachmentMessage(messageCallback);
},
//=========================================================================
'_sendMessage': function (aFunctionName, aVersion, someParameters) {
'_uploadAttachment': function (someArguments, aProgressCallback, aSharedSecret) {
throw Clipperz.Base.exception.AbstractMethod;
},
'sendMessage': function (aFunctionName, someParameters) {
'_sendMessage': function (aFunctionName, aVersion, someParameters, someOptionalParameters) {
throw Clipperz.Base.exception.AbstractMethod;
},
'sendMessage': function (aFunctionName, someParameters, someOptionalParameters) {
var deferredResult;
//console.log("PROXY.sendMessage", aFunctionName, someParameters);
// TODO: read actual application version for a property set at build time
deferredResult = new Clipperz.Async.Deferred("Proxy.sendMessage", {trace:false});
deferredResult.addMethod(this, '_sendMessage', aFunctionName, 'fake-app-version');
deferredResult.addMethod(this, '_sendMessage', aFunctionName, 'fake-app-version', someParameters, someOptionalParameters);
deferredResult.addErrback(MochiKit.Base.method(this, 'handleError'));
deferredResult.callback(someParameters);
deferredResult.callback();
return deferredResult;
},

View File

@@ -62,7 +62,7 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.JSON, Clipperz.PM.Proxy, {
//=========================================================================
'_sendMessage': function(aFunctionName, aVersion, someParameters) {
'_sendMessage': function(aFunctionName, aVersion, someParameters, someOptionalParameters) {
var deferredResult;
var parameters;
@@ -71,13 +71,19 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.JSON, Clipperz.PM.Proxy, {
version: aVersion,
parameters: Clipperz.Base.serializeJSON(someParameters)
};
someOptionalParameters = someOptionalParameters || {};
//console.log("PROXY.JSON._sendMessage", parameters);
deferredResult = new Clipperz.Async.Deferred("Proxy.JSON._sendMessage", {trace:false});
deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'remoteRequestSent');
deferredResult.addCallback(MochiKit.Async.doXHR, this.url(), {
deferredResult.addCallback(Clipperz.Async.doXHR, this.url(), {
// deferredResult.addCallback(MochiKit.Async.doXHR, this.url(), {
method:'POST',
sendContent:MochiKit.Base.queryString(parameters),
headers:{"Content-Type":"application/x-www-form-urlencoded"}
headers:{"Content-Type":"application/x-www-form-urlencoded"},
// downloadProgress:someOptionalParameters['downloadProgressCallback'] || null,
// uploadProgress:someOptionalParameters['uploadProgressCallback'] || null,
});
deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'remoteRequestReceived');
deferredResult.addCallback(MochiKit.Base.itemgetter('responseText'));
@@ -94,6 +100,114 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.JSON, Clipperz.PM.Proxy, {
return deferredResult;
},
//-------------------------------------------------------------------------
// TODO: test
'_uploadAttachment': function(someArguments, aProgressCallback, aSharedSecret, aToll) {
var formData;
var deferredResult;
var parameters = {
'toll': aToll,
'parameters': {
'message': 'uploadAttachment',
'srpSharedSecret': aSharedSecret,
'parameters': {
'attachmentReference': someArguments['attachmentReference'],
'recordReference': someArguments['recordReference'],
'version': someArguments['version']
}
}
}
var blob = new Blob([someArguments['arrayBufferData']]);
formData = new FormData();
formData.append('method', 'message');
formData.append('version', 'fake-app-version'); // Not implemented anywhere yet
formData.append('parameters', JSON.stringify(parameters));
formData.append('data', blob);
deferredResult = new Clipperz.Async.Deferred("Proxy.JSON._sendMessage", {trace:false});
//deferredResult.addCallback(function(){console.log("About to send request");});
deferredResult.addCallback(Clipperz.Async.doXHR, this.url(), {
method:'POST',
sendContent:formData,
// headers:{"Content-Type":"application/x-www-form-urlencoded"},
uploadProgress: aProgressCallback || null,
});
//deferredResult.addCallback(function(something){console.log("Done sending request"); return something;});
deferredResult.addCallback(function (someValues) {
if (someValues['result'] == 'EXCEPTION') {
throw someValues['message'];
}
// TODO: check return value with actual request. Expected: ArrayBuffer
return someValues;
});
deferredResult.callback();
return deferredResult;
},
// TODO: test
'_downloadAttachment': function(someArguments, aProgressCallback, aSharedSecret, aToll) {
var deferredResult;
var parameters;
var innerParameters = {
'toll': aToll,
'parameters': {
'message': 'downloadAttachment',
'srpSharedSecret': aSharedSecret,
'parameters': {
'reference': someArguments['reference']
}
}
}
parameters = {
method: 'message',
version: 'fake-app-version',
parameters: Clipperz.Base.serializeJSON(innerParameters)
};
deferredResult = new Clipperz.Async.Deferred("Proxy.JSON._downloadAttachment", {trace:false});
deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'remoteRequestSent');
//deferredResult.addCallback(function(){console.log("About to send request");});
deferredResult.addCallback(Clipperz.Async.doXHR, this.url(), {
method:'POST',
responseType:'arraybuffer',
sendContent:MochiKit.Base.queryString(parameters),
headers:{"Content-Type":"application/x-www-form-urlencoded"},
downloadProgress:aProgressCallback || null,
});
deferredResult.addCallback(
function(something){
// console.log("Done sending request", something);
return something;
});
deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'remoteRequestReceived');
deferredResult.addCallback(MochiKit.Base.itemgetter('response'));
deferredResult.addCallback(function (anArrayBuffer) {
var result = (anArrayBuffer.byteLength > 0) ? anArrayBuffer : false;
var DEBUG = 'PUT_A_BREAKPOINT_HERE';
return {
result: anArrayBuffer,
};
})
// deferredResult.addErrback(function(something) {
// return something;
// });
deferredResult.callback();
return deferredResult;
},
//=========================================================================
__syntaxFix__: "syntax fix"

View File

@@ -37,6 +37,8 @@ Clipperz.PM.Proxy.Offline.DataStore = function(args) {
this._tolls = {};
this._currentStaticConnection = null;
this.NETWORK_SIMULATED_SPEED = 100*1024;
return this;
}
@@ -233,9 +235,9 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
//=========================================================================
'processMessage': function (aFunctionName, someParameters) {
'processMessage': function (aFunctionName, someParameters, someOptionalParameters) {
var deferredResult;
try {
var result;
var connection;
@@ -266,7 +268,7 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
this.storeConnectionForRequestWithConnectionAndResponse(aFunctionName, someParameters, connection, result);
deferredResult = MochiKit.Async.succeed(result);
deferredResult = MochiKit.Async.succeed(result)
} catch (exception) {
deferredResult = MochiKit.Async.fail(exception);
}
@@ -274,6 +276,69 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
return deferredResult;
},
//-------------------------------------------------------------------------
uploadAttachment: function(someArguments, aProgressCallback, aSharedSecret, aToll) {
var connection = this.currentStaticConnection();
var attachmentReference = someArguments['attachmentReference'];
if (this.isReadOnly() == false) {
connection['userData']['attachments'][attachmentReference] = {
'record': someArguments['recordReference'],
'status': 'AVAILABLE',
'data': someArguments['arrayBufferData'],
'version': someArguments['version'],
};
return this.simulateNetworkDelayResponse(someArguments['arrayBufferData'].length, aProgressCallback, {
result: {},
toll: this.getTollForRequestType('MESSAGE')
});
} else {
throw Clipperz.PM.Proxy.Offline.DataStore.exception.ReadOnly;
}
},
downloadAttachment: function(someArguments, aProgressCallback, aSharedSecret, aToll) {
var connection = this.currentStaticConnection();
var reference = someArguments['reference'];
var result = connection['userData']['attachments'][reference]['data'];
return this.simulateNetworkDelayResponse(result.length, aProgressCallback, {
result: result,
// toll: this.getTollForRequestType('MESSAGE')
});
},
simulateNetworkDelayResponse: function(payloadSize, progressCallback, aResponse) {
var deferredResult;
var i;
deferredResult = new Clipperz.Async.Deferred("Proxy.Offline.DataStore.simulateNetworkDelay", {trace:false});
for (i = 0; i < payloadSize/this.NETWORK_SIMULATED_SPEED; i++) {
var loaded = i*this.NETWORK_SIMULATED_SPEED;
deferredResult.addCallback(MochiKit.Async.wait, 1);
deferredResult.addMethod(this, 'runProgressCallback', progressCallback, loaded, payloadSize);
}
deferredResult.addCallback(MochiKit.Async.succeed, aResponse);
deferredResult.callback();
return deferredResult;
},
runProgressCallback: function(aCallback, aLoadedValue, aTotalValue) {
var fakeProgressEvent = {
'lengthComputable': true,
'loaded': aLoadedValue,
'total': aTotalValue,
}
return aCallback(fakeProgressEvent);
},
//=========================================================================
'_knock': function(aConnection, someParameters) {
@@ -340,7 +405,9 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
userData = this.data()['users'][someParameters.parameters.C];
otpsData = this.data()['onetimePasswords'];
//console.log("Proxy.Offline.DataStore._handshake: otpsData:", otpsData);
if (! userData['attachments']) {
userData['attachments'] = {};
}
userOTPs = {};
MochiKit.Base.map(function(aOTP) {
@@ -349,9 +416,6 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
}
},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;
@@ -455,7 +519,7 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
//-------------------------------------------------------------------------
'_message': function(aConnection, someParameters) {
'_message': function(aConnection, someParameters, someOptionalParameters) {
var result;
if (MochiKit.Base.keys(aConnection).length==0) {
@@ -499,8 +563,14 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
//=====================================================================
} else if (someParameters.message == 'getRecordDetail') {
MochiKit.Base.update(result, aConnection['userData']['records'][someParameters['parameters']['reference']]);
result['reference'] = someParameters['parameters']['reference'];
var reference = someParameters['parameters']['reference'];
MochiKit.Base.update(result, aConnection['userData']['records'][reference]);
result['reference'] = reference;
result['attachmentStatus'] = this.attachmentStatus(aConnection, reference);
//console.log('Proxy.getRecordDetail', result);
} else if (someParameters.message == 'getAllRecordDetails') {
MochiKit.Base.update(result, aConnection['userData']['records']);
} else if (someParameters.message == 'getOneTimePasswordsDetails') {
@@ -613,7 +683,9 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
*/ //=====================================================================
} else if (someParameters.message == 'saveChanges') {
if (this.isReadOnly() == false) {
var i, c;
//console.log("Proxy.Offline.DataStore.saveChanges: parameters:", someParameters);
//console.log("Proxy.Offline.DataStore.saveChanges: attachments:", aConnection['userData']['attachments']);
var i, c, j, d;
if (aConnection['userData']['lock'] != someParameters['parameters']['user']['lock']) {
throw "the lock attribute is not processed correctly"
@@ -664,8 +736,12 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
'accessDate': Clipperz.PM.Date.formatDateWithUTCFormat(new Date())
}
}
this.pruneAttachments(aConnection, currentRecordData['record']['reference'], currentRecordData['attachments']);
}
//console.log("Proxy.Offline.DataStore.saveChanges: attachments:", aConnection['userData']['attachments']);
c = someParameters['parameters']['records']['deleted'].length;
for (i=0; i<c; i++) {
var currentRecordReference;
@@ -731,7 +807,7 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
},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
@@ -816,10 +892,47 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
} else {
result = null;
}
return result;
},
'attachmentStatus': function(aConnection, aRecordReference) {
var result;
//console.log("Proxy.attachmentStatus: data:", aConnection['userData']['attachments']);
result = {};
if (aConnection['userData']['attachments']) {
var reference;
for (reference in aConnection['userData']['attachments']) {
//console.log("Proxy.attachmentStatus: -> comparison: serverRecordReference, aRecordReference", aConnection['userData']['attachments'][reference]['record'], aRecordReference);
if (aConnection['userData']['attachments'][reference]['record'] == aRecordReference) {
result[reference] = aConnection['userData']['attachments'][reference]['status'];
}
}
}
//console.log("Proxy.attachmentStatus: result:", result);
return result;
},
/** Removes all the stored attachments whose reference is not in the given list */
'pruneAttachments': function(aConnection, aRecordReference, someAttachmentReferences) {
if (aConnection['userData']['attachments']) {
var reference;
for (reference in aConnection['userData']['attachments']) {
var attachment = aConnection['userData']['attachments'][reference];
if ( (attachment['record'] == aRecordReference) &&
(someAttachmentReferences.indexOf(reference) < 0)
) {
delete aConnection['userData']['attachments'][reference];
}
}
}
},
/*
'userSerializedEncryptedData': function(someData) {
var deferredResult;

View File

@@ -53,10 +53,19 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline, Clipperz.PM.Proxy, {
//-------------------------------------------------------------------------
'_sendMessage': function(aFunctionName, aVersion, someParameters) {
return this.dataStore().processMessage(aFunctionName, someParameters);
'_sendMessage': function(aFunctionName, aVersion, someParameters, someOptionalParameters) {
return this.dataStore().processMessage(aFunctionName, someParameters, someOptionalParameters);
},
'_uploadAttachment': function(someArguments, aProgressCallback, aSharedSecret, aToll) {
return this.dataStore().uploadAttachment(someArguments, aProgressCallback, aSharedSecret, aToll);
},
'_downloadAttachment': function(someArguments, aProgressCallback, aSharedSecret, aToll) {
return this.dataStore().downloadAttachment(someArguments, aProgressCallback, aSharedSecret, aToll);
},
//-------------------------------------------------------------------------
'isReadOnly': function () {

View File

@@ -0,0 +1,550 @@
/*
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');
Clipperz.PM.UI.AttachmentController = function(someParameters) {
this.MAX_SIMULTANEOUS_READ = 1;
this.MAX_SIMULTANEOUS_UPLOAD = 1;
this.MAX_SIMULTANEOUS_DOWNLOAD = 1;
this.MAX_SIMULTANEOUS_ENCRYPT = 1;
this.MAX_SIMULTANEOUS_DECRYPT = 1;
this.LATEST_ENCRYPTION_VERSION = '1.0'; // Versions aren't handled completely yet!
this.fileQueue = [];
this.notifications = [];
this.operationsCount = null;
this.encryptedDocument = null;
this.uploadMessageCallback = someParameters['uploadMessageCallback'];
this.downloadMessageCallback = someParameters['downloadMessageCallback'];
this.reloadServerStatusCallback = someParameters['reloadServerStatusCallback'];
return this;
}
MochiKit.Base.update(Clipperz.PM.UI.AttachmentController.prototype, {
toString: function () {
return "Clipperz.PM.UI.AttachmentController";
},
//-------------------------------------------------------------------------
notifyUpdate: function() {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'updateAttachmentQueueInfo', this.getQueueInfo(), this.getNotificationsInfo());
},
getQueueInfo: function() {
return this.fileQueue;
},
getNotificationsInfo: function() {
return this.notifications;
},
//=========================================================================
// Entry points
//=========================================================================
addAttachment: function (anAttachment) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.uploadAttachment", {trace:false});
deferredResult.collectResults({
'_attachment': MochiKit.Base.partial(MochiKit.Async.succeed, anAttachment),
'_record': MochiKit.Base.method(anAttachment, 'record'),
'reference': MochiKit.Base.method(anAttachment, 'reference'),
'meta': MochiKit.Base.method(anAttachment, 'metadata'),
'key': MochiKit.Base.method(anAttachment, 'key'),
'nonce': MochiKit.Base.method(anAttachment, 'nonce'),
'status': MochiKit.Base.partial(MochiKit.Async.succeed, 'WAITING_READ'),
'file': MochiKit.Base.method(anAttachment, 'file'),
'recordReference': MochiKit.Base.method(anAttachment.record(), 'reference'),
'process': MochiKit.Base.partial(MochiKit.Async.succeed, 'UPLOAD'), // Used only to differentiate notifications
}, {trace: false});
deferredResult.addMethod(this, 'addFileToQueue');
deferredResult.callback();
return deferredResult;
},
getAttachment: function(anAttachment, aMessageCallback) {
if (this.getQueuePosition(anAttachment.reference()) >= 0) {
this.removeFileFromQueue(anAttachment.reference());
}
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.downloadAttachment", {trace:false});
deferredResult.collectResults({
'_attachment': MochiKit.Base.partial(MochiKit.Async.succeed, anAttachment),
'_record': MochiKit.Base.method(anAttachment, 'record'),
'reference': MochiKit.Base.method(anAttachment, 'reference'),
'meta': MochiKit.Base.method(anAttachment, 'metadata'),
'key': MochiKit.Base.method(anAttachment, 'key'),
'nonce': MochiKit.Base.method(anAttachment, 'nonce'),
'status': MochiKit.Base.partial(MochiKit.Async.succeed, 'WAITING_DOWNLOAD'),
'messageCallback': MochiKit.Async.succeed(aMessageCallback),
'process': MochiKit.Base.partial(MochiKit.Async.succeed, 'DOWNLOAD'), // Used only to differentiate notifications
}, {trace: false});
deferredResult.addCallback(function(aResult){
MochiKit.Base.update(aResult, {'messageCallback': aMessageCallback});
return aResult;
});
deferredResult.addMethod(this, 'addFileToQueue');
deferredResult.callback();
return deferredResult;
},
cancelAttachment: function(anAttachment) {
var deferredResult;
var reference = anAttachment.reference()
var queueElement = this.getQueueElement(reference);
if (queueElement) {
deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.cancelAttachment", {trace:false});
deferredResult.addMethod(this, 'updateFileInQueue', reference, {'status': 'CANCELED'});
if (queueElement['deferredRequest']) {
deferredResult.addMethod(queueElement['deferredRequest'], 'cancel');
}
deferredResult.callback();
// TODO: We may also want do delete stuff in the queue element
} else {
deferredResult = MochiKit.Async.succeed();
}
return deferredResult;
},
//=========================================================================
// Queue management
//=========================================================================
dispatchQueueOperations: function() {
var currentElement;
var processNextElements;
var count = this.updateOperationsCount();
this.notifyUpdate();
processNextElements = true;
for (i in this.fileQueue) {
if(processNextElements) {
currentElement = this.fileQueue[i];
switch (currentElement['status']) {
case 'WAITING_READ':
if ((count['READING']) < this.MAX_SIMULTANEOUS_READ) {
this.readFile(currentElement['reference'], currentElement['file']);
processNextElements = false;
}
break;
case 'WAITING_ENCRYPT':
if (count['ENCRYPTING'] < this.MAX_SIMULTANEOUS_ENCRYPT) {
this.encryptFile(currentElement['reference'], currentElement['originalArray'], currentElement['key'], currentElement['nonce']);
processNextElements = false;
}
break;
case 'WAITING_UPLOAD':
if (count['UPLOADING'] < this.MAX_SIMULTANEOUS_UPLOAD) {
this.uploadFile(currentElement['reference'], currentElement['encryptedArray']);
processNextElements = false;
}
break;
case 'WAITING_DOWNLOAD':
if (count['DOWNLOADING'] < this.MAX_SIMULTANEOUS_DOWNLOAD) {
this.downloadFile(currentElement['reference'], currentElement['messageCallback']);
processNextElements = false;
}
break;
case 'WAITING_DECRYPT':
if (count['DECRYPTING'] < this.MAX_SIMULTANEOUS_DECRYPT) {
this.decryptFile(currentElement['reference'], currentElement['encryptedArray'], currentElement['key'], currentElement['nonce']);
processNextElements = false;
}
break;
case 'WAITING_SAVE':
this.saveFile(currentElement['reference'], currentElement['decryptedArray'], currentElement['meta']['name'], currentElement['meta']['type']);
processNextElements = false;
Clipperz.Sound.beep();
break;
}
}
}
},
updateOperationsCount: function() {
var count;
count = {
'WAITING_READ': 0,
'READING': 0,
'WAITING_ENCRYPT': 0,
'ENCRYPTING': 0,
'WAITING_UPLOAD': 0,
'UPLOADING': 0,
'WAITING_DOWNLOAD': 0,
'DOWNLOADING': 0,
'WAITING_DECRYPT': 0,
'DECRYPTING': 0,
'WAITING_SAVE': 0,
'DONE': 0,
'CANCELED': 0,
'FAILED': 0,
};
for (var i in this.fileQueue) {
count[this.fileQueue[i]['status']]++;
}
this.operationsCount = count;
return this.operationsCount;
},
addFileToQueue: function(someParameters) {
this.fileQueue.push(someParameters);
this.addNotification(someParameters);
this.dispatchQueueOperations();
},
removeFileFromQueue: function(aFileReference) {
this.fileQueue.splice(this.getQueuePosition(aFileReference), 1);
this.dispatchQueueOperations();
},
getQueueElement: function(aFileReference) {
var i = this.getQueuePosition(aFileReference);
return this.fileQueue[i];
},
updateFileInQueue: function(aFileReference, someParameters) {
var queuePosition = this.getQueuePosition(aFileReference);
MochiKit.Base.update(this.fileQueue[queuePosition], someParameters);
this.dispatchQueueOperations();
},
appendResult: function(aFileReference, anArray) {
var queueElement = this.getQueueElement(aFileReference);
queueElement['result'].set(anArray, queueElement['currentByte']);
},
getQueuePosition: function(aFileReference) {
var result;
result = -1;
for (var i in this.fileQueue) {
if (this.fileQueue[i].reference == aFileReference) {
result = i;
}
}
return result;
},
//=========================================================================
// Queue Processing
//=========================================================================
addNotification: function(aQueueElement) {
this.notifications.push({
'id': this.randomId(),
'queueElement': aQueueElement
})
},
removeNotification: function(aNotificationId) {
var i, position;
position = -1;
for (i in this.notifications) {
if (this.notifications[i]['id'] == aNotificationId) {
position = i;
}
}
if (position >= 0) {
this.notifications.splice(position, 1);
}
this.notifyUpdate();
},
randomId: function() {
return Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(32).toHexString().substring(2);
},
//=========================================================================
// Queue Processing: READ
//=========================================================================
readFile: function(aFileReference, aFile) {
var reader = new FileReader();
this.updateFileInQueue(aFileReference, {
'status': 'READING',
});
reader.onload = MochiKit.Base.method(this, 'readFileOnload', aFileReference);
reader.readAsArrayBuffer(aFile);
},
readFileOnload: function(aFileReference, anEvent) {
this.updateFileInQueue(aFileReference, {
'status': 'WAITING_ENCRYPT',
'originalArray': new Uint8Array(anEvent.target.result),
})
},
//=========================================================================
// Queue Processing: ENCRYPT
//=========================================================================
encryptFile: function(aFileReference, anArrayBuffer, aKey, aNonce) {
this.updateFileInQueue(aFileReference, {
'status': 'ENCRYPTING',
});
window.crypto.subtle.importKey(
"raw",
aKey, //this is an example jwk key, "raw" would be an ArrayBuffer
{ name: "AES-CBC" }, //this is the algorithm options
false, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
)
// .then(MochiKit.Base.method(this, 'doEncrypt', aFileReference, anArrayBuffer, aNonce))
.then(this.doEncrypt.bind(this,aFileReference, anArrayBuffer, aNonce))
.catch(MochiKit.Base.method(this, 'handleException', aFileReference, 'encryptFile(): encryption failed'));
},
doEncrypt: function(aFileReference, anArrayBuffer, anIV, aWebcryptoKey) {
window.crypto.subtle.encrypt(
{
name: "AES-CBC",
iv: anIV,
},
aWebcryptoKey,
anArrayBuffer
)
.then(MochiKit.Base.method(this, 'doneEncrypt', aFileReference))
.catch(MochiKit.Base.method(this, 'handleException', aFileReference, 'doEncrypt(): encryption failed'));
},
doneEncrypt: function(aFileReference, anArrayBuffer) {
this.updateFileInQueue(aFileReference, {
'status': 'WAITING_UPLOAD',
'encryptedArray': new Uint8Array(anArrayBuffer),
});
},
//=========================================================================
// Queue Processing: UPLOAD
//=========================================================================
uploadFile: function(aFileReference, anEncryptedArray) {
this.updateFileInQueue(aFileReference, {
'status': 'UPLOADING',
'deferredRequest': this.uploadFileRequest(aFileReference, anEncryptedArray),
'requestProgress': 0,
});
},
uploadFileRequest: function(aFileReference, anEncryptedArray) {
var deferredResult;
var queueElement = this.getQueueElement(aFileReference);
deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.uploadFileRequest", {trace:false});
deferredResult.addCallback(this.uploadMessageCallback, {
'attachmentReference': queueElement['_attachment'].reference(),
'recordReference': queueElement['_attachment'].record().reference(),
'arrayBufferData': anEncryptedArray,
'version': this.LATEST_ENCRYPTION_VERSION,
}, MochiKit.Base.method(this, 'uploadFileProgress', aFileReference));
deferredResult.addMethod(this, 'uploadFileDone', aFileReference);
deferredResult.addErrback(MochiKit.Base.method(this, 'handleException', aFileReference, 'uploadFileRequest(): request failed or canceled'));
deferredResult.callback();
return deferredResult;
},
uploadFileDone: function(aFileReference, aResult){
var record = this.getQueueElement(aFileReference)['_record'];
return Clipperz.Async.callbacks("AttachmentController.uploadFileDone", [
MochiKit.Base.partial(this.reloadServerStatusCallback, record),
MochiKit.Base.method(this, 'updateFileInQueue', aFileReference, {
'status': 'DONE',
'requestProgress': 1,
}),
], {trace:false});
},
uploadFileProgress: function(aFileReference, anEvent) {
var newProgress = (anEvent.lengthComputable) ? (anEvent.loaded / anEvent.total) : -1;
this.updateFileInQueue(aFileReference, {
'requestProgress': newProgress,
});
},
//=========================================================================
// Queue Processing: DOWNLOAD
//=========================================================================
downloadFile: function(aFileReference) {
var deferredRequest;
var queueElement = this.getQueueElement(aFileReference);
deferredRequest = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.downloadFile", {trace:false});
deferredRequest.addCallback(this.downloadMessageCallback, queueElement['_attachment'], MochiKit.Base.method(this, 'downloadFileProgress', aFileReference));
deferredRequest.addMethod(this, 'downloadFileDone', aFileReference);
deferredRequest.addErrback(MochiKit.Base.method(this, 'handleException', aFileReference, 'downloadFile(): download filed or canceled'));
deferredRequest.callback();
this.updateFileInQueue(aFileReference, {
'status': 'DOWNLOADING',
'deferredRequest': deferredRequest,
'requestProgress': 0,
});
},
downloadFileDone: function(aFileReference, aResult){
var queueElement = this.getQueueElement(aFileReference);
var encryptedArray = new Uint8Array(aResult);
this.updateFileInQueue(aFileReference, {
'status': 'WAITING_DECRYPT',
'key': queueElement['key'],
'nonce': queueElement['nonce'],
'encryptedArray': encryptedArray,
'requestProgress': 1,
});
},
downloadFileProgress: function(aFileReference, anEvent) {
var newProgress = (anEvent.lengthComputable) ? (anEvent.loaded / anEvent.total) : -1;
this.updateFileInQueue(aFileReference, {
'requestProgress': newProgress,
});
},
//=========================================================================
// Queue Processing: DECRYPT
//=========================================================================
decryptFile: function(aFileReference, anArrayBuffer, aKey, aNonce) {
this.updateFileInQueue(aFileReference, {
'status': 'DECRYPTING',
});
window.crypto.subtle.importKey(
"raw",
aKey, //this is an example jwk key, "raw" would be an ArrayBuffer
{name: "AES-CBC"}, //this is the algorithm options
false, //whether the key is extractable (i.e. can be used in exportKey)
["decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
)
.then(MochiKit.Base.method(this, 'doDecrypt', aFileReference, anArrayBuffer, aNonce))
.catch(MochiKit.Base.method(this, 'handleException', aFileReference, 'decryptFile(): decryption failed'));
},
doDecrypt: function(aFileReference, anArrayBuffer, anIV, aWebcryptoKey) {
window.crypto.subtle.decrypt(
{name: "AES-CBC", iv: anIV},
aWebcryptoKey,
anArrayBuffer
)
.then(MochiKit.Base.method(this, 'doneDecrypt', aFileReference))
.catch(MochiKit.Base.method(this, 'handleException', aFileReference, 'doDecrypt(): decryption failed'));
},
doneDecrypt: function(aFileReference, anArrayBuffer) {
this.updateFileInQueue(aFileReference, {
'status': 'WAITING_SAVE',
'decryptedArray': new Uint8Array(anArrayBuffer),
});
},
//=========================================================================
// Queue Processing: SAVE
//=========================================================================
saveFile: function(aFileReference, anArray, aFileName, aFileType) {
var blob = new Blob([anArray], {type: aFileType});
saveAs(blob, aFileName);
this.updateFileInQueue(aFileReference, {
'status': 'DONE',
});
},
//=========================================================================
// Exceptions
//=========================================================================
/** Handles exceptions for upload/download and encrypt/decrypt. Note that
* an exception is thrown also when the user manually cancels the file
* processing. In this case the status remains 'CANCELED'.
*/
handleException: function(aFileReference, aMessage) {
var queueElement = this.getQueueElement(aFileReference);
if (queueElement['status'] != 'CANCELED') {
this.updateFileInQueue(aFileReference, {
'status': 'FAILED',
});
}
if (aMessage) {
console.log("AttachmentController: caught exception (" + aMessage + ")");
}
},
//=========================================================================
__syntaxFix__: "syntax fix"
});

View File

@@ -0,0 +1,119 @@
/*
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');
Clipperz.PM.UI.Components.AttachmentQueueBoxClass = React.createClass({
propTypes: {
'attachmentQueueInfo': React.PropTypes.object.isRequired,
'attachmentQueueBoxStatus': React.PropTypes.string.isRequired,
},
getInitialState: function() {
return {
'unreadNotifications': [],
};
},
//=========================================================================
// Actions
//=========================================================================
handleClose: function(aNotificationElement) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'closeAttachmentNotification', aNotificationElement['id']);
},
//=========================================================================
// Render Methods
//=========================================================================
renderNotificationElement: function(aNotificationElement) {
var processIcon, status, closeButton, progressIndicator;
var queueElement = aNotificationElement['queueElement'];
processIcon = (queueElement['process'] == 'UPLOAD') ? "\u2b06" : "\u2b07";
status = "waiting";
closeButton = null;
progressIndicator = null;
if (queueElement['status'] == 'DOWNLOADING' || queueElement['status'] == 'UPLOADING') {
status = Math.floor(queueElement['requestProgress']*100) + '%';
progressIndicator = React.DOM.span({'className': 'progress'}, Clipperz.PM.UI.Components.RadialProgressIndicator({'progress': queueElement['requestProgress']}));
}
if (queueElement['status'] == 'DONE' || queueElement['status'] == 'CANCELED' || queueElement['status'] == 'FAILED') {
status = (queueElement['status'] == 'DONE') ? 'completed' : (queueElement['status'] == 'FAILED') ? "failed" : "canceled";
closeButton = React.DOM.span({'className': 'close'}, React.DOM.a({
'className': 'close',
'onClick': MochiKit.Base.method(this, 'handleClose', aNotificationElement),
}, "remove field"));
}
return React.DOM.li({},[
React.DOM.span({'className': 'contentType'}, Clipperz.PM.DataModel.Attachment.contentTypeIcon(queueElement['meta']['type'])),
React.DOM.span({'className': 'name'}, queueElement['meta']['name']),
// React.DOM.span({'className': 'size'}, queueElement['meta']['size']),
React.DOM.span({'className': 'status'}, [
React.DOM.span({'className': 'statusString'}, status),
React.DOM.span({'className': 'processIcon'}, processIcon),
]),
progressIndicator,
closeButton,
]);
},
renderNotifications: function(someNotifications) {
var result;
if (someNotifications.length == 0) {
result = React.DOM.p({}, "No attachments in queue");
} else {
result = MochiKit.Base.map(MochiKit.Base.method(this, 'renderNotificationElement'), someNotifications);
}
return result;
},
// renderNotifications: function(someNotifications) {
// console.log('AttachmentQueueBox.renderNotifications:', someNotifications);
// },
render: function () {
//test
this.renderNotifications(this.props['attachmentQueueInfo']['notifications']);
return React.DOM.div({
'className': 'attachmentQueueStatus '+this.props['attachmentQueueBoxStatus'],
}, [
React.DOM.div({'className': 'arrow'}),
React.DOM.ul({}, this.renderNotifications(this.props['attachmentQueueInfo']['notifications'])),
]);
}
//=========================================================================
});
Clipperz.PM.UI.Components.AttachmentQueueBox = React.createFactory(Clipperz.PM.UI.Components.AttachmentQueueBoxClass);

View File

@@ -30,22 +30,40 @@ Clipperz.PM.UI.Components.ButtonClass = React.createClass({
'eventName': React.PropTypes.string.isRequired,
'label': React.PropTypes.string.isRequired,
'handler': React.PropTypes.func.isRequired,
'className': React.PropTypes.string
'className': React.PropTypes.string,
'badgeTopContent': React.PropTypes.string,
'badgeBottomContent': React.PropTypes.string,
},
//=========================================================================
render: function () {
var badgeTop;
var badgeBottom;
var classes = {
'button': true
};
if (typeof(this.props['className']) != 'undefined') {
classes[this.props['className']] = true;
};
badgeTop = null;
if (this.props['badgeTopContent']) {
badgeTop = React.DOM.span({'className': 'badge top'}, this.props['badgeTopContent']);
};
badgeBottom = null;
if (this.props['badgeBottomContent']) {
badgeBottom = React.DOM.span({'className': 'badge bottom'}, this.props['badgeBottomContent']);
};
return React.DOM.div({className:Clipperz.PM.UI.Components.classNames(classes), onClick:this.props['handler']}, [
React.DOM.div({className:this.props['eventName']}, [
React.DOM.h3({className:'label'}, this.props['label'])
React.DOM.h3({className:'label'}, this.props['label']),
badgeTop,
badgeBottom,
])
]);
}

View File

@@ -32,7 +32,8 @@ Clipperz.PM.UI.Components.CardToolbarClass = React.createClass({
'accountInfo': React.PropTypes.object.isRequired,
'proxyInfo': React.PropTypes.object.isRequired,
'messageBox': React.PropTypes.object.isRequired,
'filter': React.PropTypes.object /*.isRequired */
'filter': React.PropTypes.object /*.isRequired */,
'attachmentQueueInfo': React.PropTypes.object.isRequired,
},
//----------------------------------------------------------------------------
@@ -48,15 +49,31 @@ Clipperz.PM.UI.Components.CardToolbarClass = React.createClass({
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'toggleSettingsPanel');
},
attachmentQueueToggleHandler: function(anEvent) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'toggleAttachmentQueueBox');
},
//============================================================================
renderWithSidePanels: function () {
var attachmentDownloadNotificationNumber = MochiKit.Base.filter(function(anElement) {
return anElement['queueElement']['process'] == 'DOWNLOAD';
}, this.props['attachmentQueueInfo']['notifications']).length;
var attachmentUploadNotificationNumber = this.props['attachmentQueueInfo']['notifications'].length - attachmentDownloadNotificationNumber;
return [
React.DOM.div({className:'selectionToggle'}, [
Clipperz.PM.UI.Components.Button({eventName:'selectionToggleButton', label:"tags", handler:this.selectionToggleHandler})
]),
this.renderWithoutSidePanels(),
// React.DOM.div({className:'attachmentToggle'}, [
// Clipperz.PM.UI.Components.Button({eventName:'attachmentQueueToggleButton', label:"clipperz", handler:this.attachmentQueueToggleHandler})
// ]),
// TODO: validate and adjust names
React.DOM.div({className:'settingsToggle'}, [
Clipperz.PM.UI.Components.Button({eventName:'attachmentQueueToggleButton', label:"\u2191\u2193", handler:this.attachmentQueueToggleHandler, badgeTopContent: attachmentDownloadNotificationNumber, badgeBottomContent: attachmentUploadNotificationNumber}),
Clipperz.PM.UI.Components.Button({eventName:'settingsToggleButton', label:"menu", handler:this.settingsToggleHandler})
])
];
@@ -70,6 +87,8 @@ Clipperz.PM.UI.Components.CardToolbarClass = React.createClass({
if (this.props['filter']['type'] == 'RECENT') {
result = [React.DOM.div({className:'clipperz'}, [React.DOM.span({className:'logo recent'}, "recent")])];
} else if (this.props['filter']['type'] == 'WITH_ATTACHMENTS') {
result = [React.DOM.div({className:'clipperz'}, [React.DOM.span({className:'logo withAttachments'}, "attachment")])];
} else if (this.props['filter']['type'] == 'TAG') {
result = [React.DOM.div({className:'clipperz'}, [
React.DOM.span({className:'logo tag'}, "tag"),

View File

@@ -136,6 +136,7 @@ Clipperz.PM.UI.Components.Cards.CommandToolbarClass = React.createClass({
var classes = {
'cardDetailToolbar': true,
'commands': true,
'top': true,
};
classes[style] = true;

View File

@@ -40,6 +40,7 @@ Clipperz.PM.UI.Components.Cards.DetailClass = React.createClass({
result['showGlobalMask'] = this.props['showGlobalMask'];
result['allTags'] = this.props['allTags'];
result['preferences'] = this.props['preferences'];
result['attachmentQueueInfo'] = this.props['attachmentQueueInfo'];
}
return result;

View File

@@ -41,6 +41,8 @@ Clipperz.PM.UI.Components.Cards.EditClass = React.createClass({
'fromFieldPosition': -1,
'toFieldPosition': -1,
'dropPosition': -1,
'skippedFiles': [],
};
},
@@ -581,7 +583,14 @@ console.log("DROP"); //, anEvent);
},
renderAddNewField: function () {
return React.DOM.div({'className':'newCardField', 'onClick':this.addNewField}, "add new field");
// return React.DOM.div({'className':'newCardField', 'onClick':this.addNewField}, "add new field");
return React.DOM.div({'className':'newCardField', 'onClick':this.addNewField}, [
React.DOM.div({'className':'fieldGhostShadow'}, [
React.DOM.div({'className':'label'}, ""),
React.DOM.div({'className':'value'}, ""),
]),
React.DOM.div({'className':'addNewFieldButton'}, "add new field"),
]);
},
//............................................................................
@@ -603,6 +612,210 @@ console.log("DROP"); //, anEvent);
//............................................................................
handleRemoveAttachment: function(anAttachment) {
// MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'cancelAttachment', anAttachment);
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'removeAttachment', this.record(), anAttachment);
},
//............................................................................
uploadFiles: function(someFiles) {
var i;
var newSkippedFiles = [];
for (i = 0; i < someFiles.length; i++) {
var file = someFiles[i];
if (file.size <= Clipperz.PM.DataModel.Attachment.MAX_ATTACHMENT_SIZE) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'addAttachment', this.record(), file);
} else {
newSkippedFiles.push(file);
this.setState({'skippedFiles': newSkippedFiles});
}
}
// TODO: check compatibility with all browsers
this.refs['attachmentInput'].getDOMNode().value = null;
},
//............................................................................
handleFileSelect: function(anEvent) {
this.uploadFiles(anEvent.target.files);
},
//............................................................................
handleOnDrop: function (anEvent) {
anEvent.preventDefault();
this.uploadFiles(anEvent.dataTransfer.files);
},
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
anEvent.preventDefault();
},
//............................................................................
renderSkippedFiles: function() {
var result;
result = null;
if (this.state['skippedFiles'].length > 0) {
result = React.DOM.div({'className': 'skippedFiles'},[
React.DOM.p({}, "The following files exceed the size limit of " + filesize(Clipperz.PM.DataModel.Attachment.MAX_ATTACHMENT_SIZE)),
React.DOM.ul({},
MochiKit.Base.map(function(aFile) {
return React.DOM.li({}, [
React.DOM.span({'className': 'filename'}, aFile.name),
React.DOM.span({}, " (" + filesize(aFile.size) + ")"),
]);
}, this.state['skippedFiles'])
),
]);
}
return result;
},
renderAttachmentProgress: function(aStatus, aServerStatus, aProgress) {
var result;
var broken = (! aServerStatus && (! aStatus || aStatus == 'CANCELED' || aStatus == 'FAILED' || aStatus == 'DONE'));
result = null;
if (aStatus == 'UPLOADING' || aStatus == 'DOWNLOADING') {
result = Clipperz.PM.UI.Components.RadialProgressIndicator({
'progress': aProgress,
'border': 1
});
} else if (! broken && aStatus != 'DONE' && aStatus != 'FAILED' && aServerStatus != 'AVAILABLE') {
result = Clipperz.PM.UI.Components.RadialProgressIndicator({
'progress': 0,
'border': 1,
'additionalClasses': ['waiting'],
});
}
return result;
},
renderAttachmentStatus: function(aStatus, aServerStatus, aProgress) {
var result;
var status = aStatus ? aStatus : false;
result = null;
if (status == 'FAILED') {
result = React.DOM.span({'className': 'failed'}, "failed");
} else if (status == 'UPLOADING' || status == 'DOWNLOADING') {
var actionSymbol = (status == 'UPLOADING') ? "\u2b06" : "\u2b07";
result = React.DOM.span({'className': 'progressStatus'}, actionSymbol + Math.floor(aProgress*100) + '%');
} else if (aServerStatus != 'AVAILABLE') {
switch(status) {
case 'CANCELED':
result = React.DOM.span({'className': 'broken'}, "canceled");
break;
case 'DONE':
result = React.DOM.span({'className': 'broken'}, "failed");
break;
case false:
result = React.DOM.span({'className': 'broken'}, "failed");
break;
default:
result = React.DOM.span({'className': 'waiting'}, "waiting");
}
}
return result;
},
renderAttachmentActions: function(aStatus, aServerStatus, anAttachment) {
var result;
result = null;
if (aStatus != 'DOWNLOADING') {
result = React.DOM.a({
'className': 'remove',
'onClick': MochiKit.Base.method(this, 'handleRemoveAttachment', anAttachment),
}, "remove field");
}
return result;
},
renderAttachment: function(anAttachment) {
var queueInfo = this.props['attachmentQueueInfo'].elementFetchCallback(anAttachment._reference) || [];
var queueStatus = queueInfo['status'];
var serverStatus = this.props['attachmentServerStatus'][anAttachment._reference];
var broken = (! serverStatus && (! queueStatus || queueStatus == 'CANCELED' || queueStatus == 'FAILED' || queueStatus == 'DONE'));
// console.log(anAttachment['name'], queueStatus)
var status = this.renderAttachmentStatus(queueStatus, serverStatus, queueInfo['requestProgress']);
var actions = this.renderAttachmentActions(queueStatus, serverStatus, anAttachment['_attachment']);
var progressIndicator = this.renderAttachmentProgress(queueStatus, serverStatus, queueInfo['requestProgress']);;
return React.DOM.li({
'className': (broken) ? 'broken' : '',
'key': anAttachment._reference
}, [
React.DOM.span({'className': 'contentType'}, Clipperz.PM.DataModel.Attachment.contentTypeIcon(anAttachment.contentType)),
React.DOM.span({'className': 'meta'}, [
React.DOM.span({'className': 'name'}, anAttachment.name),
React.DOM.span({'className': 'size'}, filesize(anAttachment.size)),
]),
React.DOM.span({'className': 'status'}, status),
React.DOM.span({'className': 'progress'}, progressIndicator),
React.DOM.span({'className': 'actions'}, actions),
])
},
renderAttachments: function(someAttachments) {
return React.DOM.div({'className':'cardAttachmentWrapper'}, [
React.DOM.div({'className': 'cardAttachments'}, [
React.DOM.h3({'className': 'summaryText'}, "Attachments"),
// React.DOM.p({'className': 'summaryText'}, someAttachments.length + ' files attached'),
this.renderSkippedFiles(),
React.DOM.ul({'className': 'attachmentList'},
MochiKit.Base.map(MochiKit.Base.method(this, 'renderAttachment'), someAttachments)
)
]),
React.DOM.div({
'className': 'cardUploadAttachments',
'onClick': MochiKit.Base.bind(function() { this.refs['attachmentInput'].getDOMNode().click() }, this),
'onDragOver': this.handleOnDragOver,
'onDrop': this.handleOnDrop,
},[
React.DOM.p({}, "Drag and drop your files here"),
React.DOM.p({}, "or"),
React.DOM.input({
'type': 'file',
'id': 'attachmentInput',
'className': 'attachmentInput',
'name': 'attachmentInput',
'ref': 'attachmentInput',
'onChange': this.handleFileSelect,
'multiple': true
}),
React.DOM.a({
'className': 'button',
'onDragOver': this.handleOnDragOver,
'onDrop': this.handleOnDrop,
}, "select files"),
])
])
},
//............................................................................
render: function () {
var classes = {
'edit': true
@@ -618,8 +831,9 @@ console.log("DROP"); //, anEvent);
this.renderTags(this.props['tags']),
this.renderFields(this.fields()),
this.renderAddNewField(),
this.renderAttachments(MochiKit.Base.values(this.props['attachments'])),
this.renderNotes(this.props['notes']),
this.renderDirectLogins(this.props['directLogins'])
this.renderDirectLogins(this.props['directLogins']),
])
]),
this.props['ask'] ? Clipperz.PM.UI.Components.DialogBox(this.props['ask']) : null

View File

@@ -63,6 +63,7 @@ Clipperz.PM.UI.Components.Cards.ListClass = React.createClass({
result = React.DOM.li({'className':Clipperz.PM.UI.Components.classNames(classes), 'onClick': this.handleClick, 'key':anItem['_reference'], 'data-reference':anItem['_reference'], 'data-label':anItem['label']}, [
// React.DOM.span({'className':'favicon'}, Clipperz.PM.UI.Components.Cards.FavIcon({'src':anItem['favicon']})),
React.DOM.span({'className':'label'}, anItem['label']),
React.DOM.span({'className':'attachmentsCount'}, anItem['attachmentsCount'] ? 'attachment' : ''),
]);
}

View File

@@ -130,7 +130,7 @@ Clipperz.PM.UI.Components.Cards.TagEditorClass = React.createClass({
renderEditField: function () {
return [
React.DOM.input({'type':'text', 'list':'tagListData', 'onKeyDown':this.handleKeyDown, 'onBlur':this.handleBlur, 'placeholder': "tag"}),
// React.DOM.input({'type':'text', 'list':'tagListData', 'onKeyDown':this.handleKeyDown, 'onBlur':this.handleBlur, 'placeholder': "tag"}),
React.DOM.datalist({'id':'tagListData'}, MochiKit.Base.map(function (aTag) { return React.DOM.option({}, aTag); }, this.listOfTagsNotUsedYet()))
];
},
@@ -145,6 +145,7 @@ Clipperz.PM.UI.Components.Cards.TagEditorClass = React.createClass({
return React.DOM.div({'className':Clipperz.PM.UI.Components.classNames(classes)}, [
React.DOM.ul({},[
MochiKit.Base.map(this.renderTag, this.props['selectedTags']),
this.isReadOnly() ? null : React.DOM.li({}, React.DOM.input({'type':'text', 'list':'tagListData', 'onKeyDown':this.handleKeyDown, 'onBlur':this.handleBlur, 'placeholder': "tag"})),
]),
this.isReadOnly() ? null : this.renderEditField()
]);

View File

@@ -63,6 +63,7 @@ Clipperz.PM.UI.Components.Cards.TextAreaClass = React.createClass({
handleKeyDown: function (anEvent) {
switch (anEvent.keyCode) {
case 27: // escape
// console.log("ESCAPE");
Mousetrap.trigger('esc');
break;
}
@@ -78,7 +79,7 @@ Clipperz.PM.UI.Components.Cards.TextAreaClass = React.createClass({
recalculateSize_1: function () {
var node = this.getDOMNode();
node.style.height = 'auto';
node.style.height = '33px';
node.style.height = node.scrollHeight + 'px';
window.scrollTo(window.scrollLeft, (node.scrollTop + node.scrollHeight));
},

View File

@@ -116,6 +116,17 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({
return result;
},
//----------------------------------------------------------------------------
handleGetAttachment: function (anAttachment) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'getAttachment', anAttachment);
},
handleCancelDownload: function (anAttachment) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'cancelAttachment', anAttachment);
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'closeAttachment', anAttachment);
},
//----------------------------------------------------------------------------
renderEmpty: function () {
@@ -251,6 +262,132 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({
//............................................................................
renderAttachmentProgress: function(aStatus, aServerStatus, aProgress) {
var result;
var broken = (! aServerStatus && (! aStatus || aStatus == 'CANCELED' || aStatus == 'FAILED' || aStatus == 'DONE'));
result = null;
if (aStatus == 'UPLOADING' || aStatus == 'DOWNLOADING') {
result = Clipperz.PM.UI.Components.RadialProgressIndicator({
'progress': aProgress,
'border': 1
});
} else if (! broken && aStatus != 'DONE' && aServerStatus != 'AVAILABLE') {
result = Clipperz.PM.UI.Components.RadialProgressIndicator({
'progress': 0,
'border': 1,
'additionalClasses': ['waiting'],
});
}
return result;
},
renderAttachmentStatus: function(aStatus, aServerStatus, aProgress) {
var result;
var status = aStatus ? aStatus : false;
result = null;
if (status == 'FAILED') {
result = React.DOM.span({'className': 'failed'}, "failed");
} else if (status == 'UPLOADING' || status == 'DOWNLOADING') {
var actionSymbol = (status == 'UPLOADING') ? "\u2b06" : "\u2b07";
result = React.DOM.span({'className': 'progressStatus'}, actionSymbol + Math.floor(aProgress*100) + '%');
} else if (aServerStatus != 'AVAILABLE') {
switch(status) {
case 'CANCELED':
result = React.DOM.span({'className': 'broken'}, "canceled");
break;
case 'DONE':
result = React.DOM.span({'className': 'broken'}, "failed");
break;
case false:
result = React.DOM.span({'className': 'broken'}, "failed");
break;
default:
result = React.DOM.span({'className': 'waiting'}, "waiting");
}
}
return result;
},
renderAttachmentActions: function(aStatus, aServerStatus, anAttachment) {
var result;
result = null;
if (aStatus == 'DOWNLOADING') {
result = React.DOM.a({
'className': 'cancel',
'onClick': MochiKit.Base.method(this, 'handleCancelDownload', anAttachment)
}, "remove field");
} else if (aServerStatus == 'AVAILABLE') {
result = React.DOM.a({
'className': 'download',
'onClick': MochiKit.Base.method(this, 'handleGetAttachment', anAttachment),
}, "\u2b07");
}
return result;
},
renderAttachment: function (anAttachment) {
var result;
if (this.props['attachmentQueueInfo'].elementFetchCallback != null) {
var queueInfo = this.props['attachmentQueueInfo'].elementFetchCallback(anAttachment._reference) || [];
var queueStatus = queueInfo['status'];
var serverStatus = this.props['attachmentServerStatus'][anAttachment._reference];
var broken = (! serverStatus && (! queueStatus || queueStatus == 'CANCELED'));
var status = this.renderAttachmentStatus(queueStatus, serverStatus, queueInfo['requestProgress']);
var actions = this.renderAttachmentActions(queueStatus, serverStatus, anAttachment['_attachment']);
var progressIndicator = this.renderAttachmentProgress(queueStatus, serverStatus, queueInfo['requestProgress']);
result = React.DOM.li({
'className': (broken) ? 'broken' : '',
'key': anAttachment._reference
}, [
React.DOM.span({'className': 'contentType'}, Clipperz.PM.DataModel.Attachment.contentTypeIcon(anAttachment.contentType)),
React.DOM.span({'className': 'meta'}, [
React.DOM.span({'className': 'name'}, anAttachment.name),
React.DOM.span({'className': 'size'}, filesize(anAttachment.size)),
]),
React.DOM.span({'className': 'status'}, status),
React.DOM.span({'className': 'progress'}, progressIndicator),
React.DOM.span({'className': 'actions'}, actions),
])
} else {
result = null;
}
return result;
},
renderAttachments: function(someAttachments) {
var result;
//console.log("View props:", this.props, someAttachments);
if (someAttachments.length > 0) {
result = React.DOM.div({'className': 'cardAttachments'}, [
React.DOM.h3({'className': 'summaryText'}, "Attachments"),
// React.DOM.p({'className': 'summaryText'}, someAttachments.length + ' files attached'),
React.DOM.ul({'className': 'attachmentList'},
MochiKit.Base.map(MochiKit.Base.method(this, 'renderAttachment'), someAttachments)
)
]);
} else {
result = [];
}
return result;
},
//............................................................................
renderCard: function () {
var classes = {
'view': true,
@@ -263,8 +400,9 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({
this.renderLabel(this.props['label']),
this.renderTags(this.props['tags']),
this.renderFields(this.props['fields']),
this.renderAttachments(MochiKit.Base.values(this.props['attachments'])),
this.renderNotes(this.props['notes']),
this.renderDirectLogins(this.props['directLogins'])
this.renderDirectLogins(this.props['directLogins']),
]),
this.props['ask'] ? Clipperz.PM.UI.Components.DialogBox(this.props['ask']) : null
]);

View File

@@ -0,0 +1,139 @@
/*
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');
Clipperz.PM.UI.Components.DocumentEncryptionSandboxClass = React.createClass({
getInitialState: function() {
return {
'documentMeta': null,
'encryptedDocument': null,
};
},
//=========================================================================
// Event Handlers
//=========================================================================
handleFileSelect: function(anEvent) {
// console.log("handleFileSelect: files:", anEvent.target.files);
var fileToRead = anEvent.target.files[0];
// console.log(fileToRead);
// return;
if (fileToRead) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'uploadNewFile', null, fileToRead);
}
},
//=========================================================================
// File Handling Methods
//=========================================================================
// handleEncryptionSynchronously: function(aKey, aByteArray) {
// var encryptedDocument;
// var startTime, endTime;
// console.log("Encrypting...");
// startTime = Date.now()
// encryptedDocument = Clipperz.Crypto.AES.encrypt(aKey, aByteArray);
// endTime = Date.now();
// console.log("Finished! Encryption took", (endTime-startTime)/1000, "seconds.");
// this.setState({
// 'encryptedDocument': encryptedDocument,
// });
// },
downloadFile: function(aFileReference) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadFile', aFileReference);
},
//=========================================================================
// Render Methods
//=========================================================================
renderUploadButton: function() {
var result;
if (window.File && window.FileReader && window.FileList && window.Blob) {
result = React.DOM.input({
'type': 'file',
'id': 'files',
'name': 'files',
'onChange': this.handleFileSelect,
'multiple': false
})
} else {
result = React.DOM.p({}, "Browser doesn't support the FileReader API");
}
return result;
},
renderQueue: function() {
return MochiKit.Base.map(MochiKit.Base.bind(function(anElement) {
return React.DOM.li({},[
React.DOM.ul({}, [
React.DOM.li({}, anElement['meta']['name']),
React.DOM.li({}, anElement['meta']['size']),
React.DOM.li({}, anElement['status']),
React.DOM.li({}, Math.floor((anElement['currentByte']/anElement['meta']['size'])*100)+'%'),
React.DOM.li({}, React.DOM.button({
'disabled': ! anElement.isDataReady,
'onClick': MochiKit.Base.method(this, 'downloadFile', anElement['reference']),
}, "Download File")),
])
]);
}, this), this.props['attachmentQueueInfo']);
},
render: function () {
return React.DOM.div({
'style': {
'position': 'fixed',
'width': '80%',
'height': '80%',
'background': 'white',
'color': 'black',
'top': '10%',
'left': '10%',
'border': '2px solid black',
'zIndex': '10'
}
}, [
React.DOM.p({}, "This is the doc encryption proof of concept."),
React.DOM.p({}, "*Status*: working streaming encryption and decryption! Current size limit is 250MB"),
this.renderUploadButton(),
React.DOM.ul({},this.renderQueue()),
]);
}
//=========================================================================
});
Clipperz.PM.UI.Components.DocumentEncryptionSandbox = React.createFactory(Clipperz.PM.UI.Components.DocumentEncryptionSandboxClass);

View File

@@ -135,10 +135,10 @@ Clipperz.PM.UI.Components.ExtraFeatures.OTPClass = React.createClass({
'</html>'
);
newWindow.document.close();
newWindow.focus();
newWindow.print();
newWindow.close();
// newWindow.document.close();
// newWindow.focus();
// newWindow.print();
// newWindow.close();
},
//=========================================================================

View File

@@ -34,17 +34,18 @@ 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,
'style': React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
'locked': React.PropTypes.bool,
// 'mediaQueryStyle': React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
// 'cards': React.PropTypes.deferred.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,
'locked': React.PropTypes.bool,
'attachmentQueueInfo': React.PropTypes.object.isRequired,
// 'mediaQueryStyle': React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
// 'cards': React.PropTypes.deferred.isRequired
},
getInitialState: function () {
@@ -67,10 +68,11 @@ Clipperz.PM.UI.Components.Pages.MainPageClass = React.createClass({
classes[this.props['style']] = true;
result = React.DOM.div({'key':'mainPage', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
Clipperz.PM.UI.Components.AttachmentQueueBox(this.props),
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),
this.props['ask'] ? Clipperz.PM.UI.Components.DialogBox(this.props['ask']) : null
this.props['ask'] ? Clipperz.PM.UI.Components.DialogBox(this.props['ask']) : null,
]);
}

View File

@@ -29,11 +29,12 @@ Clipperz.PM.UI.Components.Panels.MainPanelClass = React.createClass({
//=========================================================================
propTypes: {
'allTags': React.PropTypes.array,
'messageBox': React.PropTypes.object.isRequired,
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
'features': React.PropTypes.array.isRequired,
'style': React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
'allTags': React.PropTypes.array,
'messageBox': React.PropTypes.object.isRequired,
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
'features': React.PropTypes.array.isRequired,
'style': React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
'attachmentQueueInfo': React.PropTypes.object.isRequired,
},
style: function () {

View File

@@ -0,0 +1,117 @@
/*
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');
Clipperz.PM.UI.Components.RadialProgressIndicatorClass = React.createClass({
propTypes: {
'progress': React.PropTypes.number.isRequired,
},
getInitialState: function() {
return {};
},
//=========================================================================
getPathDefinition: function(aRadius, aProgress) {
aProgress = (aProgress <= 1) ? aProgress : 1;
aProgress = (aProgress >= 0) ? aProgress : 0;
var pi = Math.PI;
var radiantAngle = 2 * pi * aProgress;
var x = Math.sin( radiantAngle ) * aRadius;
var y = Math.cos( radiantAngle ) * - aRadius;
var mid = (aProgress > 0.5) ? 1 : 0;
return 'M 0 0 ' + // Start from origin
'v ' + (-aRadius) +' ' + // Straight vertical up
'A ' + aRadius + ' ' + aRadius + ' 1 ' + // Arc, vertical radius, horizontal radius, xAxisRotate (?)...
mid + ' 1 ' + // ...lrge arc flag,
x + ' ' + // ...destination x
y + ' z'; // ...destination y, close path
//[x,y].forEach(function( d ){
// d = Math.round( d * 1e3 ) / 1e3;
//});
},
//=========================================================================
getAdditionalClassesString(aList) {
var result;
aList = aList || [];
result = aList.join(' ');
return ' '+result;
},
render: function () {
var border;
var additionalClasses = this.getAdditionalClassesString(this.props['additionalClasses']);
var radius = Clipperz.PM.UI.Components.DEFAULT_RADIUS;
var borderSize = (this.props['border']) ? this.props['border'] : 0;
var boxSize = 2 * (radius + borderSize) + 2;
var center = boxSize/2;
border = null;
if (this.props['border']) {
border = React.DOM.circle({
'className': 'border',
'cx': center,
'cy': center,
'r': radius+this.props['border'],
});
}
return React.DOM.svg({
'className': 'radialProgressIndicator' + additionalClasses,
'viewBox': '0 0 ' + boxSize + ' ' + boxSize,
}, [
border,
React.DOM.circle({
'className': 'background',
'cx': center,
'cy': center,
'r': radius,
}),
React.DOM.path({
'className': 'progress',
'transform': 'translate(' + (center) + ', ' + (center) + ')',
'd': this.getPathDefinition(radius, this.props['progress'])
})
])
}
//=========================================================================
});
Clipperz.PM.UI.Components.RadialProgressIndicator = React.createFactory(Clipperz.PM.UI.Components.RadialProgressIndicatorClass);
Clipperz.PM.UI.Components.DEFAULT_RADIUS = 10; // You can resize the SVG object with CSS anyway

View File

@@ -36,6 +36,10 @@ Clipperz.PM.UI.Components.SelectionsClass = React.createClass({
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectRecentCards');
},
selectWithAttachments: function (anEvent) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectCardsWithAttachments');
},
selectUntaggedCards: function (anEvent) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectUntaggedCards');
},
@@ -76,6 +80,7 @@ Clipperz.PM.UI.Components.SelectionsClass = React.createClass({
var filterValue;
//console.log("SELECTIONS PROPS", this.props);
//console.log("withAttachmentCardsCount", this.props['withAttachmentCardsCount']);
tagInfo = this.props['tags'] ? this.props['tags'] : {};
tags = MochiKit.Base.filter(Clipperz.PM.DataModel.Record.isRegularTag, MochiKit.Base.keys(tagInfo)).sort(Clipperz.Base.caseInsensitiveCompare);
archivedCardsCount = this.props['archivedCardsCount'];
@@ -94,7 +99,10 @@ Clipperz.PM.UI.Components.SelectionsClass = React.createClass({
React.DOM.span({'className':'label'}, "Recent"),
React.DOM.span({'className':'count'}, this.props['allCardsCount'] ? '10' : '-')
]),
// React.DOM.li({'className':'untaggedCards', 'onClick': this.selectUntaggedCards}, "Untagged - " + this.props['untaggedCardsCount'])
React.DOM.li({'className':'withAttachmentCards', 'onClick': this.selectWithAttachments}, [
React.DOM.span({'className':'label'}, "With attachments"),
React.DOM.span({'className':'count'}, this.props['withAttachmentCardsCount'] ? this.props['withAttachmentCardsCount'] : '-')
]),
React.DOM.li({'className':'untaggedCards', 'onClick': this.selectUntaggedCards}, [
React.DOM.span({'className':'label'}, "Untagged"),
React.DOM.span({'className':'count'}, this.props['untaggedCardsCount'] ? this.props['untaggedCardsCount'] : '-')

View File

@@ -35,6 +35,7 @@ Clipperz.PM.UI.MainController = function() {
this._isSelectionPanelOpen = false;
this._isSettingsPanelOpen = false;
this._isAttachmentQueueBoxOpen = false;
this._pageStack = ['loadingPage'];
this._overlay = new Clipperz.PM.UI.Components.Overlay();
@@ -75,9 +76,9 @@ Clipperz.PM.UI.MainController = function() {
'importCards',
'downloadExport',
'updateProgress',
'toggleSelectionPanel', 'hideSelectionPanel', 'toggleSettingsPanel',
'toggleSelectionPanel', 'hideSelectionPanel', 'toggleSettingsPanel', 'toggleAttachmentQueueBox',
'matchMediaQuery', 'unmatchMediaQuery',
'selectAllCards', 'selectRecentCards', 'search', 'tagSelected', 'selectUntaggedCards',
'selectAllCards', 'selectRecentCards', 'selectCardsWithAttachments', 'selectUntaggedCards', 'tagSelected', 'search',
'refreshCardEditDetail',
'saveCardEdits', 'cancelCardEdits',
'selectCard',
@@ -90,9 +91,16 @@ Clipperz.PM.UI.MainController = function() {
'closeHelp',
'downloadOfflineCopy',
'runDirectLogin', 'removeDirectLogin',
'exitSearch'
'exitSearch',
'updateAttachmentQueueInfo', 'addAttachment', 'removeAttachment', 'getAttachment', 'cancelAttachment', 'closeAttachmentNotification',
]);
this._attachmentController = new Clipperz.PM.UI.AttachmentController({
'uploadMessageCallback': MochiKit.Base.method(this, 'uploadMessageCallback'),
'downloadMessageCallback': MochiKit.Base.method(this, 'downloadMessageCallback'),
'reloadServerStatusCallback': MochiKit.Base.method(this, 'reloadAttachmentServerStatusCallback')
});
Mousetrap.bind(['/'], MochiKit.Base.method(this, 'focusOnSearch'));
Mousetrap.bind(['left', 'h', 'esc'], MochiKit.Base.method(this, 'exitCurrentSelection'));
@@ -205,7 +213,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
renderPages: function (pages) {
var self = this;
MochiKit.Iter.forEach(pages, function (aPageName) {
//console.log("RENDERING", aPageName);
self._pages[aPageName] = React.render(
Clipperz.PM.UI.Components.Pages[self.capitaliseFirstLetter(aPageName)](self.pageProperties(aPageName)),
MochiKit.DOM.getElement(aPageName)
@@ -437,7 +444,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
getPassphraseDelegate = MochiKit.Base.partial(MochiKit.Async.succeed, passphrase);
user = new Clipperz.PM.DataModel.User({'username':oldUser.username(), 'getPassphraseFunction':getPassphraseDelegate});
deferredResult = new Clipperz.Async.Deferred('MainController.unlock_handler', {trace:true});
deferredResult = new Clipperz.Async.Deferred('MainController.unlock_handler', {trace:false});
deferredResult.addMethod(unlockPage, 'setProps', {'disabled': true});
@@ -539,7 +546,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
},
setUser: function (aUser) {
//console.log("SET USER", aUser);
this._user = aUser;
return this._user;
},
@@ -604,11 +610,30 @@ Clipperz.log("THE BROWSER IS OFFLINE");
return deferredResult;
},
collectAttachmentInfo: function(anAttachment) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred('MainController.collectAttachmentInfo', {trace:false});
deferredResult.setValue('_attachment'); // The object itself, maybe this should be the only value being passed
deferredResult.addMethod(anAttachment, 'reference');
deferredResult.setValue('_reference');
deferredResult.addMethod(anAttachment, 'name');
deferredResult.setValue('name');
deferredResult.addMethod(anAttachment, 'contentType');
deferredResult.setValue('contentType');
deferredResult.addMethod(anAttachment, 'size');
deferredResult.setValue('size');
deferredResult.values();
deferredResult.callback(anAttachment);
return deferredResult;
},
collectRecordInfo: function (aRecord) {
var deferredResult;
//console.log("collectRecordInfo");
deferredResult = new Clipperz.Async.Deferred('MainController.collectRecordInfo', {trace:false});
deferredResult.setValue('_record');
deferredResult.addMethod(aRecord, 'reference');
@@ -631,12 +656,21 @@ Clipperz.log("THE BROWSER IS OFFLINE");
deferredResult.addCallback(Clipperz.Async.collectAll);
deferredResult.setValue('fields');
deferredResult.addMethod(aRecord, 'attachments');
deferredResult.addCallback(MochiKit.Base.values);
deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.method(this, 'collectAttachmentInfo'));
deferredResult.addCallback(Clipperz.Async.collectAll);
deferredResult.setValue('attachments');
deferredResult.addMethod(aRecord, 'directLogins');
deferredResult.addCallback(MochiKit.Base.values);
deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.method(this, 'collectDirectLoginInfo'));
deferredResult.addCallback(Clipperz.Async.collectAll);
deferredResult.setValue('directLogins');
deferredResult.addMethod(aRecord, 'getAttachmentServerStatus');
deferredResult.setValue('attachmentServerStatus');
deferredResult.values();
deferredResult.callback(aRecord);
@@ -668,8 +702,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
deferredResult = new Clipperz.Async.Deferred('MainController.updateSelectedCard', {trace:false});
deferredResult.addMethod(this.user(), 'getRecord', someInfo['reference']);
// deferredResult.addMethod(this, function(d) {console.log(d); return d;});
deferredResult.addMethod(this, 'collectRecordInfo');
deferredResult.addMethod(this, 'setPageProperties', 'mainPage', 'selectedCard');
if ((this.mediaQueryStyle() == 'narrow') && shouldShowCardDetail) {
@@ -774,6 +806,10 @@ Clipperz.log("THE BROWSER IS OFFLINE");
filterCriteria = MochiKit.Base.operator.truth;
sortCriteria = Clipperz.Base.reverseComparator(MochiKit.Base.keyComparator('_accessDate'));
rangeFilter = function (someCards) { return someCards.slice(0, 10)};
} else if (aFilter['type'] == 'WITH_ATTACHMENTS') {
filterCriteria = function (aRecordInfo) { return aRecordInfo.attachmentsCount > 0; };
sortCriteria = Clipperz.Base.caseInsensitiveKeyComparator('label');
rangeFilter = MochiKit.Base.operator.identity;
} else if (aFilter['type'] == 'SEARCH') {
filterCriteria = this.regExpFilterGenerator(Clipperz.PM.DataModel.Record.regExpForSearch(aFilter['value']));
sortCriteria = Clipperz.Base.caseInsensitiveKeyComparator('label');
@@ -879,7 +915,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
MochiKit.Base.method(this.user(), 'getPreference', 'lock'),
MochiKit.Base.bind(function (someLockInfo) {
if (this._lockTimeout) {
// console.log("clearing previous lock timer");
clearTimeout(this._lockTimeout);
}
@@ -902,7 +937,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
? MochiKit.Async.succeed
: MochiKit.Base.partial(MochiKit.Base.filter, function (someRecordInfo) { return ! someRecordInfo['_isArchived']; });
return Clipperz.Async.callbacks("MainController.getUntaggedCardsCount", [
return Clipperz.Async.callbacks("MainController.getAllCardsCount", [
MochiKit.Base.method(this.user(), 'getRecords'),
MochiKit.Base.partial(MochiKit.Base.map, Clipperz.Async.collectResults("collectResults", {'_fullLabel':MochiKit.Base.methodcaller('fullLabel'), '_isArchived':MochiKit.Base.methodcaller('isArchived')}, {trace:false})),
Clipperz.Async.collectAll,
@@ -910,7 +945,23 @@ Clipperz.log("THE BROWSER IS OFFLINE");
function (someCards) { return someCards.length; },
], {trace:false});
},
getCardsWithAttachmentsCount: function () {
var archivedCardsFilter = this.shouldIncludeArchivedCards()
? MochiKit.Async.succeed
: MochiKit.Base.partial(MochiKit.Base.filter, function (someRecordInfo) { return ! someRecordInfo['_isArchived']; });
return Clipperz.Async.callbacks("MainController.getCardsWithAttachmentsCount", [
MochiKit.Base.method(this.user(), 'getRecords'),
MochiKit.Base.partial(MochiKit.Base.map, Clipperz.Async.collectResults("collectResults", {'attachmentsCount':MochiKit.Base.methodcaller('attachmentsCount'), '_isArchived':MochiKit.Base.methodcaller('isArchived')}, {trace:false})),
Clipperz.Async.collectAll,
archivedCardsFilter,
function (aResult) {
return MochiKit.Base.filter(function (aRecordInfo) { return aRecordInfo['attachmentsCount'] > 0; }, aResult).length;
}
], {trace:false});
},
getArchivedCardsCount: function () {
return Clipperz.Async.callbacks("MainController.getArchivedCardsCount", [
MochiKit.Base.method(this.user(), 'getRecords'),
@@ -961,6 +1012,8 @@ Clipperz.log("THE BROWSER IS OFFLINE");
MochiKit.Base.method(this, 'setPageProperties', 'mainPage', 'allTags'),
MochiKit.Base.method(this, 'getAllCardsCount'),
MochiKit.Base.method(this, 'setPageProperties', 'mainPage', 'allCardsCount'),
MochiKit.Base.method(this, 'getCardsWithAttachmentsCount'),
MochiKit.Base.method(this, 'setPageProperties', 'mainPage', 'withAttachmentCardsCount'),
MochiKit.Base.method(this, 'getArchivedCardsCount'),
MochiKit.Base.method(this, 'setPageProperties', 'mainPage', 'archivedCardsCount'),
MochiKit.Base.method(this, 'getUntaggedCardsCount'),
@@ -979,9 +1032,12 @@ Clipperz.log("THE BROWSER IS OFFLINE");
//=========================================================================
runApplication: function (anUser) {
window.history.replaceState({selectedCardInfo: null}, "", window.location.toString());
window.history.pushState({selectedCardInfo: null}, "", window.location.toString()); // Hack to support filters undo with no other actions
this.moveInPage(this.currentPage(), 'mainPage');
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'enableLock');
this.resetLockTimeout();
this.registerHistoryHandler();
return this.renderAccountData();
},
@@ -1129,11 +1185,13 @@ Clipperz.log("THE BROWSER IS OFFLINE");
this.slidePage(MochiKit.DOM.getElement(fromPage), MochiKit.DOM.getElement(toPage), 'LEFT');
this.setCurrentPage(toPage);
if (shouldAddItemToHistory) {
window.history.pushState({'fromPage': fromPage, 'toPage': toPage}, "");
} else {
// Skipping push on page change: history needs to be more granular
// New states are pushed on user actions (view card, back to main page, ...)
// if (shouldAddItemToHistory) {
// window.history.pushState({'fromPage': fromPage, 'toPage': toPage}, "");
// } else {
//console.log("Skip HISTORY");
}
// }
} else {
//console.log("No need to move in the same page");
}
@@ -1202,6 +1260,32 @@ Clipperz.log("THE BROWSER IS OFFLINE");
}
},
attachmentController: function () {
return this._attachmentController;
},
attachmentQueueInfo: function() {
var queue;
var notifications;
var elementFetchCallback;
if (this._attachmentController) {
queue = this._attachmentController.getQueueInfo();
notifications = this._attachmentController.getNotificationsInfo();
elementFetchCallback = MochiKit.Base.method(this._attachmentController, 'getQueueElement');
} else {
queue = [];
notifications = [];
elementFetchCallback = null;
}
return {
'queue': queue,
'notifications': notifications,
'elementFetchCallback': elementFetchCallback,
}
},
//-------------------------------------------------------------------------
messageBoxContent: function () {
@@ -1211,7 +1295,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
message = "";
level = 'HIDE';
//console.log("messageBox - this.user()", this.user());
if (this.featureSet() == 'EXPIRED') {
message = "Expired subscription";
level = 'ERROR';
@@ -1261,12 +1344,14 @@ Clipperz.log("THE BROWSER IS OFFLINE");
'messageBox': this.messageBoxContent(),
'userInfo': this.userInfo(),
'accountInfo': this.userAccountInfo(),
'selectionPanelStatus': this.isSelectionPanelOpen() ? 'OPEN' : 'CLOSED',
'settingsPanelStatus': this.isSettingsPanelOpen() ? 'OPEN' : 'CLOSED',
'selectionPanelStatus': this.isSelectionPanelOpen() ? 'OPEN' : 'CLOSED',
'settingsPanelStatus': this.isSettingsPanelOpen() ? 'OPEN' : 'CLOSED',
'attachmentQueueBoxStatus': this.isAttachmentQueueBoxOpen() ? 'OPEN' : 'CLOSED',
'featureSet': this.featureSet(),
'features': this.features(),
'proxyInfo': this.proxyInfo(),
'locked': false
'locked': false,
'attachmentQueueInfo': this.attachmentQueueInfo(),
// 'shouldIncludeArchivedCards': this.shouldIncludeArchivedCards(),
// 'cards': …,
// 'tags': …,
@@ -1274,6 +1359,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
};
} else if (aPageName == 'cardDetailPage') {
extraProperties = {
'attachmentQueueInfo': this.attachmentQueueInfo(),
};
} else if (aPageName == 'errorPage') {
extraProperties = {
@@ -1367,6 +1453,15 @@ Clipperz.log("THE BROWSER IS OFFLINE");
this.refreshCurrentPage();
},
isAttachmentQueueBoxOpen: function() {
return this._isAttachmentQueueBoxOpen;
},
toggleAttachmentQueueBox_handler: function (anEvent) {
this._isAttachmentQueueBoxOpen = !this._isAttachmentQueueBoxOpen;
this.refreshCurrentPage();
},
//----------------------------------------------------------------------------
selectedCardInfo: function () {
@@ -1487,6 +1582,8 @@ Clipperz.log("THE BROWSER IS OFFLINE");
selectCard_handler: function (someInfo, shouldUpdateCardDetail) {
this.selectCard(someInfo, shouldUpdateCardDetail);
window.history.pushState({selectedCardInfo: someInfo}, "", window.location.toString());
// console.log("pushing state");
},
refreshCardEditDetail_handler: function (aRecordReference) {
@@ -1689,6 +1786,10 @@ Clipperz.log("THE BROWSER IS OFFLINE");
},
cancelCardEdits_handler: function (aRecordReference) {
return this.cancelCardEdits(aRecordReference);
},
cancelCardEdits: function (aRecordReference) {
var currentPage = this.pages()[this.currentPage()];
var self = this;
var wasBrandNew;
@@ -1699,7 +1800,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
function (aValue) { wasBrandNew = aValue },
MochiKit.Base.method(this.user(), 'hasPendingChanges'),
//function (aValue) { console.log("2- USER.hasPendingChanges()", aValue); return aValue; },
Clipperz.Async.deferredIf('HasPendingChanges',[
MochiKit.Base.method(self, 'ask', {
'question': "There are pending changes to your card. Ignore changes?",
@@ -1808,7 +1908,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
cloneCard_handler: function (anEvent) {
var cardInfo;
//console.log("CLONE CARD", anEvent['reference']);
return Clipperz.Async.callbacks("MainController.cloneCard_handler", [
MochiKit.Base.method(this.user(), 'getRecord', anEvent['reference']),
MochiKit.Base.method(this.user(), 'cloneRecord'),
@@ -1827,6 +1926,11 @@ Clipperz.log("THE BROWSER IS OFFLINE");
MochiKit.Base.method(this, 'refreshUI'),
], {trace:false});
},
isPageInEditMode: function() {
var currentPage = this.pages()[this.currentPage()];
return currentPage.props['mode'] == 'edit';
},
enterEditMode: function () {
var currentPage = this.pages()[this.currentPage()];
@@ -1844,8 +1948,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
},
editCard_handler: function (anEvent) {
//console.log("EDIT CARD", anEvent['reference']);
// this.pages()[this.currentPage()].setProps({'mode': 'edit'});
this.enterEditMode();
},
@@ -1881,11 +1983,16 @@ Clipperz.log("THE BROWSER IS OFFLINE");
goBackToMainPage_handler: function (anEvent) {
this.goBackToMainPage(anEvent);
window.history.pushState({selectedCardInfo: null}, "", window.location.toString());
},
//============================================================================
selectAllCards_handler: function () {
this.selectAllCards();
},
selectAllCards: function () {
this.setPageProperties('mainPage', 'searchTerm', '');
this.resetSelectedCard();
this.setFilter('ALL');
@@ -1900,6 +2007,13 @@ Clipperz.log("THE BROWSER IS OFFLINE");
return this.refreshSelectedCards();
},
selectCardsWithAttachments_handler: function () {
this.resetSelectedCard();
this.setFilter('WITH_ATTACHMENTS');
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'hideSelectionPanel');
return this.refreshSelectedCards();
},
search_handler: function (aValue) {
this.resetSelectedCard();
@@ -2044,7 +2158,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
prevCardInfo = this.previousCardInfo();
shouldUpdateCardDetail = this.shouldShowCardDetailWhenMovingBetweenCardsUsingKeys();
//console.log("PREV CARD INFO", prevCardInfo);
if (prevCardInfo != null) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectCard', prevCardInfo, shouldUpdateCardDetail);
}
@@ -2056,7 +2170,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
nextCardInfo = this.nextCardInfo();
shouldUpdateCardDetail = this.shouldShowCardDetailWhenMovingBetweenCardsUsingKeys();
//console.log("NEXT CARD INFO", shouldUpdateCardDetail);
if (nextCardInfo != null) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectCard', nextCardInfo, shouldUpdateCardDetail);
}
@@ -2086,6 +2200,128 @@ Clipperz.log("THE BROWSER IS OFFLINE");
},
//============================================================================
updateAttachmentQueueInfo_handler: function(someProperties) {
this.setPageProperties(this.currentPage(), 'attachmentQueueInfo', this.attachmentQueueInfo());
},
addAttachment_handler: function(aRecord, aFile) { // aReference, someMetadata, aKey, aNonce
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("MainController.addCardAttachment_handler", {'trace':false});
deferredResult.addMethod(aRecord, 'createNewAttachment');
deferredResult.addCallback(MochiKit.Base.methodcaller('setFile', aFile));
deferredResult.addMethod(this.attachmentController(), 'addAttachment');
deferredResult.addCallback(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'refreshCardEditDetail', aRecord.reference());
deferredResult.callback();
return deferredResult;
},
uploadMessageCallback: function(someArguments, aProgressCallback) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("MainController.uploadMessageCallback", {'trace':false});
deferredResult.addMethod(this.user().connection(), 'uploadAttachment', someArguments, aProgressCallback);
deferredResult.callback();
return deferredResult;
},
//----------------------------------------------------------------------------
removeAttachment_handler: function(aRecord, anAttachment) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("MainController.removeAttachment_handler", {trace: false});
deferredResult.addCallback(MochiKit.Base.method(this, 'ask', {
'question': "Do you really want to delete this attachment?",
'possibleAnswers':{
'cancel': {'label':"No", 'isDefault':true, 'answer':MochiKit.Base.methodcaller('cancel', new MochiKit.Async.CancelledError())},
'revert': {'label':"Yes", 'isDefault':false, 'answer':MochiKit.Base.methodcaller('callback')}
}
})),
deferredResult.addMethod(this, 'cancelAttachment_handler', anAttachment);
deferredResult.addMethod(aRecord, 'removeAttachment', anAttachment);
deferredResult.addCallback(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'refreshCardEditDetail', aRecord.reference());
deferredResult.callback();
return deferredResult;
},
//----------------------------------------------------------------------------
getAttachment_handler: function(anAttachment) {
this.attachmentController().getAttachment(anAttachment);
},
downloadMessageCallback: function(anAttachment, aProgressCallback) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("MainController.downloadAttachment_handler", {'trace':false});
deferredResult.addMethod(this.user().connection(), 'downloadAttachment', {
'reference': anAttachment.reference()
}, aProgressCallback);
deferredResult.callback();
return deferredResult;
},
//----------------------------------------------------------------------------
cancelAttachment_handler: function(anAttachment) {
return this.attachmentController().cancelAttachment(anAttachment);
},
closeAttachmentNotification_handler: function(aNotificationId) {
return this.attachmentController().removeNotification(aNotificationId);
},
//----------------------------------------------------------------------------
reloadAttachmentServerStatusCallback: function(aRecord) {
return Clipperz.Async.callbacks("MainController.reloadAttachmentServerStatus_handler", [
MochiKit.Base.method(this.user(), 'getRecordDetail', aRecord),
MochiKit.Base.bind(function () {
if (this._selectedCardInfo && this._selectedCardInfo['reference']) {
return this.refreshUI(this._selectedCardInfo['reference']);
}
}, this),
], {trace:false});
},
//============================================================================
registerHistoryHandler: function() {
window.onpopstate = MochiKit.Base.method(this, 'handleOnpopstate');
},
handleOnpopstate: function (anEvent) {
if (this.filter().type != 'ALL') {
this.selectAllCards();
window.history.pushState(window.history.state, "", window.location.toString());
} else if(anEvent.state) {
if (this.isPageInEditMode()) {
window.history.pushState({selectedCardInfo: this.selectedCardInfo()}, "", window.location.toString());
this.cancelCardEdits(this.selectedCardReference());
} else {
if (anEvent.state['selectedCardInfo']) {
this.selectCard(anEvent.state['selectedCardInfo'], true);
} else {
this.selectCard(null, true);
this.goBackToMainPage();
}
}
}
// console.log('History changed', anEvent.state);
},
//============================================================================
/*
wrongAppVersion: function (anError) {
// this.pages()['errorPage'].setProps({message:anError.message});

View File

@@ -0,0 +1,30 @@
/*
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.Sound');
Clipperz.Sound.beep = function() {
var snd = new Audio("data:audio/wav;base64,//uQRAAAAWMSLwUIYAAsYkXgoQwAEaYLWfkWgAI0wWs/ItAAAGDgYtAgAyN+QWaAAihwMWm4G8QQRDiMcCBcH3Cc+CDv/7xA4Tvh9Rz/y8QADBwMWgQAZG/ILNAARQ4GLTcDeIIIhxGOBAuD7hOfBB3/94gcJ3w+o5/5eIAIAAAVwWgQAVQ2ORaIQwEMAJiDg95G4nQL7mQVWI6GwRcfsZAcsKkJvxgxEjzFUgfHoSQ9Qq7KNwqHwuB13MA4a1q/DmBrHgPcmjiGoh//EwC5nGPEmS4RcfkVKOhJf+WOgoxJclFz3kgn//dBA+ya1GhurNn8zb//9NNutNuhz31f////9vt///z+IdAEAAAK4LQIAKobHItEIYCGAExBwe8jcToF9zIKrEdDYIuP2MgOWFSE34wYiR5iqQPj0JIeoVdlG4VD4XA67mAcNa1fhzA1jwHuTRxDUQ//iYBczjHiTJcIuPyKlHQkv/LHQUYkuSi57yQT//uggfZNajQ3Vmz+Zt//+mm3Wm3Q576v////+32///5/EOgAAADVghQAAAAA//uQZAUAB1WI0PZugAAAAAoQwAAAEk3nRd2qAAAAACiDgAAAAAAABCqEEQRLCgwpBGMlJkIz8jKhGvj4k6jzRnqasNKIeoh5gI7BJaC1A1AoNBjJgbyApVS4IDlZgDU5WUAxEKDNmmALHzZp0Fkz1FMTmGFl1FMEyodIavcCAUHDWrKAIA4aa2oCgILEBupZgHvAhEBcZ6joQBxS76AgccrFlczBvKLC0QI2cBoCFvfTDAo7eoOQInqDPBtvrDEZBNYN5xwNwxQRfw8ZQ5wQVLvO8OYU+mHvFLlDh05Mdg7BT6YrRPpCBznMB2r//xKJjyyOh+cImr2/4doscwD6neZjuZR4AgAABYAAAABy1xcdQtxYBYYZdifkUDgzzXaXn98Z0oi9ILU5mBjFANmRwlVJ3/6jYDAmxaiDG3/6xjQQCCKkRb/6kg/wW+kSJ5//rLobkLSiKmqP/0ikJuDaSaSf/6JiLYLEYnW/+kXg1WRVJL/9EmQ1YZIsv/6Qzwy5qk7/+tEU0nkls3/zIUMPKNX/6yZLf+kFgAfgGyLFAUwY//uQZAUABcd5UiNPVXAAAApAAAAAE0VZQKw9ISAAACgAAAAAVQIygIElVrFkBS+Jhi+EAuu+lKAkYUEIsmEAEoMeDmCETMvfSHTGkF5RWH7kz/ESHWPAq/kcCRhqBtMdokPdM7vil7RG98A2sc7zO6ZvTdM7pmOUAZTnJW+NXxqmd41dqJ6mLTXxrPpnV8avaIf5SvL7pndPvPpndJR9Kuu8fePvuiuhorgWjp7Mf/PRjxcFCPDkW31srioCExivv9lcwKEaHsf/7ow2Fl1T/9RkXgEhYElAoCLFtMArxwivDJJ+bR1HTKJdlEoTELCIqgEwVGSQ+hIm0NbK8WXcTEI0UPoa2NbG4y2K00JEWbZavJXkYaqo9CRHS55FcZTjKEk3NKoCYUnSQ0rWxrZbFKbKIhOKPZe1cJKzZSaQrIyULHDZmV5K4xySsDRKWOruanGtjLJXFEmwaIbDLX0hIPBUQPVFVkQkDoUNfSoDgQGKPekoxeGzA4DUvnn4bxzcZrtJyipKfPNy5w+9lnXwgqsiyHNeSVpemw4bWb9psYeq//uQZBoABQt4yMVxYAIAAAkQoAAAHvYpL5m6AAgAACXDAAAAD59jblTirQe9upFsmZbpMudy7Lz1X1DYsxOOSWpfPqNX2WqktK0DMvuGwlbNj44TleLPQ+Gsfb+GOWOKJoIrWb3cIMeeON6lz2umTqMXV8Mj30yWPpjoSa9ujK8SyeJP5y5mOW1D6hvLepeveEAEDo0mgCRClOEgANv3B9a6fikgUSu/DmAMATrGx7nng5p5iimPNZsfQLYB2sDLIkzRKZOHGAaUyDcpFBSLG9MCQALgAIgQs2YunOszLSAyQYPVC2YdGGeHD2dTdJk1pAHGAWDjnkcLKFymS3RQZTInzySoBwMG0QueC3gMsCEYxUqlrcxK6k1LQQcsmyYeQPdC2YfuGPASCBkcVMQQqpVJshui1tkXQJQV0OXGAZMXSOEEBRirXbVRQW7ugq7IM7rPWSZyDlM3IuNEkxzCOJ0ny2ThNkyRai1b6ev//3dzNGzNb//4uAvHT5sURcZCFcuKLhOFs8mLAAEAt4UWAAIABAAAAAB4qbHo0tIjVkUU//uQZAwABfSFz3ZqQAAAAAngwAAAE1HjMp2qAAAAACZDgAAAD5UkTE1UgZEUExqYynN1qZvqIOREEFmBcJQkwdxiFtw0qEOkGYfRDifBui9MQg4QAHAqWtAWHoCxu1Yf4VfWLPIM2mHDFsbQEVGwyqQoQcwnfHeIkNt9YnkiaS1oizycqJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=");
snd.play();
};