Implemented Attachments in client

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -59,6 +59,31 @@
"setId": 0,
"iconIdx": 4
},
{
"icon": {
"paths": [
"M254.003 1024c-68.403 0-132.454-28.979-178.739-75.315-89.6-89.6-114.637-246.067 11.008-371.712 73.677-73.574 368.589-368.486 515.789-515.686 52.275-52.275 118.733-72.448 182.426-55.347 62.515 16.691 113.818 67.942 130.56 130.509 17.050 63.693-3.174 130.202-55.398 182.426l-493.158 493.21c-28.16 28.16-59.955 44.851-91.955 48.179-31.642 3.328-61.85-6.861-83.046-28.006-38.4-38.349-43.827-110.541 19.968-174.285l346.368-346.47c14.234-14.182 37.325-14.182 51.558 0 14.234 14.234 14.234 37.325 0 51.558l-346.419 346.419c-29.952 29.952-32.717 58.573-19.968 71.322 5.581 5.53 14.080 8.090 23.859 7.066 15.002-1.587 32.102-11.315 48.077-27.29l493.158-493.158c33.843-33.843 46.797-73.626 36.506-112.077-10.138-37.837-41.165-68.864-79.002-79.002-38.451-10.291-78.234 2.714-112.077 36.506-147.149 147.2-442.061 442.112-515.686 515.686-96.051 96.051-73.062 206.592-11.059 268.646 62.054 62.003 172.595 85.043 268.698-11.059l515.738-515.686c14.182-14.234 37.325-14.234 51.507 0 14.234 14.234 14.234 37.325 0 51.507l-515.738 515.738c-60.774 60.826-128.768 86.323-192.973 86.323z"
],
"tags": [
"paperclip",
"attachment"
],
"grid": 20,
"attrs": []
},
"attrs": [],
"properties": {
"id": 8,
"order": 41,
"prevSize": 24,
"code": 59392,
"name": "paperclip",
"ligatures": "attachment"
},
"setIdx": 8,
"setId": 0,
"iconIdx": 8
},
{
"icon": {
"paths": [
@ -304,6 +329,206 @@
"setId": 8,
"iconIdx": 64
},
{
"icon": {
"paths": [
"M917.806 229.076c-22.212-30.292-53.174-65.7-87.178-99.704s-69.412-64.964-99.704-87.178c-51.574-37.82-76.592-42.194-90.924-42.194h-496c-44.112 0-80 35.888-80 80v864c0 44.112 35.888 80 80 80h736c44.112 0 80-35.888 80-80v-624c0-14.332-4.372-39.35-42.194-90.924zM785.374 174.626c30.7 30.7 54.8 58.398 72.58 81.374h-153.954v-153.946c22.984 17.78 50.678 41.878 81.374 72.572zM896 944c0 8.672-7.328 16-16 16h-736c-8.672 0-16-7.328-16-16v-864c0-8.672 7.328-16 16-16 0 0 495.956-0.002 496 0v224c0 17.672 14.326 32 32 32h224v624z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"file-empty",
"file",
"document",
"paper",
"page",
"new",
"empty",
"blank"
],
"defaultCode": 57485,
"grid": 16
},
"attrs": [],
"properties": {
"order": 39,
"id": 1598,
"prevSize": 32,
"code": 59684,
"ligatures": "other file",
"name": "file-empty"
},
"setIdx": 3,
"setId": 7,
"iconIdx": 36
},
{
"icon": {
"paths": [
"M917.806 229.076c-22.212-30.292-53.174-65.7-87.178-99.704s-69.412-64.964-99.704-87.178c-51.574-37.82-76.592-42.194-90.924-42.194h-496c-44.112 0-80 35.888-80 80v864c0 44.112 35.888 80 80 80h736c44.112 0 80-35.888 80-80v-624c0-14.332-4.372-39.35-42.194-90.924zM785.374 174.626c30.7 30.7 54.8 58.398 72.58 81.374h-153.954v-153.946c22.984 17.78 50.678 41.878 81.374 72.572zM896 944c0 8.672-7.328 16-16 16h-736c-8.672 0-16-7.328-16-16v-864c0-8.672 7.328-16 16-16 0 0 495.956-0.002 496 0v224c0 17.672 14.326 32 32 32h224v624z",
"M736 832h-448c-17.672 0-32-14.326-32-32s14.328-32 32-32h448c17.674 0 32 14.326 32 32s-14.326 32-32 32z",
"M736 704h-448c-17.672 0-32-14.326-32-32s14.328-32 32-32h448c17.674 0 32 14.326 32 32s-14.326 32-32 32z",
"M736 576h-448c-17.672 0-32-14.326-32-32s14.328-32 32-32h448c17.674 0 32 14.326 32 32s-14.326 32-32 32z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"file-text",
"file",
"document",
"list",
"paper",
"page"
],
"defaultCode": 57478,
"grid": 16
},
"attrs": [],
"properties": {
"id": 193,
"order": 36,
"prevSize": 32,
"code": 59686,
"ligatures": "text file",
"name": "file-text2"
},
"setIdx": 3,
"setId": 7,
"iconIdx": 38
},
{
"icon": {
"paths": [
"M832 896h-640v-128l192-320 263 320 185-128v256z",
"M832 480c0 53.020-42.98 96-96 96-53.022 0-96-42.98-96-96s42.978-96 96-96c53.020 0 96 42.98 96 96z",
"M917.806 229.076c-22.212-30.292-53.174-65.7-87.178-99.704s-69.412-64.964-99.704-87.178c-51.574-37.82-76.592-42.194-90.924-42.194h-496c-44.112 0-80 35.888-80 80v864c0 44.112 35.888 80 80 80h736c44.112 0 80-35.888 80-80v-624c0-14.332-4.372-39.35-42.194-90.924zM785.374 174.626c30.7 30.7 54.8 58.398 72.58 81.374h-153.954v-153.946c22.984 17.78 50.678 41.878 81.374 72.572zM896 944c0 8.672-7.328 16-16 16h-736c-8.672 0-16-7.328-16-16v-864c0-8.672 7.328-16 16-16 0 0 495.956-0.002 496 0v224c0 17.672 14.326 32 32 32h224v624z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"file-picture",
"file",
"document",
"file-image"
],
"defaultCode": 59823,
"grid": 16
},
"attrs": [],
"properties": {
"order": 38,
"id": 1603,
"prevSize": 32,
"code": 59687,
"ligatures": "image file",
"name": "file-picture"
},
"setIdx": 3,
"setId": 7,
"iconIdx": 39
},
{
"icon": {
"paths": [
"M917.806 229.076c-22.21-30.292-53.174-65.7-87.178-99.704s-69.412-64.964-99.704-87.178c-51.574-37.82-76.592-42.194-90.924-42.194h-496c-44.112 0-80 35.888-80 80v864c0 44.112 35.886 80 80 80h736c44.112 0 80-35.888 80-80v-624c0-14.332-4.372-39.35-42.194-90.924v0zM785.374 174.626c30.7 30.7 54.8 58.398 72.58 81.374h-153.954v-153.946c22.982 17.78 50.678 41.878 81.374 72.572v0zM896 944c0 8.672-7.328 16-16 16h-736c-8.672 0-16-7.328-16-16v-864c0-8.672 7.328-16 16-16 0 0 495.956-0.002 496 0v224c0 17.672 14.324 32 32 32h224v624z",
"M756.288 391.252c-7.414-6.080-17.164-8.514-26.562-6.632l-320 64c-14.958 2.994-25.726 16.126-25.726 31.38v236.876c-18.832-8.174-40.678-12.876-64-12.876-70.692 0-128 42.98-128 96s57.308 96 128 96 128-42.98 128-96v-229.766l256-51.202v133.842c-18.832-8.174-40.678-12.876-64-12.876-70.692 0-128 42.98-128 96s57.308 96 128 96 128-42.98 128-96v-319.998c0-9.586-4.298-18.668-11.712-24.748z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"file-music",
"file",
"document",
"file-song",
"file-audio"
],
"defaultCode": 59825,
"grid": 16
},
"attrs": [],
"properties": {
"order": 35,
"id": 1604,
"prevSize": 32,
"code": 59688,
"ligatures": "audio file",
"name": "file-music"
},
"setIdx": 3,
"setId": 7,
"iconIdx": 40
},
{
"icon": {
"paths": [
"M917.806 229.076c-22.208-30.292-53.174-65.7-87.178-99.704s-69.412-64.964-99.704-87.178c-51.574-37.82-76.594-42.194-90.924-42.194h-496c-44.112 0-80 35.888-80 80v864c0 44.112 35.882 80 80 80h736c44.112 0 80-35.888 80-80v-624c0-14.332-4.372-39.35-42.194-90.924v0 0zM785.374 174.626c30.7 30.7 54.8 58.398 72.58 81.374h-153.954v-153.946c22.98 17.78 50.678 41.878 81.374 72.572v0 0zM896 944c0 8.672-7.328 16-16 16h-736c-8.672 0-16-7.328-16-16v-864c0-8.672 7.328-16 16-16 0 0 495.956-0.002 496 0v224c0 17.672 14.32 32 32 32h224v624z",
"M256 512h320v320h-320v-320z",
"M576 640l192-128v320l-192-128z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"file-video",
"file",
"document",
"file-camera"
],
"defaultCode": 59829,
"grid": 16
},
"attrs": [],
"properties": {
"order": 37,
"id": 1611,
"prevSize": 32,
"code": 59690,
"ligatures": "video file",
"name": "file-video"
},
"setIdx": 3,
"setId": 7,
"iconIdx": 42
},
{
"icon": {
"paths": [
"M917.806 229.076c-22.208-30.292-53.174-65.7-87.178-99.704s-69.412-64.964-99.704-87.178c-51.574-37.82-76.592-42.194-90.924-42.194h-496c-44.112 0-80 35.888-80 80v864c0 44.112 35.884 80 80 80h736c44.112 0 80-35.888 80-80v-624c0-14.332-4.372-39.35-42.194-90.924v0 0zM785.374 174.626c30.7 30.7 54.8 58.398 72.58 81.374h-153.954v-153.946c22.98 17.78 50.678 41.878 81.374 72.572v0 0zM896 944c0 8.672-7.328 16-16 16h-736c-8.672 0-16-7.328-16-16v-864c0-8.672 7.328-16 16-16 0 0 495.956-0.002 496 0v224c0 17.672 14.322 32 32 32h224v624z",
"M256 64h128v64h-128v-64z",
"M384 128h128v64h-128v-64z",
"M256 192h128v64h-128v-64z",
"M384 256h128v64h-128v-64z",
"M256 320h128v64h-128v-64z",
"M384 384h128v64h-128v-64z",
"M256 448h128v64h-128v-64z",
"M384 512h128v64h-128v-64z",
"M256 848c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-160c0-26.4-21.6-48-48-48h-80v-64h-128v272zM448 768v64h-128v-64h128z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"file-zip",
"file",
"document",
"file-compressed",
"file-type",
"file-format"
],
"defaultCode": 58598,
"grid": 16
},
"attrs": [],
"properties": {
"id": 1380,
"order": 40,
"prevSize": 32,
"code": 59691,
"ligatures": "archive file",
"name": "file-zip"
},
"setIdx": 3,
"setId": 7,
"iconIdx": 43
},
{
"icon": {
"paths": [
@ -425,6 +650,35 @@
"setId": 7,
"iconIdx": 310
},
{
"icon": {
"paths": [
"M842.012 589.48c-13.648-13.446-43.914-20.566-89.972-21.172-31.178-0.344-68.702 2.402-108.17 7.928-17.674-10.198-35.892-21.294-50.188-34.658-38.462-35.916-70.568-85.772-90.576-140.594 1.304-5.12 2.414-9.62 3.448-14.212 0 0 21.666-123.060 15.932-164.666-0.792-5.706-1.276-7.362-2.808-11.796l-1.882-4.834c-5.894-13.592-17.448-27.994-35.564-27.208l-10.916-0.344c-20.202 0-36.664 10.332-40.986 25.774-13.138 48.434 0.418 120.892 24.98 214.738l-6.288 15.286c-17.588 42.876-39.63 86.060-59.078 124.158l-2.528 4.954c-20.46 40.040-39.026 74.028-55.856 102.822l-17.376 9.188c-1.264 0.668-31.044 16.418-38.028 20.644-59.256 35.38-98.524 75.542-105.038 107.416-2.072 10.17-0.53 23.186 10.014 29.212l16.806 8.458c7.292 3.652 14.978 5.502 22.854 5.502 42.206 0 91.202-52.572 158.698-170.366 77.93-25.37 166.652-46.458 244.412-58.090 59.258 33.368 132.142 56.544 178.142 56.544 8.168 0 15.212-0.78 20.932-2.294 8.822-2.336 16.258-7.368 20.792-14.194 8.926-13.432 10.734-31.932 8.312-50.876-0.72-5.622-5.21-12.574-10.068-17.32zM211.646 814.048c7.698-21.042 38.16-62.644 83.206-99.556 2.832-2.296 9.808-8.832 16.194-14.902-47.104 75.124-78.648 105.066-99.4 114.458zM478.434 199.686c13.566 0 21.284 34.194 21.924 66.254s-6.858 54.56-16.158 71.208c-7.702-24.648-11.426-63.5-11.426-88.904 0 0-0.566-48.558 5.66-48.558v0zM398.852 637.494c9.45-16.916 19.282-34.756 29.33-53.678 24.492-46.316 39.958-82.556 51.478-112.346 22.91 41.684 51.444 77.12 84.984 105.512 4.186 3.542 8.62 7.102 13.276 10.65-68.21 13.496-127.164 29.91-179.068 49.862v0zM828.902 633.652c-4.152 2.598-16.052 4.1-23.708 4.1-24.708 0-55.272-11.294-98.126-29.666 16.468-1.218 31.562-1.838 45.102-1.838 24.782 0 32.12-0.108 56.35 6.072 24.228 6.18 24.538 18.734 20.382 21.332v0z",
"M917.806 229.076c-22.21-30.292-53.174-65.7-87.178-99.704s-69.412-64.964-99.704-87.178c-51.574-37.82-76.592-42.194-90.924-42.194h-496c-44.112 0-80 35.888-80 80v864c0 44.112 35.886 80 80 80h736c44.112 0 80-35.888 80-80v-624c0-14.332-4.372-39.35-42.194-90.924v0zM785.374 174.626c30.7 30.7 54.8 58.398 72.58 81.374h-153.954v-153.946c22.982 17.78 50.678 41.878 81.374 72.572v0zM896 944c0 8.672-7.328 16-16 16h-736c-8.672 0-16-7.328-16-16v-864c0-8.672 7.328-16 16-16 0 0 495.956-0.002 496 0v224c0 17.672 14.324 32 32 32h224v624z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"file-pdf",
"file",
"file-format"
],
"defaultCode": 58594,
"grid": 16
},
"attrs": [],
"properties": {
"id": 1376,
"order": 34,
"prevSize": 32,
"code": 60122,
"ligatures": "pdf file",
"name": "file-pdf"
},
"setIdx": 3,
"setId": 7,
"iconIdx": 474
},
{
"icon": {
"paths": [

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();
};

View File

@ -0,0 +1,171 @@
/*
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/.
*/
/**
* filesize
*
* @author Jason Mulligan <jason.mulligan@avoidwork.com>
* @copyright 2015 Jason Mulligan <jason.mulligan@avoidwork.com>
* @license BSD-3-Clause
* @link http://filesizejs.com
* @module filesize
* @version 3.1.3
*/
( global ) => {
const bit = /b$/;
const si = {
bits: [ "B", "kb", "Mb", "Gb", "Tb", "Pb", "Eb", "Zb", "Yb" ],
bytes: [ "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]
};
/**
* filesize
*
* @method filesize
* @param {Mixed} arg String, Int or Float to transform
* @param {Object} descriptor [Optional] Flags
* @return {String} Readable file size String
*/
let filesize = ( arg, descriptor={} ) => {
let result = [];
let skip = false;
let val = 0;
let e, base, bits, ceil, neg, num, output, round, unix, spacer, suffixes;
if ( isNaN( arg ) ) {
throw new Error( "Invalid arguments" );
}
bits = ( descriptor.bits === true );
unix = ( descriptor.unix === true );
base = descriptor.base !== undefined ? descriptor.base : 2;
round = descriptor.round !== undefined ? descriptor.round : unix ? 1 : 2;
spacer = descriptor.spacer !== undefined ? descriptor.spacer : unix ? "" : " ";
suffixes = descriptor.suffixes !== undefined ? descriptor.suffixes : {};
output = descriptor.output !== undefined ? descriptor.output : "string";
e = descriptor.exponent !== undefined ? descriptor.exponent : -1;
num = Number( arg );
neg = ( num < 0 );
ceil = base > 2 ? 1000 : 1024;
// Flipping a negative number to determine the size
if ( neg ) {
num = -num;
}
// Zero is now a special case because bytes divide by 1
if ( num === 0 ) {
result[ 0 ] = 0;
if ( unix ) {
result[ 1 ] = "";
}
else {
result[ 1 ] = "B";
}
}
else {
// Determining the exponent
if ( e === -1 || isNaN( e ) ) {
e = Math.floor( Math.log( num ) / Math.log( ceil ) );
}
// Exceeding supported length, time to reduce & multiply
if ( e > 8 ) {
val = val * ( 1000 * ( e - 8 ) );
e = 8;
}
if ( base === 2 ) {
val = num / Math.pow( 2, ( e * 10 ) );
}
else {
val = num / Math.pow( 1000, e );
}
if ( bits ) {
val = ( val * 8 );
if ( val > ceil ) {
val = val / ceil;
e++;
}
}
result[ 0 ] = Number( val.toFixed( e > 0 ? round : 0 ) );
result[ 1 ] = si[ bits ? "bits" : "bytes" ][ e ];
if ( !skip && unix ) {
if ( bits && bit.test( result[ 1 ] ) ) {
result[ 1 ] = result[ 1 ].toLowerCase();
}
result[ 1 ] = result[ 1 ].charAt( 0 );
if ( result[ 1 ] === "B" ) {
result[ 0 ] = Math.floor( result[ 0 ] );
result[ 1 ] = "";
}
else if ( !bits && result[ 1 ] === "k" ) {
result[ 1 ] = "K";
}
}
}
// Decorating a 'diff'
if ( neg ) {
result[ 0 ] = -result[ 0 ];
}
// Applying custom suffix
result[ 1 ] = suffixes[ result[ 1 ] ] || result[ 1 ];
// Returning Array, Object, or String (default)
if ( output === "array" ) {
return result;
}
if ( output === "exponent" ) {
return e;
}
if ( output === "object" ) {
return { value: result[ 0 ], suffix: result[ 1 ] };
}
return result.join( spacer );
}
// CommonJS, AMD, script tag
if ( typeof exports !== "undefined" ) {
module.exports = filesize;
}
else if ( typeof define === "function" ) {
define( () => {
return filesize;
} );
}
else {
global.filesize = filesize;
}
}( typeof global !== "undefined" ? global : window );

View File

@ -0,0 +1,179 @@
/*
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";
/**
* filesize
*
* @author Jason Mulligan <jason.mulligan@avoidwork.com>
* @copyright 2015 Jason Mulligan <jason.mulligan@avoidwork.com>
* @license BSD-3-Clause
* @link http://filesizejs.com
* @module filesize
* @version 3.1.3
*/
(function (global) {
var bit = /b$/;
var si = {
bits: ["B", "kb", "Mb", "Gb", "Tb", "Pb", "Eb", "Zb", "Yb"],
bytes: ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
};
/**
* filesize
*
* @method filesize
* @param {Mixed} arg String, Int or Float to transform
* @param {Object} descriptor [Optional] Flags
* @return {String} Readable file size String
*/
var filesize = function (arg) {
var descriptor = arguments[1] === undefined ? {} : arguments[1];
var result = [];
var skip = false;
var val = 0;
var e = undefined,
base = undefined,
bits = undefined,
ceil = undefined,
neg = undefined,
num = undefined,
output = undefined,
round = undefined,
unix = undefined,
spacer = undefined,
suffixes = undefined;
if (isNaN(arg)) {
throw new Error("Invalid arguments");
}
bits = descriptor.bits === true;
unix = descriptor.unix === true;
base = descriptor.base !== undefined ? descriptor.base : 2;
round = descriptor.round !== undefined ? descriptor.round : unix ? 1 : 2;
spacer = descriptor.spacer !== undefined ? descriptor.spacer : unix ? "" : " ";
suffixes = descriptor.suffixes !== undefined ? descriptor.suffixes : {};
output = descriptor.output !== undefined ? descriptor.output : "string";
e = descriptor.exponent !== undefined ? descriptor.exponent : -1;
num = Number(arg);
neg = num < 0;
ceil = base > 2 ? 1000 : 1024;
// Flipping a negative number to determine the size
if (neg) {
num = -num;
}
// Zero is now a special case because bytes divide by 1
if (num === 0) {
result[0] = 0;
if (unix) {
result[1] = "";
} else {
result[1] = "B";
}
} else {
// Determining the exponent
if (e === -1 || isNaN(e)) {
e = Math.floor(Math.log(num) / Math.log(ceil));
}
// Exceeding supported length, time to reduce & multiply
if (e > 8) {
val = val * (1000 * (e - 8));
e = 8;
}
if (base === 2) {
val = num / Math.pow(2, e * 10);
} else {
val = num / Math.pow(1000, e);
}
if (bits) {
val = val * 8;
if (val > ceil) {
val = val / ceil;
e++;
}
}
result[0] = Number(val.toFixed(e > 0 ? round : 0));
result[1] = si[bits ? "bits" : "bytes"][e];
if (!skip && unix) {
if (bits && bit.test(result[1])) {
result[1] = result[1].toLowerCase();
}
result[1] = result[1].charAt(0);
if (result[1] === "B") {
result[0] = Math.floor(result[0]);
result[1] = "";
} else if (!bits && result[1] === "k") {
result[1] = "K";
}
}
}
// Decorating a 'diff'
if (neg) {
result[0] = -result[0];
}
// Applying custom suffix
result[1] = suffixes[result[1]] || result[1];
// Returning Array, Object, or String (default)
if (output === "array") {
return result;
}
if (output === "exponent") {
return e;
}
if (output === "object") {
return { value: result[0], suffix: result[1] };
}
return result.join(spacer);
};
// CommonJS, AMD, script tag
if (typeof exports !== "undefined") {
module.exports = filesize;
} else if (typeof define === "function") {
define(function () {
return filesize;
});
} else {
global.filesize = filesize;
}
})(typeof global !== "undefined" ? global : window);

View File

@ -0,0 +1,29 @@
/*
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/.
*/
/*
2015
@version 3.1.3
*/
"use strict";!function(a){var b=/b$/,c={bits:["B","kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb"],bytes:["B","kB","MB","GB","TB","PB","EB","ZB","YB"]},d=function(a){var d=void 0===arguments[1]?{}:arguments[1],e=[],f=!1,g=0,h=void 0,i=void 0,j=void 0,k=void 0,l=void 0,m=void 0,n=void 0,o=void 0,p=void 0,q=void 0,r=void 0;if(isNaN(a))throw new Error("Invalid arguments");return j=d.bits===!0,p=d.unix===!0,i=void 0!==d.base?d.base:2,o=void 0!==d.round?d.round:p?1:2,q=void 0!==d.spacer?d.spacer:p?"":" ",r=void 0!==d.suffixes?d.suffixes:{},n=void 0!==d.output?d.output:"string",h=void 0!==d.exponent?d.exponent:-1,m=Number(a),l=0>m,k=i>2?1e3:1024,l&&(m=-m),0===m?(e[0]=0,e[1]=p?"":"B"):((-1===h||isNaN(h))&&(h=Math.floor(Math.log(m)/Math.log(k))),h>8&&(g=1e3*g*(h-8),h=8),g=2===i?m/Math.pow(2,10*h):m/Math.pow(1e3,h),j&&(g=8*g,g>k&&(g/=k,h++)),e[0]=Number(g.toFixed(h>0?o:0)),e[1]=c[j?"bits":"bytes"][h],!f&&p&&(j&&b.test(e[1])&&(e[1]=e[1].toLowerCase()),e[1]=e[1].charAt(0),"B"===e[1]?(e[0]=Math.floor(e[0]),e[1]=""):j||"k"!==e[1]||(e[1]="K"))),l&&(e[0]=-e[0]),e[1]=r[e[1]]||e[1],"array"===n?e:"exponent"===n?h:"object"===n?{value:e[0],suffix:e[1]}:e.join(q)};"undefined"!=typeof exports?module.exports=d:"function"==typeof define?define(function(){return d}):a.filesize=d}("undefined"!=typeof global?global:window);
//# sourceMappingURL=filesize.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -30,7 +30,11 @@
"xDate.repository": "https://github.com/arshaw/xdate",
"xDate.version": "0.8",
"xDate.commit": "f83cd8d63fab8cfe6f00ccba9041d2591daedb74"
"xDate.commit": "f83cd8d63fab8cfe6f00ccba9041d2591daedb74",
"filesize.repository": "https://github.com/avoidwork/filesize.js.git",
"filesize.version": "3.1.3",
"filesize.commit": "20b4ac454f9c6236b6611ddd1b9cbe97d0efbbd2"
},
"html.template": "index_template.html",
@ -73,6 +77,9 @@
"-- PapaParse/papaparse.min.js",
"xDate/xdate.js",
"-- Filesize/filesize.js",
"Filesize/filesize.min.js",
"-- IT WOULD BE NICE TO BE ABLE TO GET RID OF THESE IMPORTS",
"Clipperz/YUI/Utils.js",
@ -92,6 +99,7 @@
"-- Clipperz/Set.js",
"-- Clipperz/Profile.js",
"Clipperz/KeyValueObjectStore.js",
"Clipperz/Sound.js",
"Clipperz/Crypto/SHA.js",
"Clipperz/Crypto/AES.js",
@ -147,6 +155,7 @@
"Clipperz/PM/DataModel/Record.js",
"Clipperz/PM/DataModel/Record.Version.js",
"Clipperz/PM/DataModel/Record.Version.Field.js",
"Clipperz/PM/DataModel/Attachment.js",
"Clipperz/PM/DataModel/DirectLogin.js",
"Clipperz/PM/DataModel/DirectLoginInput.js",
"Clipperz/PM/DataModel/DirectLoginBinding.js",
@ -173,6 +182,8 @@
"Clipperz/PM/UI/Components/Selections.js",
"Clipperz/PM/UI/Components/TagIndexItem.js",
"Clipperz/PM/UI/Components/Help.js",
"Clipperz/PM/UI/Components/AttachmentQueueBox.js",
"Clipperz/PM/UI/Components/RadialProgressIndicator.js",
"Clipperz/PM/UI/Components/ExpiredPanel.js",
@ -222,6 +233,7 @@
"-- Clipperz/PM/UI/MainDesktopController.js",
"Clipperz/PM/UI/DirectLoginController.js",
"Clipperz/PM/UI/ExportController.js",
"Clipperz/PM/UI/AttachmentController.js",
"Clipperz/PM/UI/ImportContext.js",
"main.js"
],

File diff suppressed because one or more lines are too long

View File

@ -323,6 +323,10 @@ div.cardToolbar {
&.tag, &.search {
font-size: 14pt;
}
&.withAttachments {
font-weight: bold;
}
}
span.value {
@ -334,30 +338,64 @@ div.cardToolbar {
&.selectionToggle {
@include flex(1);
text-align: left;
.button {
text-align: left;
display: inline-block;
.label {
padding-left: 8px;
padding-right: 8px;
}
}
}
// &.logo {
// @include align-self(center);
// @include flex(4);
// text-align: center;
// }
&.settingsToggle {
@include flex(1);
text-align: right;
.button {
text-align: right;
display: inline-block;
.label {
padding-right: 8px;
padding-left: 8px;
}
.attachmentQueueToggleButton {
font-family: "clipperz-font";
margin-right: 10px;
h3 {
font-weight: bold;
font-size: 33pt;
letter-spacing: -10px;
}
.badge {
position: absolute;
margin-left: -12px;
width: auto;
height: 16px;
line-height: 17px;
background-color: #1863a1;
color: white;
text-align: center;
font-size: 8pt;
font-weight: bold;
border-radius: 8px;
padding: 0px 4px;
&.top {
top: 11px;
}
&.bottom {
top: 25px;
margin-left: -42px;
}
}
}
}
}
@ -372,6 +410,183 @@ div.cardToolbar {
}
}
$arrow-size: 8px;
div.attachmentQueueStatus {
position: fixed;
top: calc(48px - #{$arrow-size});
right: 0;
padding: 0;
color: white;
z-index: 10;
height: calc(100% - 48px);
pointer-events: none;
.arrow {
width: 0;
height: 0;
border-style: solid;
border-width: 0 $arrow-size $arrow-size $arrow-size;
border-color: transparent transparent black transparent;
position: fixed;
right: 70px;
top: calc(49px - #{$arrow-size});
}
&.closed {
display: none;
visibility: hidden;
}
p {
padding: 1em;
}
ul {
pointer-events: all;
max-height: 100%;
overflow-y: auto;
overflow-x: hidden;
white-space: nowrap;
background: black;
margin-top: $arrow-size;
padding: 0;
box-shadow: 0px 2px 5px #888888;
li {
padding: 0 0 0 1em;;
border-bottom: 1px solid white;
span {
display: inline-block;
vertical-align: middle;
height: 40px;
line-height: 40px;
}
.contentType {
@include icon-font();
color: white;
width: 30px;
}
.name {
width: 250px;
overflow: hidden;
text-overflow: ellipsis;
}
.status {
width: 150px;
text-align: right;
.statusString {
font-size: .8em;
}
.processIcon {
margin-left: 1em;
}
}
.close {
a {
@include icon-font();
display: inline-block;
width: 50px;
text-align: center;
letter-spacing: 1px;
font-size: 20px;
@include userSelectNone();
}
a:hover {
color: lightgray;
cursor: pointer;
}
}
.progress {
width: 50px;
height: 30px;
text-align: center;
@include userSelectNone();
}
&:last-child {
border-bottom: 0;
}
}
}
.radialProgressIndicator {
height: 25px;
.background {
fill: white;
}
.progress {
fill: $clipperz-orange;
}
}
}
.narrow {
.attachmentQueueStatus {
width:100%;
ul {
white-space: inherit;
li {
padding-top: .2em;
padding-bottom: .2em;
span {
height: 25px;
line-height: 30px;
}
.name {
width: 225px;
}
.status {
padding-left: 30px;
text-align: left;
width: calc(100% - 100px);
line-height: 20px;
font-size: .8em;
.processIcon {
margin-left: 0.5em;
}
}
.close {
height: inherit;
line-height: inherit;
float: right;
display: block;
margin-top: -5px;
}
.progress {
padding-left: 18px;
margin-top: -24px;
}
}
}
}
}
div.cardContent {
// @include flex(flex-grow); // ???
@ -481,6 +696,8 @@ div.cardContent {
@include flexbox();
@include flex-direction(column);
width:100%;
}
.content {
@ -498,6 +715,7 @@ div.cardContent {
@include flexbox();
@include flex-direction(column);
height: 100%;
width: 100%;
.content {
@include flex(auto);
@ -516,6 +734,8 @@ div.cardContent {
@include chromeFix();
width:100%;
li.save {
cursor: default;
background-color: #55aa55;

View File

@ -248,3 +248,11 @@ refer to http://www.clipperz.com.
}
*/
}
@mixin userSelectNone () {
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}

View File

@ -52,10 +52,19 @@ $tagEditor_lineHeight: 27px;
font-size: 13pt;
font-weight: 100;
padding-right: 10px;
margin-right: 10px;
padding-left: 1px;
line-height: $tagEditor_lineHeight;
/* boxed style test */
/* background-color: $clipperz-orange;
padding-left: 5px;
padding-right: 5px;
border-radius: 3px;
border:1px solid gray;
color: white;
*/
// &:last-child:after {
// content: '';
// }
@ -68,9 +77,12 @@ $tagEditor_lineHeight: 27px;
span.tagRemoveButton {
@include icon-font();
@include userSelectNone();
cursor: pointer;
line-height: 22px;
color: #ccc;
/*color:white;*/
&:hover {
color: rgb(155, 0, 0);

View File

@ -36,6 +36,8 @@ $cardArchivedColor: #eee;
$cardToolbarBackgroundColor: $clipperz-blue;
$iconMargin: 6px;
$labelColor: gray;
$lightRed: #dd1111;
$darkRed: #aa2222;
div.cardList {
// background-color: $yellow;
@ -106,6 +108,14 @@ div.cardList {
line-height: 1.2em;
}
.attachmentsCount {
@include icon-font();
color: #aaa;
padding-right: 8px;
padding-left: 6px;
padding-top: 18px;
}
}
}
@ -267,6 +277,10 @@ $cardViewBasePadding: 10px;
background-color: $cardToolbarBackgroundColor;
color: white;
&.top {
display: none; /* Issue #203 */
}
&.narrow {
@include transition(height, 2.5s, ease-in);
@ -305,8 +319,9 @@ $cardViewBasePadding: 10px;
.removeDirectLogin {
@include icon-font();
margin: $iconMargin;
margin-top: 12px;
display: inline-block;
margin-left: $iconMargin;
// margin-top: 12px;
cursor: pointer;
color: white;
width: 40px;
@ -319,13 +334,206 @@ $cardViewBasePadding: 10px;
.directLoginLabel {
vertical-align: bottom;
padding-left: 17px;
// padding-left: 17px;
padding-left: 0px;
}
}
.cardUploadAttachments {
border: 2px dashed #ccc;
margin: 0 1em 1em 1em;
padding: 1em;
text-align: center;
margin-left: 44px;
p {
margin-bottom: 1em;
}
.attachmentInput {
display: none;
}
.button {
display: inline-block;
color: white;
background-color: #ff9900;
font-size: 14pt;
padding: 10px 14px;
}
}
}
}
.cardAttachmentWrapper {
border-bottom: 3px double #eee;
}
.cardAttachments {
padding-bottom: 10px;
h3 {
padding-top: 14px;
padding-bottom: 14px;
color: gray;
font-size: 12pt;
&:before {
@include icon-font();
content: "attachment";
padding-left: 13px;
padding-right: 13px;
padding-top: 0px;
padding-bottom: 0px;
font-weight: bold;
font-size: 14pt;
display: inline-block;
transform: scaleX(-1);
};
}
.skippedFiles {
margin: 1em 1em 1em 44px;
color: white;
background: $clipperz-orange;
padding: 1em;
font-size: .8em;
ul {
padding: 1em 1em 1em 0;
list-style-type: none;
li {
.filename {
font-weight: bold;
}
}
}
}
.attachmentList {
margin-left: 44px;
padding-right: 8px;
li {
@include flexbox();
@include flex-direction(row);
padding-bottom: 6px;
&.broken {
span {
color: $lightRed !important;
}
}
/*cursor: pointer;*/
.contentType {
@include flex(none);
width: 30px;
@include icon-font();
color: gray;
}
span {
/*border:1px solid black;*/
line-height: 30px;
display: inline-block;
}
.meta {
@include flex(auto);
text-align: left;
font-size: 14pt;
width: 100%; /* Hack to fix long names behaviour */
overflow: hidden;
.name {
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
white-space: pre;
}
.size {
display: block;
color: #aaa;
font-size: 8pt;
line-height: 8pt;
}
}
.status {
width: 100px;
text-align: right;
.waiting {
font-size: 10pt;
color: #aaa;
}
}
.actions {
@include userSelectNone();
display: inline-block;
width:30px;
a {
display: inline-block;
width: 30px;
cursor:pointer;
font-size: 20pt;
color: #aaa;
&.cancel, &.remove {
@include icon-font();
&:hover {
color: $lightRed;
};
}
&.download {
&:hover {
color: $clipperz-blue;
};
}
}
}
.progress {
width: 30px;
text-align: center;
.radialProgressIndicator {
width: 25px;
height: 30px;
.background {
fill: white;
}
.progress {
fill: black;
}
.border {
fill: black;
}
&.waiting {
.border {
fill: #aaa;
}
}
}
}
}
}
}
.edit {
.cardDetailToolbar {
@ -406,6 +614,64 @@ $cardViewBasePadding: 10px;
.tagEditor {
padding: $cardViewBasePadding;
// background-color: cyan;
flex-wrap: wrap;
/* I considered this specific to the card View/Edit page: should it go in tagEditor.scss instead? */
ul {
width: calc(100% - 31px);
flex-wrap: wrap;
padding-left: 31px;
&:before {
margin-left: -32px;
}
li {
.tagLabel {
max-width: 500px; // Hack: length computation breaks when the content is very long
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
}
}
}
&.readOnly {
ul {
li {
display: inline-block;
max-width: 30%;
.tagLabel {
display: inline-block;
width: 100%;
}
}
}
}
&.readWrite {
ul {
li {
// Uniform size
/*display: inline-flex;*/
/*width: 30%;*/
// Variable size
display: inline-block;
max-width: 30%;
.tagLabel {
width: calc(100% - 30px);
display: inline-block;
}
input {
width: 100%;
}
}
}
}
}
.cardNotes {
@ -483,7 +749,7 @@ $cardViewBasePadding: 10px;
font-size: 20pt;
&:hover {
color: rgb(155, 0, 0);
color: $lightRed;
};
}
@ -643,6 +909,8 @@ $cardViewBasePadding: 10px;
font-size: 15pt;
margin: $iconMargin;
margin-right: 16px;
cursor: default;
&.URL {
@ -686,15 +954,60 @@ $cardViewBasePadding: 10px;
}
.newCardField {
@include icon-font();
margin: $iconMargin;
height: 115px;
border-bottom: 3px double #eee;
cursor: pointer;
color: #ccc;
font-size: 20pt;
.fieldGhostShadow {
// width: 100%;
padding-top: 11px;
padding-bottom: 11px;
padding-left: 42px;
padding-right: 49px;
// float: left;
// clear: both;
.label {
background-color: white;
height: 25px;
margin-bottom: 8px;
}
.value {
background-color: white;
height: 58px;
}
}
.addNewFieldButton {
margin-top: -90px;
width: 70px;
font-size: 50pt;
margin-left: auto;
margin-right: auto;
@include icon-font();
// margin: $iconMargin;
color: #eee;
// border-radius: 30px;
}
&:hover {
color: green;
background-color: #f5f5f5;
.fieldGhostShadow {
}
.addNewFieldButton {
color: #7c7;
}
};
}
.cardDirectLogin {

View File

@ -56,7 +56,13 @@ refer to http://www.clipperz.com.
&.recentCards:before {
content: "recent";
}
&.withAttachmentCards:before {
// transform: scaleX(-1);
content: "attachment";
font-weight: bold;
}
&.untaggedCards {
padding-left: 35px;
}
@ -253,18 +259,40 @@ $selectionColor: $clipperz-orange;
#selections.ALL {
li.allCards {
color: $selectionColor;
&:before {
color: white;
}
}
}
#selections.WITH_ATTACHMENTS {
li.withAttachmentCards {
color: $selectionColor;
&:before {
color: white;
}
}
}
#selections.RECENT {
li.recentCards {
color: $selectionColor;
&:before {
color: white;
}
}
}
#selections.UNTAGGED {
li.untaggedCards {
color: $selectionColor;
&:before {
color: white;
}
}
}
@ -278,8 +306,13 @@ $selectionColor: $clipperz-orange;
form {
div.form {
label {
// color: $selectionColor;
}
input {
color: $selectionColor;
}
.searchClear {
span.count {
display: block;
@ -300,6 +333,10 @@ $selectionColor: $clipperz-orange;
ul.tagList {
li.selected {
color: $selectionColor;
&:before {
color: white;
}
}
}
}

View File

@ -0,0 +1,55 @@
<!--
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/.
-->
<html>
<head>
<title>Clipperz.Crypto.AES_2 - tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script type="text/javascript" src="../../../../js/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="../../../SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="../../../SimpleTest/test.css">
<script type='text/javascript' src='../../../../js/Clipperz/YUI/Utils.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/YUI/DomHelper.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/Base.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/ByteArray.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/Async.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/Logging.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/Crypto/Base.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/Crypto/BigInt.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/Crypto/AES.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/Crypto/AES_2.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/Crypto/SHA.js'></script>
<script type='text/javascript' src='../../../../js/Clipperz/Crypto/PRNG.js'></script>
<script type="text/javascript" src="../../../SimpleTest/SimpleTest.Async.js"></script>
</head>
<body>
<pre id="test">
<script type="text/javascript" src="AES_2.test.js"></script>
</pre>
</body>
</html>

View File

@ -0,0 +1,167 @@
/*
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";
function testEncryptedData (tool, keyValue, encryptedText, expectedCleanText, someTestArgs) {
var key = Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(keyValue));
var value = new Clipperz.ByteArray().appendBase64String(encryptedText);
var deferredResult = new Clipperz.Async.Deferred("pythonCompatibility_test", someTestArgs);
deferredResult.addCallback(Clipperz.Crypto.AES_2.deferredDecrypt, key, value);
deferredResult.addCallback(function(aResult) {
return aResult.asString();
});
deferredResult.addTest(expectedCleanText, tool);
deferredResult.callback();
return deferredResult;
}
//=============================================================================
var tests = {
'incrementNonce_test': function (someTestArgs) {
var nonce;
nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Clipperz.Crypto.AES_2.incrementNonce(nonce)
SimpleTest.eq(nonce, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], "increment 0 based nonce");
nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
Clipperz.Crypto.AES_2.incrementNonce(nonce)
SimpleTest.eq(nonce, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2], "increment '1' nonce");
nonce = [58,231,19,199,48,86,154,169,188,141,46,196,83,34,37,89]
Clipperz.Crypto.AES_2.incrementNonce(nonce)
SimpleTest.eq(nonce, [58,231,19,199,48,86,154,169,188,141,46,196,83,34,37,90], "increment '1' nonce");
return
},
'pythonCompatibility_test': function (someTestArgs) {
var keyValue = "clipperz"
var cleanText = "Lorem īpsum dōlōr siÞ ǽmēt, stet voluptatum ei eum, quō pērfecto lobortis eā, vel ċu deserūisse comprehēƿsam. Eu sed cībō veniam effīciendi, Þe legere ðominġ est, ðuō ċu saperet inermis pērfeċto. Vim ei essent consetētūr, quo etīam saepē æpeirian in, et atqūi velīÞ sǣepe his? Æn porrō putanÞ sinġulis mei, ēx sonet noster mea, tē alterum praesent percipitur qūo. ViÞaē neċessitatibus ne vim, per ex communē sentēntiǣe! Qui stet ǽdhūċ uÞ."
// def testEncrypt (keyValue, cleanText):
// key = keyDerivation(keyValue)
// iv = random.getrandbits(128)
// ctr = Crypto.Util.Counter.new(128, initial_value=iv)
// cipher = AES.new(key, Crypto.Cipher.AES.MODE_CTR, counter=ctr)
// encryptedValue = cipher.encrypt(cleanText.encode('utf-8'))
// data = base64.b64encode(base64.b16decode(hex(iv).upper()[2:-1]) + encryptedValue)
//
// return data
var pythonEncryptedData = "9AFIXRO2nY0mkLJI6Xd4bd+Ov1g+kYUh73nICEVUM8OGt5FnfV/w2BfmTvdMGZjs+rF8w0ksrS9Ny8j2+2zPUUrKnVRXO6eGVPSN5VfuYFSHucV98msINH0FpOZHftuKCuJkB/orjQhoIbj9SXT0yUwB3b4R2bk48Br7R8G2bhxqrHRmnYQn22AQVA83UstNvCOdXT7ArfwJZbVSSMkdmvcziZ8ObMvaH+FXD/K8i7dzS1yP03MMBtIkYN8PnyUMS2uAHKiR11jGuha9QfXjLJlWUQWZgNB9NKyOKf7tN+OgtAoWmHmKlpTshfwbfFD8wBPR0kkhR0cC+7queIjpCDnBJ+Nod78zWgPDR8g64sph7OB686HkP03cO66aH/LNuAt03gxaVyE8ufvoStRjlIthOuys5xYWP+hTFYDC7OhCOLKvhZoY4Tr/FP+TjporX3ivCJUEEvwvXeftAxFVRl4JDin0ys0iPTQ7QlbtVa+iep2n9FUG1NOn5boD9y+iw64UJAcex4MqEIdpCHne9LjpiqshcwLmfEeLlFab28LHnvYPGkXDrSRjCujx8ZmmTw96sAIDqER8p1AqaSojwvONYBGrq+f5/f4xjzZJAknMmxYEN14Phbxc8WEhpe5omWdB80C1Kv6CLsoQnGAIshURSZryToXL"
return testEncryptedData("python", keyValue, pythonEncryptedData, cleanText, someTestArgs)
},
//-------------------------------------------------------------------------
'streamEncrypt': function (someTestArgs) {
// var key = new Clipperz.Crypto.AES.Key({key: Clipperz.Crypto.SHA.deriveKey("super secure passphrase")});
var key = new Clipperz.Crypto.AES.Key({key: Clipperz.PM.Crypto.deriveKey("super secure passphrase")});
var nonce = Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(128/8);
var message = new Clipperz.ByteArray("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG");
var context = new Clipperz.Crypto.AES_2.EncryptionStreamingExecutionContext({
nonce: nonce,
key: key
});
var originalNonce = context.nonce().clone();
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("AES_test.streamEncrypt", {trace:false});
deferredResult.addMethod(context, 'deferredProcessBlock', message);
deferredResult.addCallback(function () {
console.log("originalNonce", originalNonce.toHexString());
console.log("currentNonce", context.nonce().toHexString());
});
deferredResult.callback();
return deferredResult;
},
'blockStreamEncrypt': function(someTestArgs) {
var lastNonce;
var initialKey = Clipperz.PM.Crypto.deriveKey("super secure passphrase");
var key = new Clipperz.Crypto.AES.Key({key: Clipperz.PM.Crypto.deriveKey("super secure passphrase")});
var nonce = Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(128/8);
var message = new Clipperz.ByteArray("PRANZO D'ACQUA FA VOLTI SGHEMBI.");
var encryptedMessage = Clipperz.Crypto.AES_2.encrypt(initialKey, message, nonce);
var chunk1 = new Clipperz.ByteArray("PRANZO D'ACQUA F");
var chunk2 = new Clipperz.ByteArray("A VOLTI SGHEMBI.");
var encryptedChunk1;
var encryptedChunk2;
var context = new Clipperz.Crypto.AES_2.EncryptionStreamingExecutionContext({
nonce: nonce,
key: key
});
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("AES_test.blockStreamEncrypt", {trace:false});
deferredResult.addMethod(context, 'deferredProcessBlock', chunk1);
deferredResult.addCallback(function (aResult) {
encryptedChunk1 = aResult;
lastNonce = context.nonce().clone();
});
deferredResult.addMethod(context, 'deferredProcessBlock', chunk2);
deferredResult.addCallback(function (aResult) {
encryptedChunk2 = aResult;
var compositeEncryptedMessage = new Clipperz.ByteArray();
// compositeEncryptedMessage.appendBytes(context.nonce().arrayValues());
compositeEncryptedMessage.appendBytes(lastNonce.arrayValues());
compositeEncryptedMessage.appendBytes(encryptedChunk1.arrayValues());
compositeEncryptedMessage.appendBytes(encryptedChunk2.arrayValues());
console.log('encrypted message', encryptedMessage.toHexString());
console.log('composite message', compositeEncryptedMessage.toHexString());
console.log('encrypted message', encryptedMessage.toBase64String());
console.log('composite message', compositeEncryptedMessage.toBase64String());
console.log('encrypted message', encryptedMessage.arrayValues());
console.log('composite message', compositeEncryptedMessage.arrayValues());
});
deferredResult.callback();
return deferredResult;
},
//-------------------------------------------------------------------------
'syntaxFix': MochiKit.Base.noop
}
//=============================================================================
Clipperz.Crypto.PRNG.defaultRandomGenerator().fastEntropyAccumulationForTestingPurpose();
SimpleTest.runDeferredTests("Clipperz.Crypto.AES_2", tests, {trace:false});