/* Copyright 2008-2018 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.DataModel'); //if (typeof(Clipperz) == 'undefined') { Clipperz = {}; } //if (typeof(Clipperz.PM) == 'undefined') { Clipperz.PM = {}; } //if (typeof(Clipperz.PM.DataModel) == 'undefined') { Clipperz.PM.DataModel = {}; } Clipperz.PM.DataModel.Record = function(args) { Clipperz.PM.DataModel.Record.superclass.constructor.apply(this, arguments); this._updateDate = (args.updateDate ? Clipperz.PM.Date.parse(args.updateDate) : Clipperz.Base.exception.raise('MandatoryParameter')); // Disabled due to invalid data and the raised exception // this._accessDate = (args.accessDate ? Clipperz.PM.Date.parse(args.accessDate) : Clipperz.Base.exception.raise('MandatoryParameter')); this._retrieveIndexDataFunction = args.retrieveIndexDataFunction || Clipperz.Base.exception.raise('MandatoryParameter'); this._updateIndexDataFunction = args.updateIndexDataFunction || Clipperz.Base.exception.raise('MandatoryParameter'); 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._certificateInfo = null; this._tags = []; this._directLogins = {}; this._attachments = {}; this._versions = {}; this._currentRecordVersion = null; if (this.isBrandNew()) { var newVersion; this.setNotes(''); newVersion = new Clipperz.PM.DataModel.Record.Version({ 'retrieveKeyFunction': MochiKit.Base.method(this, 'getVersionKey'), 'getVersion': MochiKit.Base.method(this, 'getVersion') }); this._versions[newVersion.reference()] = newVersion; this._currentVersionReference = newVersion.reference(); // this.setLabel(''); } return this; } Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.EncryptedRemoteObject, { 'toString': function() { return "Record (" + this.reference() + ")"; }, //------------------------------------------------------------------------- // 'reference': function () { // return this._reference; // }, //========================================================================= setID: function (anID) { //console.log("RECORD - SET ID", this, anID); return this.setValue('ID', anID); }, getID: function () { return this.getValue('ID'); }, //========================================================================= collectFieldInfo: function (aField) { var deferredResult; deferredResult = new Clipperz.Async.Deferred('Record.collectFieldInfo', {trace:false}); deferredResult.setValue('_field'); deferredResult.addMethod(aField, 'reference'); deferredResult.setValue('_reference'); deferredResult.addMethod(aField, 'label'); deferredResult.setValue('label'); deferredResult.addMethod(aField, 'value'); deferredResult.setValue('value'); deferredResult.addMethod(aField, 'actionType'); deferredResult.setValue('actionType'); deferredResult.addMethod(aField, 'isHidden'); deferredResult.setValue('isHidden'); deferredResult.values(); deferredResult.callback(aField); return deferredResult; }, collectDirectLoginInfo: function (aDirectLogin) { var deferredResult; deferredResult = new Clipperz.Async.Deferred('Record.collectDirectLoginInfo', {trace:false}); deferredResult.addMethod(aDirectLogin, 'reference'); deferredResult.setValue('_reference'); deferredResult.addMethod(aDirectLogin, 'label'); deferredResult.setValue('label'); deferredResult.addMethod(aDirectLogin, 'favicon'); deferredResult.setValue('favicon'); deferredResult.values(); deferredResult.callback(); return deferredResult; }, collectAttachmentInfo: function(anAttachment) { var deferredResult; deferredResult = new Clipperz.Async.Deferred('Record.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.addMethod(anAttachment, 'hash'); deferredResult.setValue('hash'); deferredResult.values(); deferredResult.callback(anAttachment); return deferredResult; }, collectRecordInfo: function () { var deferredResult; deferredResult = new Clipperz.Async.Deferred('Record.collectRecordInfo', {trace:false}); deferredResult.setValue('_record'); deferredResult.addMethod(this, 'reference'); deferredResult.setValue('_reference'); deferredResult.addMethod(this, 'isArchived'); deferredResult.setValue('_isArchived'); deferredResult.addMethod(this, 'isBrandNew'); deferredResult.setValue('_isBrandNew'); deferredResult.addMethod(this, 'label'); deferredResult.setValue('label'); deferredResult.addMethod(this, 'notes'); deferredResult.setValue('notes'); deferredResult.addMethod(this, 'tags'); deferredResult.setValue('tags'); deferredResult.addMethod(this, 'getID'); deferredResult.setValue('ID'); deferredResult.addMethod(this, 'fields'); deferredResult.addCallback(MochiKit.Base.values); deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.method(this, 'collectFieldInfo')); deferredResult.addCallback(Clipperz.Async.collectAll); deferredResult.setValue('fields'); deferredResult.addMethod(this, 'certificateInfo'); deferredResult.addCallback(MochiKit.Base.bind(function (someCertificateInfo) { var result; if ((someCertificateInfo) && (someCertificateInfo['status'] == 'requested')) { result = this.updateCertificateInfo() } else { result = MochiKit.Async.succeed(someCertificateInfo); } return result; }, this)); deferredResult.setValue('certificateInfo'); deferredResult.addMethod(this, 'attachmentsInfo'); deferredResult.setValue('attachments'); deferredResult.addMethod(this, '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(this, 'getAttachmentServerStatus'); deferredResult.setValue('attachmentServerStatus'); deferredResult.values(); deferredResult.callback(this); return deferredResult; }, //========================================================================= certificateMetadata: function () { var deferredResult; deferredResult = new Clipperz.Async.Deferred('Record.certificateMetadata', {trace:false}); deferredResult.addMethod(this, 'collectRecordInfo'); deferredResult.addCallback(function (someValues) { var result = {}; var propertiesToCollect = ['label', 'notes']; var fieldPropertiesToCollect = ['label', 'value']; var attachmentPropertiesToCollect = ['name', 'contentType', 'size', 'hash']; MochiKit.Iter.forEach(propertiesToCollect, function (aKey) { result[aKey] = someValues[aKey]}); result['fields'] = MochiKit.Base.map(function (aField) { var fieldResult = {}; MochiKit.Iter.forEach(fieldPropertiesToCollect, function (aKey) { fieldResult[aKey] = aField[aKey]}); return fieldResult; }, someValues['fields']); result['attachments'] = MochiKit.Base.map(function (anAttachment) { var attachmentResult = {}; MochiKit.Iter.forEach(attachmentPropertiesToCollect, function (aKey) { attachmentResult[aKey] = anAttachment[aKey]}); return attachmentResult; }, someValues['attachments']); if (result['notes'] == "") { delete result['notes']; } return result; }); deferredResult.addCallback(Clipperz.Base.serializeJSON); //deferredResult.addCallback(function (aValue) { console.log("<<< CERTIFICATE METADATA", aValue); return aValue;}); deferredResult.callback(); return deferredResult; }, //========================================================================= /* publicKeyBufferFromHash256: function (aBuffer) { // var fixedBuffer; // // fixedBuffer = new npm.buffer.Buffer(33); // fixedBuffer.writeUInt8(0x03, 0); // https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki // aBuffer.copy(fixedBuffer, 1); // // return npm.bitcoin.ECPair.fromPublicKeyBuffer(fixedBuffer, NETWORK); return npm.bitcoin.HDNode.fromSeedHex(aBuffer.toString('hex')).getPublicKeyBuffer(); }, */ keyFromHash256: function (aBuffer) { // return npm.bitcoin.HDNode.fromSeedHex(aBuffer.toString('hex'), NETWORK); var d = npm.BigInteger.fromHex(aBuffer.toString('hex')); return new npm.bitcoin.ECPair(d, null, {'network':NETWORK, 'compressed':true}) }, computeAttachmentsCertificateInfo: function (attachmentController) { var deferredResult; var collectResultsInfo = {}; var shouldComputeResult = false; var self = this; MochiKit.Iter.forEach(MochiKit.Base.values(this.attachments()), function (anAttachment) { shouldComputeResult = true; collectResultsInfo[anAttachment.reference()] = [ MochiKit.Base.method(anAttachment, 'metadata'), MochiKit.Base.itemgetter('hash'), function (anHashValue) { var hashBuffer = new npm.buffer.Buffer(anHashValue, 'hex'); var key = self.keyFromHash256(hashBuffer); return { 'hash': anHashValue, 'publicKey': key.getPublicKeyBuffer().toString('hex'), 'address': key.getAddress(), } } ]; }); if (shouldComputeResult) { deferredResult = new Clipperz.Async.Deferred("Record.computeAttachmentsCertificateInfo", {trace:false}); deferredResult.collectResults(collectResultsInfo); deferredResult.callback(); } else { deferredResult = MochiKit.Async.succeed(collectResultsInfo); } return deferredResult; }, computeCertificateInfo: function (aWallet) { var deferredResult; deferredResult = new Clipperz.Async.Deferred("Record.computeCertificateInfo", {trace:false}); deferredResult.collectResults({ 'reference': MochiKit.Base.method(this, 'reference'), 'version': MochiKit.Base.partial(MochiKit.Async.succeed, "1.0"), 'card.publicKey': [ MochiKit.Base.method(this, 'getID'), MochiKit.Base.method(aWallet, 'getKeyForDocumentWithID'), MochiKit.Base.methodcaller('getPublicKeyBuffer'), MochiKit.Base.methodcaller('toString', 'hex'), ], 'card.address': [ MochiKit.Base.method(this, 'getID'), MochiKit.Base.method(aWallet, 'getKeyForDocumentWithID'), MochiKit.Base.methodcaller('getAddress') ], /* 'metadata': [ MochiKit.Base.method(this, 'certificateMetadata'), function (aString) { return new npm.buffer.Buffer(aString, 'utf8') }, npm.bitcoin.crypto.hash160, function (anHash160) { return npm.bitcoin.address.toBase58Check(anHash160, NETWORK.pubKeyHash);} ], */ 'metadata.publicKey': [ MochiKit.Base.method(this, 'certificateMetadata'), function (aString) { return new npm.buffer.Buffer(aString, 'utf8') }, npm.bitcoin.crypto.hash256, MochiKit.Base.method(this, 'keyFromHash256'), MochiKit.Base.methodcaller('getPublicKeyBuffer'), MochiKit.Base.methodcaller('toString', 'hex'), ], 'metadata.address': [ MochiKit.Base.method(this, 'certificateMetadata'), function (aString) { return new npm.buffer.Buffer(aString, 'utf8') }, npm.bitcoin.crypto.hash256, MochiKit.Base.method(this, 'keyFromHash256'), MochiKit.Base.methodcaller('getAddress') ], // 'attachments': MochiKit.Base.method(this, 'computeAttachmentsCertificateInfo') }); //deferredResult.addCallback(function (aValue) { console.log("=== CERTIFICATE INFO", MochiKit.Base.serializeJSON(aValue)); return aValue;}); deferredResult.callback(); return deferredResult; }, //========================================================================= 'getIndexData': function () { return this._retrieveIndexDataFunction(this.reference()); }, //......................................................................... 'getIndexDataForKey': function (aKey) { return Clipperz.Async.callbacks("Record.getIndexDataForKey", [ MochiKit.Base.method(this, 'getIndexData'), MochiKit.Base.itemgetter(aKey) ], {trace:false}); }, //------------------------------------------------------------------------- 'setIndexDataForKey': function (aKey, aValue) { // return this._updateIndexDataFunction(this.reference(), aKey, aValue); var deferredResult; deferredResult = new Clipperz.Async.Deferred("Record.setIndexDataForKey", {trace:false}); deferredResult.addMethod(this, 'getIndexDataForKey', aKey); deferredResult.addCallback(MochiKit.Base.bind(function (aCurrentValue) { var result; var originalValue; originalValue = this.transientState().getValue('originalValues.indexData.' + aKey); if (originalValue == null) { originalValue = this.transientState().setValue('originalValues.indexData.' + aKey, aCurrentValue); } if (aCurrentValue != aValue) { if (originalValue != aValue) { this.transientState().setValue('hasPendingChanges.indexData.' + aKey, true); } else { this.transientState().setValue('hasPendingChanges.indexData.' + aKey, false); } result = this._updateIndexDataFunction(this.reference(), aKey, aValue); } else { result = MochiKit.Async.succeed(aValue); } return result; }, this)); deferredResult.callback(); return deferredResult; }, //---------------------------------------------------------------------------- setAttachmentServerStatus: function(anObject) { this._attachmentServerStatus = anObject; }, getAttachmentServerStatus: function() { return this._attachmentServerStatus; }, updatedAttachmentServerStatus: function () { return Clipperz.Async.callbacks("Record.updatedAttachmentServerStatus", [ MochiKit.Base.partial(this.retrieveRemoteDataFunction(), this), MochiKit.Base.itemgetter('attachmentStatus'), MochiKit.Base.method(this, 'setAttachmentServerStatus'), ], {trace:false}); }, //---------------------------------------------------------------------------- setCertificateInfo: function (aValue) { this._certificateInfo = aValue; return this._certificateInfo; }, certificateInfo: function () { return this._certificateInfo; }, updateCertificateInfo: function () { return Clipperz.Async.callbacks("Record.updateCertificateInfo", [ MochiKit.Base.partial(this.retrieveRemoteDataFunction(), this), MochiKit.Base.itemgetter('certificateInfo'), MochiKit.Base.method(this, 'setCertificateInfo'), ], {trace:false}); }, //============================================================================ /* 'key': function () { return this.getIndexDataForKey('key'); }, */ //============================================================================ 'fullLabel': function () { return this.getIndexDataForKey('label'); }, 'setFullLabel': function (aLabel, someTags) { var fullLabel = MochiKit.Base.extend([aLabel], MochiKit.Base.map(function (aTag) { return Clipperz.PM.DataModel.Record.tagChar + aTag; }, someTags)).join(' '); return this.setIndexDataForKey('label', fullLabel); }, 'updateTags': function (someTagInfo) { return Clipperz.Async.callbacks("Record.updateTags", [ MochiKit.Base.method(this, 'label'), MochiKit.Base.bind(function (aLabel) { return this.setFullLabel(aLabel, MochiKit.Base.keys(someTagInfo)); }, this) ], {trace:false}); }, //............................................................................ 'extractLabelFromFullLabel': function (aValue) { return Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aValue); }, 'extractTagsFromFullLabel': function (aLabel) { return Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aLabel); }, //............................................................................ 'label': function () { return Clipperz.Async.callbacks("Record.label", [ MochiKit.Base.method(this, 'fullLabel'), MochiKit.Base.method(this, 'extractLabelFromFullLabel') ], {trace:false}); }, 'setLabel': function (aValue) { // var tags; return Clipperz.Async.callbacks("Record.setLabel", [ MochiKit.Base.method(this, 'tags'), // function (someValues) { console.log("TAGS", someValues); tags = someValues; }, // MochiKit.Base.method(this, 'setIndexDataForKey', 'label', aValue), // MochiKit.Base.method(this, 'updateFullLabelWithTags', tags), MochiKit.Base.method(this, 'setFullLabel', aValue), MochiKit.Base.method(this, 'label'), ], {trace:false}); // return this.setIndexDataForKey('label', aValue); // [???] }, //......................................................................... 'tags': function () { return Clipperz.Async.callbacks("Record.label", [ MochiKit.Base.method(this, 'fullLabel'), MochiKit.Base.method(this, 'extractTagsFromFullLabel'), MochiKit.Base.keys ], {trace:false}); }, 'addTag': function (aNewTag) { // var tag = aNewTag.replace(/\s/g, Clipperz.PM.DataModel.Record.tagSpace); //console.log("TAG", aNewTag, tag); return Clipperz.Async.callbacks("Record.addTag", [ MochiKit.Base.method(this, 'fullLabel'), MochiKit.Base.method(this, 'extractTagsFromFullLabel'), function (someTags) { someTags[aNewTag] = true; return someTags; }, MochiKit.Base.method(this, 'updateTags') ], {trace:false}); }, 'removeTag': function (aTag) { //console.log("ADD TAG", aNewTag); return Clipperz.Async.callbacks("Record.removeTag", [ MochiKit.Base.method(this, 'fullLabel'), MochiKit.Base.method(this, 'extractTagsFromFullLabel'), function (someTags) { delete someTags[aTag]; return someTags; }, MochiKit.Base.method(this, 'updateTags') ], {trace:false}); }, 'toggleArchive': function () { return Clipperz.Async.callbacks("Record.toggleArchive", [ MochiKit.Base.method(this, 'isArchived'), Clipperz.Async.deferredIf("Record is archived", [ MochiKit.Base.method(this, 'removeTag', Clipperz.PM.DataModel.Record.archivedTag) ], [ MochiKit.Base.method(this, 'addTag', Clipperz.PM.DataModel.Record.archivedTag) ]), ], {trace:false}); }, 'isArchived': function () { return Clipperz.Async.callbacks("Record.isArchived", [ MochiKit.Base.method(this, 'tags'), function (someTags) { return MochiKit.Iter.some(someTags, MochiKit.Base.partial(MochiKit.Base.objEqual, Clipperz.PM.DataModel.Record.archivedTag))}, ], {trace:false}); }, //========================================================================= 'headerNotes': function () { return this.getIndexDataForKey('notes'); }, //------------------------------------------------------------------------- 'notes': function () { return Clipperz.Async.callbacks("Record.notes", [ MochiKit.Base.method(this, 'headerNotes'), MochiKit.Base.bind(function (someHeaderNotes) { var result; if ((someHeaderNotes == null) || (typeof(someHeaderNotes) == 'undefined')) { result = this.getValue('notes'); } else { result = MochiKit.Async.succeed(someHeaderNotes); } return result; }, this) ], {trace:false}); }, //......................................................................... 'setNotes': function (aValue) { return this.setValue('notes', aValue); }, //========================================================================= 'updateDate': function () { return MochiKit.Async.succeed(this._updateDate); }, 'accessDate': function () { return MochiKit.Async.succeed(this._accessDate); }, //========================================================================= certificateStatus: function() { // TODO: FAKE IMPLEMENTATION, just enough to test the UI var result; // if (this.reference().charAt(0) == 'a') { // result = 'published'; // } else if (this.reference().charAt(0) == '0') { // result = 'requested'; // } else { // result = ''; // } result = ''; return result; // 'requested' | 'published' | '' }, markAsCertified: function () { return this.addTag(Clipperz.PM.DataModel.Record.certifiedTag); }, hasBeenCertified: function () { return Clipperz.Async.callbacks("Record.isArchived", [ MochiKit.Base.method(this, 'tags'), function (someTags) { return MochiKit.Iter.some(someTags, MochiKit.Base.partial(MochiKit.Base.objEqual, Clipperz.PM.DataModel.Record.certifiedTag))}, ], {trace:false}); }, //========================================================================= 'favicon': function () { var result; var directLogins; directLogins = MochiKit.Base.values(this.directLogins()); if (directLogins.length > 0) { result = directLogins[0].favicon(); // } else if (/* is there an URL to use for searching a favicon */){ } else { result = null; // MochiKit.Async.succeed(Clipperz.PM.Strings['defaultFaviconUrl']); } return result; }, //------------------------------------------------------------------------- 'searchableContent': function () { var deferredResult; deferredResult = new Clipperz.Async.Deferred("Record.searchableContent", {trace:false}); deferredResult.collectResults({ 'recordLabel': MochiKit.Base.method(this, 'fullLabel'), 'directLoginLabels': [ MochiKit.Base.method(this, 'directLoginReferences'), MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.itemgetter('label')) ] }) deferredResult.addCallback(function (someValues) { return someValues['recordLabel'] + ' ' + someValues['directLoginLabels'].join(' '); }); deferredResult.callback(); return deferredResult; }, //------------------------------------------------------------------------- 'isMatching': function (aRegExp) { return Clipperz.Async.callbacks("deferredFilterFunction", [ MochiKit.Base.method(this, 'searchableContent'), MochiKit.Base.method(aRegExp, 'test'), function (doesItMatch) { var result; if (doesItMatch) { result = MochiKit.Async.succeed('match'); } else { result = MochiKit.Async.fail('miss'); } return result; } ], {trace:false}); }, //========================================================================= 'content': function () { var deferredResult; var result; result = { 'fields': [], 'directLogins': [] }; deferredResult = new Clipperz.Async.Deferred("Record.content", {trace:false}); deferredResult.addMethod(this, 'reference'); deferredResult.addCallback(function (aValue) { result['reference'] = aValue; }); deferredResult.addMethod(this, 'label'); deferredResult.addCallback(function (aValue) { result['title'] = aValue; }); deferredResult.addMethod(this, 'notes'); deferredResult.addCallback(function (aValue) { result['notes'] = aValue; }); deferredResult.addMethod(this, 'fields'); 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['fields'].push(aValue); }); deferredResult.addMethod(this, 'directLogins'); 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.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; }, //========================================================================= 'directLogins': function () { return this._directLogins; }, 'bindDirectLogin': function (aDirectLogin) { this._directLogins[aDirectLogin.reference()] = aDirectLogin; }, 'directLoginWithReference': function (aDirectLoginReference) { return this._directLogins[aDirectLoginReference]; }, 'createNewDirectLoginFunction': function () { return this._createNewDirectLoginFunction; }, '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(); return this.createNewDirectLoginFunction()(this); }, 'removeDirectLogin': function(aDirectLogin) { this.saveOriginalDirectLoginStatusToTransientState(); return Clipperz.Async.callbacks("Record.removeDirectLogin", [ MochiKit.Base.method(this, 'removeValue', 'directLogins' + '.' + aDirectLogin.reference()), MochiKit.Base.bind(function () { delete this._directLogins[aDirectLogin.reference()] }, this) ], {trace:false}); }, 'directLoginReferences': function () { var result; result = Clipperz.Async.callbacks("Record.directLoginReferences", [ MochiKit.Base.method(this, 'directLogins'), MochiKit.Base.values, function (someDirectLogins) { var result; var i,c; result = []; c = someDirectLogins.length; for (i=0; i>> TAGS", someValues); return someValues; }, MochiKit.Base.keys, MochiKit.Base.partial(MochiKit.Base.filter, Clipperz.PM.DataModel.Record.isRegularTag), MochiKit.Base.partial(MochiKit.Base.filter, Clipperz.PM.DataModel.Record.isNotImportTag), function (tags) { return MochiKit.Iter.reduce(function (result, value) { result[value] = true; return result; }, tags, {}); }, //function (someValues) { console.log("<<< TAGS", someValues); return someValues; }, MochiKit.Base.method(this, 'updateTags'), MochiKit.Base.method(aRecord, 'notes'), MochiKit.Base.method(this, 'setNotes'), MochiKit.Base.method(aRecord, 'getFieldsValues'), MochiKit.Base.values, MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addField')), Clipperz.Async.collectAll, 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, 'bindDirectLogin')), //function (aValue) { console.log("-> DirectLogin Values", aValue); return aValue; }, // Clipperz.Async.collectAll, // TODO: attachments MochiKit.Base.bind(function () { return this; }, this) ], {trace:false}); }, 'directLoginWithJsonData': function (someData) { var result; result = new Clipperz.PM.DataModel.DirectLogin({'record': this}); return result; }, 'setUpWithJSON': function(data, labelPostfix) { return Clipperz.Async.callbacks("Record.setUpWithJSON", [ // TODO: proper tag handling MochiKit.Base.method(this,'setLabel', data['label'] + ((labelPostfix) ? labelPostfix : '')), MochiKit.Base.method(this,'setNotes', data['data']['notes']), // TODO: check whether fields' order is kept or not MochiKit.Base.partial(MochiKit.Base.values, data['currentVersion']['fields']), MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addField')), Clipperz.Async.collectAll, MochiKit.Base.partial(MochiKit.Async.succeed, this), ], {trace:false}); }, //========================================================================= 'exportDirectLogins': function() { var result; var directLoginsObject = this.directLogins(); if (MochiKit.Base.keys(directLoginsObject).length == 0) { result = {}; } else { var callbackObject = Object.keys(directLoginsObject).reduce(function(previous, current) { previous[current] = MochiKit.Base.method( directLoginsObject[current], 'serializedData' ); return previous; }, {}); result = Clipperz.Async.collectResults("Record.exportDirectLogins", callbackObject,{trace:false})(); } return result; }, 'export': function() { var deferredResult; var label; var data; var currentVersion; // var directLogins; var currentVersionObject; data = {}; currentVersion = {}; // directLogins = {}; deferredResult = new Clipperz.Async.Deferred('Record.export', {trace:false}); deferredResult.addMethod(this, 'getCurrentRecordVersion'); deferredResult.addCallback(function(recordVersionIn) { currentVersionObject = recordVersionIn; }) deferredResult.addMethod(this, 'fullLabel'); deferredResult.addMethod(this, function(labelIn) {label = labelIn}); deferredResult.addMethod(this, 'exportDirectLogins'); deferredResult.addCallback(function(directLoginsIn) { data['directLogins'] = directLoginsIn; }); deferredResult.addCallback(function() { return currentVersionObject.getKey(); }), // deferredResult.addMethod(this,function(keyIn) { data['currentVersionKey'] = keyIn; }); deferredResult.addMethod(this, 'notes'); deferredResult.addMethod(this, function(notesIn) { data['notes'] = notesIn; }); // deferredResult.addMethod(this, function() { currentVersion['reference'] = this.currentVersionReference(); }); deferredResult.addCallback(function() { return currentVersionObject.exportFields(); }), deferredResult.addCallback(function(fieldsIn) { currentVersion['fields'] = fieldsIn; }); deferredResult.addMethod(this, function() { return { 'label': label, 'data': data, 'currentVersion': currentVersion }; }); deferredResult.callback(); return deferredResult; }, //========================================================================= __syntaxFix__: "syntax fix" }); Clipperz.PM.DataModel.Record.defaultCardInfo = { '_rowObject': MochiKit.Async.succeed, '_reference': MochiKit.Base.methodcaller('reference'), '_searchableContent': MochiKit.Base.methodcaller('searchableContent'), '_accessDate': MochiKit.Base.methodcaller('accessDate'), '_isArchived': MochiKit.Base.methodcaller('isArchived'), '_isBrandNew': MochiKit.Base.methodcaller('isBrandNew'), 'label': MochiKit.Base.methodcaller('label'), 'favicon': MochiKit.Base.methodcaller('favicon'), 'attachmentsCount': MochiKit.Base.methodcaller('attachmentsCount'), 'hasBeenCertified': MochiKit.Base.methodcaller('hasBeenCertified'), }; Clipperz.PM.DataModel.Record.defaultSearchField = '_searchableContent'; Clipperz.PM.DataModel.Record.tagChar = '\uE009'; Clipperz.PM.DataModel.Record.specialTagChar = '\uE010'; //Clipperz.PM.DataModel.Record.tagSpace = '\uE011'; Clipperz.PM.DataModel.Record.specialTagsConstructor = function (aTag) { return Clipperz.PM.DataModel.Record.specialTagChar + aTag; //.replace(/\s/g, Clipperz.PM.DataModel.Record.tagSpace); } Clipperz.PM.DataModel.Record.archivedTag = Clipperz.PM.DataModel.Record.specialTagsConstructor('ARCH'); Clipperz.PM.DataModel.Record.certifiedTag = Clipperz.PM.DataModel.Record.specialTagsConstructor('CERT'); Clipperz.PM.DataModel.Record.regExpForTag = function (aTag) { return new RegExp('\\' + Clipperz.PM.DataModel.Record.tagChar + aTag, 'g'); }; Clipperz.PM.DataModel.Record.regExpForNoTag = function () { return new RegExp('^((?!\\' + Clipperz.PM.DataModel.Record.tagChar + '[^' + Clipperz.PM.DataModel.Record.specialTagChar + ']).)*$', 'g'); } Clipperz.PM.DataModel.Record.isSpecialTag = function (aTag) { return aTag.indexOf(Clipperz.PM.DataModel.Record.specialTagChar) == 0; }; Clipperz.PM.DataModel.Record.isRegularTag = function (aTag) { return !Clipperz.PM.DataModel.Record.isSpecialTag(aTag); }; Clipperz.PM.DataModel.Record.isNotImportTag = function (aTag) { return aTag.indexOf('Import_') != 0; }; Clipperz.PM.DataModel.Record.regExpForSearch = function (aSearch) { return new RegExp(aSearch.replace(/[^A-Za-z0-9]/g, '\\$&'), 'i'); }; // Slightly more readable: http://rubular.com/r/4O8xGEsLgw Clipperz.PM.DataModel.Record.tagRegExp = new RegExp('\\' + Clipperz.PM.DataModel.Record.tagChar + '(' + Clipperz.PM.DataModel.Record.specialTagChar + '?[^'+Clipperz.PM.DataModel.Record.tagChar+']+?)\\s*(?='+Clipperz.PM.DataModel.Record.tagChar+'|$)', 'g'); Clipperz.PM.DataModel.Record.trimSpacesRegExp = new RegExp('^\\s+|\\s+$', 'g'); Clipperz.PM.DataModel.Record.extractLabelFromFullLabel = function (aValue) { var value; value = aValue; value = value.replace(Clipperz.PM.DataModel.Record.tagRegExp, ''); value = value.replace(Clipperz.PM.DataModel.Record.trimSpacesRegExp, ''); return value; }; Clipperz.PM.DataModel.Record.extractTagsFromFullLabel = function (aLabel) { var tagRegEx; var result; var match; result = {}; tagRegEx = Clipperz.PM.DataModel.Record.tagRegExp; match = tagRegEx.exec(aLabel); while (match != null) { result[match[1]] = true; match = tagRegEx.exec(aLabel); } return result; }; Clipperz.PM.DataModel.Record.labelContainsTag = function (aLabel, aTag) { return MochiKit.Iter.some( MochiKit.Base.keys(Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aLabel)), MochiKit.Base.partial(MochiKit.Base.operator.eq, aTag) ); }; Clipperz.PM.DataModel.Record.labelContainsArchiveTag = function (aLabel) { return Clipperz.PM.DataModel.Record.labelContainsTag(aLabel, Clipperz.PM.DataModel.Record.archivedTag); };