Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,11 @@
;'use strict';window.ContactsCleaner=function(contacts){this.lcontacts=contacts;var total=contacts.length;var next=0;var self=this;var CHUNK_SIZE=5;var numResponses=0;var notifyClean=false;var mustHold=false;var holded=false;var mustFinish=false;this.start=function(){mustHold=holded=mustFinish=false;if(total>0){cleanContacts(0);}
else if(typeof self.onsuccess==='function'){window.setTimeout(self.onsuccess,0);}};this.hold=function(){mustHold=true;};this.finish=function(){mustFinish=true;if(holded){notifySuccess();}};this.resume=function(){mustHold=holded=mustFinish=false;window.setTimeout(function resume_clean(){cleanContacts(next);});};this.performClean=function(contact,number,cbs){var req=navigator.mozContacts.remove(utils.misc.toMozContact(contact));req.number=number;req.onsuccess=cbs.success;req.onerror=function(e){errorHandler(contact.id,e.target.error);};};function successHandler(e){if(notifyClean||typeof self.oncleaned==='function'){notifyClean=true;var cleaned=e.target.number;window.setTimeout(function(){self.oncleaned(cleaned);},0);}
continueCb();}
function errorHandler(contactid,error){if(typeof self.onerror==='function'){self.onerror(contactid,error);}
continueCb();}
function cleanContacts(from){for(var idx=from;idx<(from+CHUNK_SIZE)&&idx<total;idx++){var contact=contacts[idx];self.performClean(contact,idx,{success:successHandler});}}
function notifySuccess(){if(typeof self.onsuccess==='function'){window.setTimeout(self.onsuccess);}}
function continueCb(){next++;numResponses++;if(next<total&&numResponses===CHUNK_SIZE){numResponses=0;if(!mustHold&&!mustFinish){cleanContacts(next);}
else if(mustFinish&&!holded){notifySuccess();}
if(mustHold){holded=true;}}
else if(next>=total){notifySuccess();}}};

View file

@ -0,0 +1,67 @@
;(function(exports){'use strict';function MatcherObj(pdataProvider){var blankRegExp=/\s+/g;var FB_CATEGORY='facebook';var FB_LINKED='fb_linked';var dataProvider=pdataProvider;function MultipleMatcher(ptargets,pmatchingOptions){var next=0;var self=this;var targets=ptargets;var matchingOptions=pmatchingOptions;var finalMatchings={};function doMatchBy(target,callbacks){var options={filterValue:target,filterBy:matchingOptions.filterBy,filterOp:matchingOptions.filterOp};var req=dataProvider.find(options);req.onsuccess=function(){var matchings=req.result.filter(function(aResult){return filterFacebook(aResult,matchingOptions.linkParams);});var filterBy=options.filterBy;var sanitizedTarget=SimplePhoneMatcher.sanitizedNumber(target);var targetVariants=SimplePhoneMatcher.generateVariants(target);matchings.forEach(function(aMatching){if(matchingOptions.selfContactId===aMatching.id){return;}
var field=options.filterBy[0];var values=aMatching[field];values.forEach(function(aValue){var value=aValue.value;var sanitizedValue=SimplePhoneMatcher.sanitizedNumber(value);var valueMatched=false;if(targetVariants.length>1){if(targetVariants.indexOf(sanitizedValue)!==-1){valueMatched=true;}}else if(SimplePhoneMatcher.generateVariants(sanitizedValue).indexOf(sanitizedTarget)!==-1){valueMatched=true;}
if(valueMatched){var matchings,matchingObj;if(!finalMatchings[aMatching.id]){matchingObj={matchings:{},matchingContact:aMatching};finalMatchings[aMatching.id]=matchingObj;matchingObj.matchings[filterBy[0]]=[];}
else{matchingObj=finalMatchings[aMatching.id];}
matchings=matchingObj.matchings[filterBy[0]];var sameValueMatchings=matchings.filter(function(matching){return(matching.matchedValue===value);});if(sameValueMatchings.length===0){matchings.push({'target':target,'matchedValue':value});}}});});carryOn();};req.onerror=function(e){window.console.error('Error while trying to do the matching',e.target.error.name);notifyMismatch(self);};}
function carryOn(){next++;if(next<targets.length){doMatchBy(targets[next],callbacks);}
else if(Object.keys(finalMatchings).length>0){notifyMatch(self,finalMatchings);}
else{notifyMismatch(self);}}
function matched(contacts){carryOn();}
var callbacks={onmatch:matched,onmismatch:carryOn};this.start=function(){doMatchBy(targets[0],callbacks);};}
function notifyMatch(obj,matchings){typeof obj.onmatch==='function'&&obj.onmatch(matchings);}
function notifyMismatch(obj){typeof obj.onmismatch==='function'&&obj.onmismatch();}
function matchBy(aContact,field,filterOper,callbacks,poptions){var options=poptions||{};var values=[];if(Array.isArray(aContact[field])){aContact[field].forEach(function(aField){if(field==='tel'){var variants=SimplePhoneMatcher.generateVariants(aField.value);variants.forEach(function(aVariant){values.push(aVariant);});}
else if(typeof aField.value==='string'){values.push(aField.value.trim());}});}
if(values.length>0){var matcher=new MultipleMatcher(values,{filterBy:[field],filterOp:filterOper,selfContactId:aContact.id,linkParams:options});matcher.onmatch=callbacks.onmatch;matcher.onmismatch=callbacks.onmismatch;matcher.start();}
else{notifyMismatch(callbacks);}}
function matchByTel(aContact,callbacks,options){matchBy(aContact,'tel','match',callbacks,options);}
function matchByEmail(aContact,callbacks,options){matchBy(aContact,'email','startsWith',callbacks,options);}
function doMatch(aContact,mode,callbacks){if(mode==='passive'){doMatchPassive(aContact,callbacks);}
else if(mode==='active'){doMatchActive(aContact,callbacks);}}
function doMatchTelAndEmail(aContact,callbacks,options){var localCbs={onmatch:function(telMatches){var matchCbs={onmatch:function(mailMatches){var allMatches=telMatches;Object.keys(mailMatches).forEach(function(aMatch){if(!allMatches[aMatch]){allMatches[aMatch]=mailMatches[aMatch];}
else{allMatches[aMatch].matchings.email=mailMatches[aMatch].matchings.email;}});notifyMatch(callbacks,allMatches);},onmismatch:function(){notifyMatch(callbacks,telMatches);}};matchByEmail(aContact,matchCbs,options);},onmismatch:function(){matchByEmail(aContact,callbacks,options);}};matchByTel(aContact,localCbs,options);}
function getSourceSimUrl(aContact){var out;if(Array.isArray(aContact.category)&&aContact.category.indexOf('sim')!==-1&&Array.isArray(aContact.url)){for(var j=0;j<aContact.url.length;j++){var aUrl=aContact.url[j];if(aUrl.type.indexOf('source')!==-1&&aUrl.type.indexOf('sim')!==-1){out=aUrl.value;}}}
return out;}
function doMatchPassive(aContact,callbacks){if(!hasName(aContact)){notifyMismatch(callbacks);return;}
var simUrl=getSourceSimUrl(aContact);if(simUrl){performPassiveMatchForSimContact(simUrl,aContact,callbacks);}
else{performPassiveMatch(aContact,callbacks);}}
function performPassiveMatchForSimContact(simUrl,aContact,callbacks){var localCbs={onmatch:function(results){var matchingFound=false;Object.keys(results).forEach(function(aResultId){var matchingContact=results[aResultId].matchingContact;var matchingUrl=getSourceSimUrl(matchingContact);if(matchingUrl&&matchingUrl===simUrl){matchingFound=true;var matchings={};matchings[aResultId]={matchingContact:matchingContact};callbacks.onmatch(matchings);return;}});if(!matchingFound){performPassiveMatch(aContact,callbacks);}},onmismatch:function(){performPassiveMatch(aContact,callbacks);}};matchByName(aContact,localCbs);}
function performPassiveMatch(aContact,callbacks){var matchingsFound={};var localCbs={onmatch:function(results){var names=[];Object.keys(results).forEach(function(aResultId){var mContact=results[aResultId].matchingContact;if(!hasName(mContact)){return;}
var targetFN=null;if(!isEmptyStr(aContact.familyName)){targetFN=Normalizer.toAscii(aContact.familyName[0].trim().toLowerCase()).replace(blankRegExp,'');}
var targetGN=null;if(!isEmptyStr(aContact.givenName)){targetGN=Normalizer.toAscii(aContact.givenName[0].trim().toLowerCase()).replace(blankRegExp,'');}
var targetName=(targetGN||'')+(targetFN||'');var mFamilyName=null;var mGivenName=null;if(!isEmptyStr(mContact.familyName)){mFamilyName=Normalizer.toAscii(mContact.familyName[0].trim().toLowerCase()).replace(blankRegExp,'');}
if(!isEmptyStr(mContact.givenName)){mGivenName=Normalizer.toAscii(mContact.givenName[0].trim().toLowerCase()).replace(blankRegExp,'');}
var mName=(mGivenName||'')+(mFamilyName||'');names.push({contact:mContact,familyName:mFamilyName,givenName:mGivenName,name:mName});var matchingList=names.filter(function(obj){return((obj.familyName===targetFN&&obj.givenName===targetGN)||(obj.name&&obj.name===targetName)&&(!Array.isArray(obj.contact.category)||obj.contact.category.indexOf(FB_CATEGORY)===-1));});matchingList.forEach(function(aMatching){var contact=aMatching.contact;matchingsFound[contact.id]={matchingContact:contact};});});reconcileResults(aContact,matchingsFound,results,callbacks);},onmismatch:function(){notifyMismatch(callbacks);}};doMatchTelAndEmail(aContact,localCbs);}
function doMatchActive(aContact,callbacks){var options={linkedMatched:{},linkedTo:getLinkedTo(aContact)};var localCbs={onmatch:function(results){var cbsName={onmatch:function(nameResults){Object.keys(nameResults).forEach(function(aId){if(!results[aId]){results[aId]=nameResults[aId];}
else{results[aId].matchings.name=nameResults[aId].matchings.name;}});notifyMatch(callbacks,results);},onmismatch:function(){notifyMatch(callbacks,results);}};matchByName(aContact,cbsName,options);},onmismatch:function(){matchByName(aContact,callbacks,options);}};doMatchTelAndEmail(aContact,localCbs,options);}
function notifyFindNameReady(){document.dispatchEvent(new CustomEvent('by_name_ready'));}
function matchByName(aContact,callbacks,options){var isSimContact=(Array.isArray(aContact.category)&&aContact.category.indexOf('sim')!==-1);if((isEmptyStr(aContact.familyName)||isEmptyStr(aContact.givenName))&&!isSimContact){notifyMismatch(callbacks);return;}
var finalResult={};var resultsByName=[];if(!isEmptyStr(aContact.name)){var targetName=aContact.name[0].trim();var reqName=dataProvider.find({filterValue:targetName,filterBy:['name'],filterOp:'startsWith'});reqName.onsuccess=function(){resultsByName=reqName.result.filter(function(aResult){return filterFacebook(aResult,options);});notifyFindNameReady();};reqName.onerror=function(e){window.console.warn('Error while trying to find by name: ',e.target.error.name);notifyFindNameReady();};}
else{notifyFindNameReady();}
if(isEmptyStr(aContact.familyName)){endOfMatchByName(finalResult,aContact,resultsByName,callbacks);}
else{var targetFamilyName=aContact.familyName[0].trim();var reqFamilyName=dataProvider.find({filterValue:targetFamilyName,filterBy:['familyName'],filterOp:'startsWith'});reqFamilyName.onsuccess=function(){var results=reqFamilyName.result;var givenNames=[];var targetGN=null;if(!isEmptyStr(aContact.givenName)){targetGN=Normalizer.toAscii(aContact.givenName[0].trim().toLowerCase()).replace(blankRegExp,'');}
results.forEach(function(mContact){if(mContact.id===aContact.id||isEmptyStr(mContact.givenName)){return;}
givenNames.push({contact:mContact,givenName:Normalizer.toAscii(mContact.givenName[0].trim().toLowerCase()).replace(blankRegExp,'')});});var finalMatchings=givenNames.filter(function(obj){var gn=obj.givenName;return((gn===targetGN||targetGN.startsWith(gn)||gn.startsWith(targetGN))&&filterFacebook(obj.contact,options));});finalMatchings.forEach(function(aMatching){finalResult[aMatching.contact.id]={matchings:{'name':[{target:targetFamilyName,matchedValue:Array.isArray(aMatching.contact.name)?aMatching.contact.name[0]:getCompleteName(aMatching.contact)}]},matchingContact:aMatching.contact};});if(resultsByName){endOfMatchByName(finalResult,aContact,resultsByName,callbacks);}
else{document.addEventListener('by_name_ready',function nameReady(){document.removeEventListener('by_name_ready',nameReady);endOfMatchByName(finalResult,aContact,resultsByName,callbacks);});}};reqFamilyName.onerror=function(e){window.console.error('Error while trying to find by familyName: ',e.target.error.name);notifyMismatch(callbacks);};}}
function getLinkedTo(contact){var out=null;if(Array.isArray(contact.category)){var idx=contact.category.indexOf(FB_LINKED);if(idx!==-1){out=contact.category[idx+1];}}
return out;}
function isFbLinked(contact){return(Array.isArray(contact.category)&&contact.category.indexOf(FB_LINKED)!==-1);}
function isFbContact(contact){return(Array.isArray(contact.category)&&contact.category.indexOf(FB_CATEGORY)!==-1);}
function filterFacebook(contact,linkParams){var out=false;if(!isFbContact(contact)){out=true;}
else if(isFbLinked(contact)){var linkedTo=getLinkedTo(contact);var targetUid=linkParams.linkedTo||'';var linkedMatched=linkParams.linkedMatched||{};if(targetUid===linkedTo){out=true;}
else if((Object.keys(linkedMatched).length===0||linkedMatched[linkedTo])&&!targetUid){linkedMatched[linkedTo]=linkedTo;out=true;}}
return out;}
function endOfMatchByName(finalResult,aContact,resultsByName,callbacks){resultsByName.forEach(function(aResult){if(aResult.id===aContact.id){return;}
var matchingObj={matchings:{'name':[{target:(Array.isArray(aContact.name)&&(typeof aContact.name[0]==='string'))?aContact.name[0].trim():getCompleteName(aContact),matchedValue:Array.isArray(aResult.name)?aResult.name[0]:getCompleteName(aResult)}]},matchingContact:aResult};if(!finalResult[aResult.id]){finalResult[aResult.id]=matchingObj;}
else{finalResult[aResult.id].matchings.name=matchingObj.matchings.name;}});if(Object.keys(finalResult).length>0){notifyMatch(callbacks,finalResult);}
else{notifyMismatch(callbacks);}}
function getCompleteName(contact){var givenName=Array.isArray(contact.givenName)?contact.givenName[0]:'';var familyName=Array.isArray(contact.familyName)?contact.familyName[0]:'';var completeName=givenName&&familyName?givenName+' '+familyName:givenName||familyName;return completeName;}
function isEmpty(collection){return((!Array.isArray(collection)||!collection[0])||(collection[0]&&collection[0].value&&!collection[0].value.trim()));}
function isEmptyStr(collection){return(!Array.isArray(collection)||(typeof collection[0]!=='string')||!(collection[0].trim()));}
function hasName(aContact){return(!isEmptyStr(aContact.givenName)||!isEmptyStr(aContact.familyName));}
function reconcileResults(incomingContact,nameMatches,phoneMailMatches,callbacks){var finalMatchings={};Object.keys(nameMatches).forEach(function(aNameMatching){var matchingContact=nameMatches[aNameMatching].matchingContact;var isPhoneMatching=Array.isArray(phoneMailMatches[aNameMatching].matchings.tel);var isMailMatching=Array.isArray(phoneMailMatches[aNameMatching].matchings.email);if(isPhoneMatching&&isMailMatching){finalMatchings[aNameMatching]=phoneMailMatches[aNameMatching];}
else if(isPhoneMatching&&(isEmpty(incomingContact.email)||isEmpty(matchingContact.email))){finalMatchings[aNameMatching]=phoneMailMatches[aNameMatching];}
else if(isMailMatching&&(isEmpty(incomingContact.tel)||isEmpty(matchingContact.tel))){finalMatchings[aNameMatching]=phoneMailMatches[aNameMatching];}});if(Object.keys(finalMatchings).length>0){notifyMatch(callbacks,finalMatchings);}
else{notifyMismatch(callbacks);}}
this.match=doMatch;Object.defineProperty(this,'dataProvider',{set:function(theProvider){dataProvider=theProvider;}});}
exports.Matcher=new MatcherObj(navigator.mozContacts);}(window));

View file

@ -0,0 +1,33 @@
;'use strict';var contacts=window.contacts||{};contacts.Merger=(function(){var DEFAULT_ADR_TYPE='home';var DEFAULT_TEL_TYPE='other';var DEFAULT_EMAIL_TYPE='other';function doMerge(pmasterContact,pmatchingContacts,callbacks){window.setTimeout(function contactsMerge(){mergeAll(pmasterContact,pmatchingContacts,filled,callbacks);},0);}
function doInMemoryMerge(pmasterContact,pmatchingContacts){return new Promise(function(resolve,reject){mergeAll(pmasterContact,pmatchingContacts,inMemoryMergeDone,{success:function(masterContact){resolve(masterContact);},error:function(err){reject(err);}});});}
function inMemoryMergeDone(callbacks,matchingContacts,masterContact){callbacks.success(masterContact);}
function isSimContact(contact){return Array.isArray(contact.category)&&contact.category.indexOf('sim')!==-1;}
function mergeAll(masterContact,matchingContacts,onFilled,callbacks){var emailsHash;var categoriesHash;var telsHash;var mergedContact={};var mergedPhoto;mergedContact.givenName=[];copyStringArray(masterContact.givenName,mergedContact.givenName);mergedContact.familyName=[];copyStringArray(masterContact.familyName,mergedContact.familyName);mergedContact.photo=masterContact.photo||[];mergedPhoto=mergedContact.photo;mergedContact.bday=masterContact.bday;mergedContact.anniversary=masterContact.anniversary;mergedContact.adr=masterContact.adr||[];telsHash={};mergedContact.tel=[];if(Array.isArray(masterContact.tel)){masterContact.tel.forEach(function(aTel){if(!telsHash[aTel.value]){aTel.type=(Array.isArray(aTel.type)?aTel.type:[aTel.type]);aTel.type[0]=aTel.type[0]||DEFAULT_TEL_TYPE;mergedContact.tel.push(aTel);}
var variants=SimplePhoneMatcher.generateVariants(aTel.value);variants.forEach(function(aVariant){telsHash[aVariant]=true;});});}
mergedContact.email=[];emailsHash={};populateEmails(masterContact.email,emailsHash,mergedContact.email);mergedContact.org=masterContact.org||[];mergedContact.category=[];categoriesHash={};populateNoDuplicates(masterContact.category,categoriesHash,mergedContact.category);mergedContact.url=masterContact.url||[];mergedContact.note=masterContact.note||[];matchingContacts.forEach(function(aResult){var theMatchingContact=aResult.matchingContact;var givenName=theMatchingContact.givenName;if(Array.isArray(givenName)&&givenName[0]){if(mergedContact.givenName.indexOf(givenName[0])===-1){if(mergedContact.givenName[0]&&mergedContact.givenName[0].trim()){mergedContact.givenName.push(givenName[0]);}
else{mergedContact.givenName[0]=givenName[0];}}}
var familyName=theMatchingContact.familyName;if(Array.isArray(familyName)&&familyName[0]){if(mergedContact.familyName.indexOf(familyName[0])===-1){if(mergedContact.familyName[0]&&mergedContact.familyName[0].trim()){mergedContact.familyName.push(familyName[0]);}
else{mergedContact.familyName[0]=familyName[0];}}}
if((!isDefined(masterContact.givenName)||!isDefined(masterContact.familyName))&&isSimContact(theMatchingContact)){if(isDefined(mergedContact.givenName)&&isDefined(mergedContact.familyName)&&mergedContact.familyName[0]===mergedContact.givenName[0]){if(!isDefined(masterContact.givenName)){mergedContact.givenName=[];mergedContact.name=mergedContact.familyName;}
if(!isDefined(masterContact.familyName)){mergedContact.familyName=[];mergedContact.name=mergedContact.givenName;}}}
if(!mergedContact.bday&&theMatchingContact.bday){mergedContact.bday=theMatchingContact.bday;}
if(!mergedContact.anniversary&&theMatchingContact.anniversary){mergedContact.anniversary=theMatchingContact.anniversary;}
if(isDefined(theMatchingContact.org)&&mergedContact.org.length===0){mergedContact.org=theMatchingContact.org;}
if(isDefined(theMatchingContact.category)){populateNoDuplicates(theMatchingContact.category,categoriesHash,mergedContact.category);}
populateEmails(theMatchingContact.email,emailsHash,mergedContact.email);if(Array.isArray(theMatchingContact.tel)){var theMatchings=aResult.matchings||{};var telMatchings=theMatchings.tel;theMatchingContact.tel.forEach(function(aTel){var theValue=aTel.value;var target=theValue,matchedValue='';if(telMatchings){var matchedFrom=telMatchings.filter(function(x){return(x.target===theValue||x.matchedValue===theValue);});if(matchedFrom[0]){target=matchedFrom[0].target;matchedValue=matchedFrom[0].matchedValue;}}
var variants=SimplePhoneMatcher.generateVariants(theValue);var alreadyPresent=false;for(var j=0;j<variants.length;j++){if(telsHash[variants[j]]){alreadyPresent=true;}}
if(!alreadyPresent){theValue=target.length>matchedValue.length?target:matchedValue;mergedContact.tel.push({type:aTel.type||[DEFAULT_TEL_TYPE],value:theValue,carrier:aTel.carrier,pref:aTel.pref});variants.forEach(function(aVariant){telsHash[aVariant]=true;});}});}
if(!Array.isArray(mergedPhoto)||mergedPhoto.length===0&&isDefined(theMatchingContact.photo)){var photo=ContactPhotoHelper.getFullResolution(theMatchingContact);mergedPhoto=[photo];}
populateField(theMatchingContact.adr,mergedContact.adr,DEFAULT_ADR_TYPE);populateField(theMatchingContact.url,mergedContact.url);populateField(theMatchingContact.note,mergedContact.note);});mergedContact.name=[((mergedContact.givenName[0]?mergedContact.givenName[0]:'')+' '+
(mergedContact.familyName[0]?mergedContact.familyName[0]:'')).trim()];fillMasterContact(masterContact,mergedContact,mergedPhoto,onFilled.bind(null,callbacks,matchingContacts));}
function filled(callbacks,matchingContacts,masterContact){var req=navigator.mozContacts.save(utils.misc.toMozContact(masterContact));req.onsuccess=function(){matchingContacts.forEach(function(aMatchingContact){if(aMatchingContact.matchingContact.id){var contact=aMatchingContact.matchingContact;navigator.mozContacts.remove(utils.misc.toMozContact(contact));}});if(typeof callbacks.success==='function'){callbacks.success(masterContact);}};req.onerror=function(){window.console.error('Error while saving merged Contact: ',req.error.name);typeof callbacks.error==='function'&&callbacks.error(req.error);};}
function isDefined(field){return(Array.isArray(field)&&field[0]&&((typeof field[0]==='string'&&field[0].trim().length>0)||typeof field[0]==='object'));}
function copyStringArray(source,dest){if(Array.isArray(source)){source.forEach(function(aVal){if(aVal&&aVal.trim()){dest.push(aVal);}});}}
function populateEmails(sourceEmails,hash,out){if(Array.isArray(sourceEmails)){sourceEmails.forEach(function(aEmail){aEmail.type=Array.isArray(aEmail.type)?aEmail.type:[aEmail.type];aEmail.type[0]=aEmail.type[0]||DEFAULT_EMAIL_TYPE;var value=aEmail.value;if(value&&value.trim()){value=value.toLowerCase();if(!hash[value]){aEmail.value=value;out.push(aEmail);hash[value]=true;}}});}}
function populateNoDuplicates(source,hash,out){if(Array.isArray(source)){source.forEach(function(aCat){if(!hash[aCat]){out.push(aCat);hash[aCat]=true;}});}}
function populateField(source,destination,defaultType){if(Array.isArray(source)){var stringifiedDestination=destination.map(function(element){return JSON.stringify(element);});source.forEach(function(as,index){if(stringifiedDestination.indexOf(JSON.stringify(as))===-1){if(defaultType&&(!as.type||!as.type[0])){as.type=[defaultType];}
destination.push(as);}});}}
function fillMasterContact(masterContact,mergedContact,mergedPhotos,done){var fields=['familyName','givenName','name','org','email','tel','bday','anniversary','adr','category','url','note','photo'];fields.forEach(function(aField){masterContact[aField]=mergedContact[aField];});if(!Array.isArray(mergedPhotos)||!mergedPhotos[0]||mergedPhotos.length>=2){done(masterContact);return;}
var mergedPhoto=mergedPhotos[0];utils.thumbnailImage(mergedPhoto,function gotTumbnail(thumbnail){if(mergedPhoto!==thumbnail){masterContact.photo=[mergedPhoto,thumbnail];}else{masterContact.photo=[mergedPhoto];}
done(masterContact);});}
return{merge:doMerge,inMemoryMerge:doInMemoryMerge};})();

View file

@ -0,0 +1,18 @@
;'use strict';var utils=window.utils||{};if(!utils.alphaScroll){(function initScrollbar(doc){var alphaScroll=utils.alphaScroll={};var scrollToCallback,jumper,overlay,overlayStyle,groupSelector,liSearch,liFavorites;var isScrolling=false;var alreadyRendered=false;var isDesktop=false;var P_SCROLLTO_CB='scrollToCb';var P_JUMPER='jumper';var P_OVERLAY='overlay';var P_GROUP_SELECTOR='groupSelector';var TRANSITION_DELAY='0.3s';var TRANSITION_DURATION='0.2s';var RESET_TRANSITION='0s';var offset=0,lastY=0;var isTouch='ontouchstart'in window;var touchstart=isTouch?'touchstart':'mousedown';var touchmove=isTouch?'touchmove':'mousemove';var touchend=isTouch?'touchend':'mouseup';var getY=(function getYWrapper(){return isTouch?function(e){return e.touches[0].pageY;}:function(e){return e.pageY;};})();var getTarget=(function getTargetWrapper(){if(isTouch){return function(e){var touch=e.touches[0];return document.elementFromPoint(touch.pageX,touch.pageY);};}else{return function(e){return e.target;};}})();alphaScroll.init=function(params,desktop){isDesktop=params&&params.desktop;if(alreadyRendered&&!isDesktop){return;}
scrollToCallback=params[P_SCROLLTO_CB];jumper=params[P_JUMPER];overlay=params[P_OVERLAY];groupSelector=params[P_GROUP_SELECTOR];overlay.textContent='';overlayStyle=overlay.style;jumper.addEventListener(touchstart,scrollStart);jumper.addEventListener(touchmove,scrollTo);jumper.addEventListener(touchend,scrollEnd);if(typeof jumper==='string'){jumper=document.querySelector(jumper);}
var template=jumper.querySelector('*[data-template]');if(!template){alreadyRendered=true;return;}
var frag=document.createDocumentFragment();var alphabet=[];for(var i=65;i<=90;i++){alphabet.push({anchor:String.fromCharCode(i)});}
alphabet.push({anchor:'#'});utils.templates.append(jumper,alphabet,frag);jumper.appendChild(frag);alreadyRendered=true;};alphaScroll.showGroup=function(name){var selector='[data-anchor="group-'+name+'"]';var group=jumper.querySelector(selector);if(group){group.classList.remove('hide');}};alphaScroll.hideGroup=function(name){var selector='[data-anchor="group-'+name+'"]';var group=jumper.querySelector(selector);if(group){group.classList.add('hide');}};function hideExtraItems(value){if(!liSearch){liSearch=jumper.querySelector('li[data-anchor="search-container"]');}
if(!liFavorites){liFavorites=jumper.querySelector('li[data-anchor="group-favorites"]');}
liSearch.hidden=value;liFavorites.hidden=value;}
alphaScroll.toggleFormat=function(type){hideExtraItems(type==='short');};function scrollTo(evt){evt.preventDefault();evt.stopPropagation();if(!isScrolling){return;}
var currentY=getY(evt);if(Math.abs(lastY-currentY)<offset/2&&!isDesktop){return;}
lastY=currentY;var elem=getTarget(evt);if(!elem){return;}
var dataset=elem.dataset;if(dataset.letter){overlay.textContent=dataset.letter;}else if(dataset.img){overlay.textContent='';var img=new Image();img.src='style/images/'+dataset.img;overlay.appendChild(img);}
var anch=dataset.anchor;var selector=anch==='group-#'?'group-und':anch;var domTarget=doc.querySelector('#section-'+selector+', #'+selector);if(!domTarget){return;}
scrollToCallback(domTarget,selector.replace('group-',''));}
function scrollStart(evt){var dataset=getTarget(evt).dataset;evt.preventDefault();evt.stopPropagation();if(!dataset.letter&&!dataset.img){return;}
offset=offset||jumper.querySelector('[data-anchor]').offsetHeight;overlayStyle.MozTransitionDelay=RESET_TRANSITION;overlayStyle.MozTransitionDuration=RESET_TRANSITION;overlayStyle.opacity='1';isScrolling=true;scrollTo(evt);}
function scrollEnd(evt){evt.preventDefault();evt.stopPropagation();var transitionDelay=TRANSITION_DELAY;var transitionDuration=TRANSITION_DURATION;if(overlay.textContent==='#'||overlay.textContent===''){transitionDelay=RESET_TRANSITION;transitionDuration=RESET_TRANSITION;}
overlayStyle.MozTransitionDelay=transitionDelay;overlayStyle.MozTransitionDuration=transitionDuration;overlayStyle.opacity='0';overlay.textContent=null;isScrolling=false;lastY=0;}
(function(doc){var images=doc.querySelectorAll('li[data-img]');Object.keys(images).forEach(function(value){var img=new Image();img.src='contacts/style/images/'+images[value].dataset.img;});}(doc));})(document);}

View file

@ -0,0 +1,19 @@
;'use strict';(function(){var CHUNK_SIZE=5;window.ContactsImporter=function(pContacts,pAccessToken,pConnector){this.contacts=Object.keys(pContacts);var contactsHash=pContacts;var access_token=pAccessToken;var total=this.contacts.length;var self=this;var serviceConnector=pConnector;var numImported=0;var mustHold=false;var holded=false;var mustFinish=false;var isOnLine=navigator.onLine;var numMergedDuplicated=0;window.addEventListener('online',onLineChanged);window.addEventListener('offline',onLineChanged);function onLineChanged(){isOnLine=navigator.onLine;}
function contactSaved(e){var cfdata=this;if(typeof self.oncontactimported==='function'){window.setTimeout(function(){self.oncontactimported(cfdata);},0);}
continueCb();}
function contactSaveError(err){window.console.error('Error while importing contact: ',err.name);if(typeof self.onerror==='function'){window.setTimeout(self.onerror.bind(null,err),0);}
continueCb();}
function saveMozContact(deviceContact,successCb,errorCb){var req=navigator.mozContacts.save(utils.misc.toMozContact(deviceContact));req.onsuccess=successCb;req.onerror=errorCb;}
function pictureReady(blobPicture){var serviceContact=this;var done=function(){var deviceContact=self.adapt(serviceContact);self.persist(deviceContact,contactSaved.bind(serviceContact),contactSaveError);};if(!blobPicture){done();return;}
utils.thumbnailImage(blobPicture,function gotThumbnail(thumbnail){if(blobPicture!==thumbnail){serviceContact.photo=[blobPicture,thumbnail];}else{serviceContact.photo=[blobPicture];}
done();});}
function pictureError(){window.console.error('Error while getting picture for contact: ',this.user_id);self.persist(self.adapt(this),contactSaved.bind(this),contactSaveError);}
function pictureTimeout(){window.console.warn('Timeout while getting picture for contact: ',this.user_id);self.persist(self.adapt(this),contactSaved.bind(this),contactSaveError);}
this.start=function(){numMergedDuplicated=0;mustHold=false;holded=false;mustFinish=false;importContacts(numImported);};this.hold=function(){mustHold=true;};this.finish=function(){mustFinish=true;if(holded){notifySuccess();}};this.resume=function(){mustHold=false;holded=false;mustFinish=false;window.setTimeout(function resume_import(){importContacts(numImported);},0);};this.persist=function(contactData,successCb,errorCb){var cbs={onmatch:function(matches){numMergedDuplicated++;contacts.adaptAndMerge(this,matches,{success:successCb,error:errorCb});}.bind(utils.misc.toMozContact(contactData)),onmismatch:function(){saveMozContact(this,successCb,function onMismatchError(evt){errorCb(evt.target.error);});}.bind(contactData)};Matcher.match(contactData,'passive',cbs);};this.adapt=function(serviceContact){return serviceConnector.adaptDataForSaving(serviceContact);};function importContacts(from){for(var i=from;i<from+CHUNK_SIZE&&i<total;i++){importContact(i);}}
function importContact(index){var serviceContact=contactsHash[self.contacts[index]];var callbacks={success:pictureReady.bind(serviceContact),error:pictureError.bind(serviceContact),timeout:pictureTimeout.bind(serviceContact)};if(isOnLine===true){serviceConnector.downloadContactPicture(serviceContact,access_token,callbacks);}
else{callbacks.success(null);}}
function notifySuccess(){if(typeof self.onsuccess==='function'){window.setTimeout(function do_success(){self.onsuccess(numImported,numMergedDuplicated);},0);}}
function continueCb(){numImported++;var next=numImported+CHUNK_SIZE-1;if(next<total){if(!mustHold&&!mustFinish){importContact(next);}
else if(mustFinish&&!holded){notifySuccess();}
if(mustHold){holded=true;}}
else if(numImported>=total){notifySuccess();}}};})();

View file

@ -0,0 +1,383 @@
'use strict';
/* global fb, utils */
/* global Matcher */
if (!window.FacebookConnector) {
window.FacebookConnector = (function() {
var nextUpdateTime;
window.fb = window.fb || {};
fb.operationsTimeout = parent.config ?
parent.config.operationsTimeout : null;
// Only one instance in order to reuse it for saving friends in the
// database. If the instance is not released between transactions, new
// objects will be created.
var reusedFbContact = new fb.Contact();
reusedFbContact.ready = true;
function createFbImporter(clist, access_token) {
/* jshint validthis:true */
var out = new window.ContactsImporter(clist, access_token, this);
out.persist = persistFbData;
return out;
}
function persistFbData(data, successCb, errorCb, options) {
var saveCbs = {
success: function(e) {
if (options === 'not_match') {
successCb();
return;
}
var cbs = {
onmatch: function(matches) {
// For each of the matching contacts link with the just imported
Object.keys(matches).forEach(function(aMatchId) {
var fbContact = new
fb.Contact(matches[aMatchId].matchingContact);
var mozContReq = fb.utils.getMozContact(data.uid);
mozContReq.onsuccess = function() {
var linkParams = {
uid: data.uid,
mozContact: mozContReq.result
};
if (Array.isArray(data.photo) && data.photo[0]) {
linkParams.photoUrl = data.pic_big;
}
var req = fbContact.linkTo(linkParams);
req.onsuccess = successCb;
req.onerror = function error(e) {
window.console.error('Error while automatically linking : ',
aMatchId, data.uid, e.target.name);
successCb();
};
};
mozContReq.onerror = function(e) {
window.console.error(
'Error getting mozContact data in automatic linking: ',
e.target.error.name);
successCb();
};
});
},
onmismatch: successCb
};
// Try to match and if so merge is performed
Matcher.match(data, 'passive', cbs);
},
error: errorCb
};
saveFbContact(data, saveCbs.success, saveCbs.error);
}
function saveFbContact(data, successCb, errorCb) {
var fbContact, successWrapperCb;
if (reusedFbContact.ready) {
reusedFbContact.ready = null;
fbContact = reusedFbContact;
successWrapperCb = function onsuccess(e) {
reusedFbContact.ready = true;
successCb(e);
};
} else {
fbContact = new fb.Contact();
successWrapperCb = successCb;
}
fbContact.setData(data);
var req = fbContact.save();
req.onsuccess = successWrapperCb;
req.onerror = errorCb;
}
function setInfraForSync(existingContacts, friendsImported, callback) {
// From the FTU the infra for sync will not be loaded
if (!fb.sync) {
(typeof callback === 'function') && callback();
return;
}
// Check wether we need to set the update alarm
window.asyncStorage.getItem(fb.utils.ALARM_ID_KEY, function(data) {
if (!data || (existingContacts && existingContacts.length === 0) &&
!friendsImported) {
// This is the first contact imported
fb.utils.setLastUpdate(nextUpdateTime, function() {
var req = fb.sync.scheduleNextSync();
if (typeof callback === 'function') {
req.onsuccess = callback;
}
});
}
});
}
var UID_FILTER_IDX = 5;
var acc_tk;
function buildFriendQuery(uid) {
var aquery1 = [].concat(FRIENDS_QUERY);
aquery1[UID_FILTER_IDX] = '= ' + uid;
return aquery1.join('');
}
// Query that retrieves the information about friends
var FRIENDS_QUERY = [
'SELECT uid, name, first_name, last_name, pic_big, current_location, ' ,
'middle_name, birthday_date, email, profile_update_time, ' ,
' work, phones, hometown_location' ,
' FROM user' ,
' WHERE uid ',
'IN (SELECT uid1 FROM friend WHERE uid2=me())' ,
' ORDER BY ',
'first_name'
];
var friendsQueryStr = FRIENDS_QUERY.join('');
function contactDataLoaded(options, response) {
if (!response.error) {
// Just in case this is the first contact imported
nextUpdateTime = Date.now();
/* jshint validthis:true */
var successCb = this.success;
var friend = response.data[0];
if (friend) {
var out1 = self.adaptDataForShowing(friend);
var photoCbs = {};
photoCbs.success = function(blobPicture) {
if (!blobPicture) {
pictureLoaded(out1, options, successCb);
return;
}
utils.thumbnailImage(blobPicture,
function gotThumbnail(thumbnail) {
if (blobPicture !== thumbnail) {
out1.photo = [blobPicture, thumbnail];
} else {
out1.photo = [blobPicture];
}
pictureLoaded(out1, options, successCb);
});
}; // successCb
self.downloadContactPicture(friend, acc_tk, photoCbs);
} // if friend
else {
window.console.error('FB: No Friend data found');
/* jshint validthis:true */
this.error('No friend data found');
}
}
else {
/* jshint validthis:true */
this.error(response.error);
}
}
function pictureLoaded(friend, options, success) {
var data = self.adaptDataForSaving(friend);
/* jshint validthis:true */
persistFbData(data, function() {
success({
uid: friend.uid,
url: friend.pic_big
});
}, function error(err) {
console.error('Error while persisting: ', err);
}, options);
// If there is no an alarm set it has to be set
window.asyncStorage.getItem(fb.utils.ALARM_ID_KEY, function(data) {
if (!data) {
fb.utils.setLastUpdate(nextUpdateTime,
fb.sync.scheduleNextSync);
}
});
}
function FacebookConnector() { }
FacebookConnector.prototype = {
listAllContacts: function(access_token, callbacks, options) {
if (options && options.orderBy === 'lastName') {
FRIENDS_QUERY[FRIENDS_QUERY.length] = 'last_name';
friendsQueryStr = FRIENDS_QUERY.join('');
}
return fb.utils.runQuery(friendsQueryStr, {
success: callbacks.success,
error: callbacks.error,
timeout: callbacks.timeout
},access_token);
},
listDeviceContacts: function(callbacks) {
// Dummy implementation for the time being
var req = fb.utils.getAllFbContacts();
req.onsuccess = function successHandler() {
callbacks.success(req.result);
};
req.onerror = callbacks.error;
},
getImporter: function(contactsList, access_token) {
return createFbImporter.bind(this)(contactsList, access_token);
},
// Imports a Contact to FB indexedDB private database
importContact: function(uid, access_token, callbacks, options) {
acc_tk = access_token;
var oneFriendQuery = buildFriendQuery(uid);
var auxCallbacks = {
success: contactDataLoaded.bind(callbacks, options),
error: callbacks.error,
timeout: callbacks.timeout
};
fb.utils.runQuery(oneFriendQuery, auxCallbacks, access_token);
},
cleanContacts: function(contactsList, mode, cb) {
if (mode === 'update') {
var cleaner = new fb.utils.FbContactsCleaner(contactsList, mode);
window.setTimeout(cleaner.start, 0);
cb(cleaner);
}
else {
// Mode === clear
var req = fb.utils.clearFbData();
req.onsuccess = function() {
cb(req.result);
};
req.onerror = function(e) {
window.console.error('Error while starting cleaning: ',
e.target.error.name);
// Notifying the UI importer to show an error to the user
// As we cannot get a cleaner the param is set to null
cb(null);
};
}
},
adaptDataForShowing: function(source) {
var box = utils.misc.getPreferredPictureBox();
var picWidth = box.width;
var picHeight = box.height;
var out = fb.friend2mozContact(source);
out.contactPictureUri = 'https://graph.facebook.com/' +
source.uid + '/picture?type=square' +
'&width=' + picWidth +
'&height=' + picHeight +
'&t=' + Date.now();
return out;
},
adaptDataForSaving: function fbAdapt(cfdata) {
var worksAt = fb.getWorksAt(cfdata);
var address = fb.getAddresses(cfdata);
var birthDate = null;
if (cfdata.birthday_date && cfdata.birthday_date.length > 0) {
birthDate = fb.getBirthDate(cfdata.birthday_date);
}
var fbInfo = {
bday: birthDate,
org: [worksAt]
};
if (address) {
fbInfo.adr = address;
}
// This is the short telephone number to enable indexing
if (cfdata.shortTelephone) {
fbInfo.shortTelephone = cfdata.shortTelephone;
delete cfdata.shortTelephone;
}
// Check whether we were able to get the photo or not
fbInfo.url = [];
if (cfdata.photo) {
fbInfo.photo = cfdata.photo;
if (cfdata.pic_big) {
// The URL is stored for synchronization purposes
fb.setFriendPictureUrl(fbInfo, cfdata.pic_big);
}
delete cfdata.photo;
}
// Facebook info is set and then contact is saved
cfdata.fbInfo = fbInfo;
return cfdata;
},
// It would allow to know the UID of a service contact already imported
// on the device. That is needed by the generic importer, for live
// a dummy implementation as we are currently not supporting updates
getContactUid: function(deviceContact) {
return fb.getFriendUid(deviceContact);
},
get name() {
return 'facebook';
},
get automaticLogout() {
return false;
},
downloadContactPicture: function(contact, access_token, callbacks) {
return fb.utils.getFriendPicture(contact.uid, callbacks.success,
access_token, utils.misc.getPreferredPictureDetail());
},
oncontactsloaded: function(lfriends) {
// This is the timestamp for later syncing as it set at the time
// when data was ready
nextUpdateTime = Date.now();
// Now caching the number
fb.utils.setCachedNumFriends(lfriends.length);
},
oncontactsimported: function(existingContacts, friendsImported, cb) {
setInfraForSync(existingContacts, friendsImported, cb);
fb.contacts.flush();
},
startSync: function(existingContacts, myFriendsByUid, syncFinished) {
var callbacks = {
success: syncFinished
};
fb.sync.startWithData(existingContacts, myFriendsByUid, callbacks);
},
scheduleNextSync: function() {
fb.sync.scheduleNextSync();
}
};
var self = new FacebookConnector();
return self;
})();
}

View file

@ -0,0 +1,659 @@
'use strict';
/* global utils */
var fb = window.fb || {};
// Encapsulates all the logic to obtain the data for a FB contact
fb.Contact = function(deviceContact, cid) {
var contactData;
var devContact = deviceContact;
var contactid = cid;
function doGetFacebookUid(data) {
return fb.getFriendUid(data);
}
function getFacebookUid() {
return doGetFacebookUid(deviceContact);
}
function setFacebookUid(value) {
doSetFacebookUid(deviceContact, value);
}
function doSetFacebookUid(dcontact, value) {
if (!dcontact.category) {
dcontact.category = [];
}
if (dcontact.category.indexOf(fb.CATEGORY) === -1) {
markAsFb(dcontact);
}
var idx = dcontact.category.indexOf(fb.CATEGORY);
dcontact.category[idx + 2] = value;
}
function markAsFb(dcontact) {
if (!dcontact.category) {
dcontact.category = [];
}
if (dcontact.category.indexOf(fb.CATEGORY) === -1) {
dcontact.category.push(fb.CATEGORY);
dcontact.category.push(fb.NOT_LINKED);
}
return dcontact;
}
// Mark a mozContact (deviceContact) as linked to a FB contact (uid)
function markAsLinked(dcontact, uid) {
if (!dcontact.category) {
dcontact.category = [];
}
if (dcontact.category.indexOf(fb.LINKED) === -1) {
dcontact.category.push(fb.CATEGORY);
dcontact.category.push(fb.LINKED);
dcontact.category.push(uid);
}
return dcontact;
}
function promoteToLinked(dcontact) {
var idx = dcontact.category.indexOf(fb.NOT_LINKED);
if (idx != -1) {
dcontact.category[idx] = fb.LINKED;
}
}
// Sets the data for an imported FB Contact
this.setData = function(data) {
contactData = data;
};
Object.defineProperty(this, 'uid', {
get: getFacebookUid,
set: setFacebookUid,
enumerable: true,
});
Object.defineProperty(this, 'mozContact', {
get: getDevContact
});
function getDevContact() {
return devContact;
}
// For saving an imported FB contact
this.save = function() {
var outReq = new fb.utils.Request();
if (contactData && navigator.mozContacts) {
window.setTimeout(function save_do() {
var photoList = contactData.fbInfo.photo;
if (photoList && photoList.length > 0 && photoList[0]) {
utils.squareImage(photoList[0], function squared(squared_image) {
photoList[0] = squared_image;
doSave(outReq);
});
}
else {
doSave(outReq);
}
},0);
}
else {
throw 'Data or mozContacts not available';
}
return outReq;
};
function doSave(outReq) {
// Info to be saved on mozContacts
var contactInfo = {};
// Copying names to the mozContact
copyNames(contactData, contactInfo);
// URL (photo, etc) is stored also with the Device contact
if (contactData.fbInfo.url) {
contactInfo.url = contactData.fbInfo.url;
}
doSetFacebookUid(contactInfo, contactData.uid);
var fbReq = persistToFbCache(contactData);
fbReq.onsuccess = function() {
var mozContactsReq = navigator.mozContacts.save(
utils.misc.toMozContact(contactInfo));
mozContactsReq.onsuccess = function(e) {
outReq.done(fbReq.result);
};
mozContactsReq.onerror = function(e) {
window.console.error('FB: Error while saving on mozContacts',
e.target.error);
outReq.failed(e.target.error);
}; // fbReq.onsuccess
};
fbReq.onerror = function() {
window.console.error('FB: Error while saving on datastore',
fbReq.error && fbReq.error.name);
outReq.failed(fbReq.error);
};
}
// Persists FB Friend Data to the FB cache
function persistToFbCache(contactData, mUpdate) {
var isUpdate = (mUpdate === true ? mUpdate : false);
var outReq = new fb.utils.Request();
window.setTimeout(function persist_fb_do() {
// now saving the FB-originated data to the "private cache area"
var data = Object.create(contactData.fbInfo);
data.tel = contactData.tel || [];
data.email = contactData.email || [];
data.uid = contactData.uid;
Object.keys(contactData.fbInfo).forEach(function(prop) {
data[prop] = contactData.fbInfo[prop];
});
// Names are also stored on datastore
// thus restoring the contact (if unlinked) will be trivial
copyNames(contactData, data);
var updaterFn = (isUpdate === true ?
fb.contacts.update : fb.contacts.save);
var fbReq = updaterFn(data);
fbReq.onsuccess = function() {
outReq.done(fbReq.result);
};
fbReq.onerror = function() {
window.console.error('FB: Error while saving Fb data on the Datastore',
fbReq.error && fbReq.error.name);
outReq.failed(fbReq.error);
};
},0);
return outReq;
}
// Updates a FB Contact
this.update = function(contactData) {
// Auxiliary function to do all the work for saving to the FB cache
function auxDoUpdate(contactData, outReq) {
// If the photo was not updated it is needed to save it
if (!contactData.fbInfo.photo) {
var dataReq = fb.contacts.get(contactData.uid);
dataReq.onsuccess = function() {
contactData.fbInfo.photo = dataReq.result.photo;
auxCachePersist(contactData, outReq, true);
};
dataReq.onerror = function() {
window.console.error('Error while retrieving existing photo for ',
contactData.uid, dataReq.error);
outReq.failed(dataReq.error);
};
}
else {
utils.squareImage(contactData.fbInfo.photo[0],
function sq_img(squaredImg) {
contactData.fbInfo.photo[0] = squaredImg;
auxCachePersist(contactData, outReq, true);
}
);
}
}
// Persist the data to the FB Cache
function auxCachePersist(contactData, outReq, isUpdate) {
var fbReq = persistToFbCache(contactData, isUpdate);
fbReq.onsuccess = function() {
outReq.done(fbReq.result);
};
fbReq.onerror = function() {
window.console.error('FB: Error while saving to FB cache: ',
contactData.uid, fbReq.error);
outReq.failed(fbReq.error);
};
}
// Code starts here
var outReq = new fb.utils.Request();
window.setTimeout(function update_do() {
if (!fb.isFbLinked(devContact)) {
copyNames(contactData, devContact);
} else {
// We are going to update names if they are propagated
revisitPropagatedNames(contactData, devContact);
}
// Check whether the photo has changed
if (contactData.fbInfo.photo) {
devContact.url = contactData.fbInfo.url;
}
var auxReq = new fb.utils.Request();
auxReq.onsuccess = function() {
var mozContactsReq = navigator.mozContacts.save(
utils.misc.toMozContact(devContact));
mozContactsReq.onsuccess = function(e) {
outReq.done();
};
mozContactsReq.onerror = function(e) {
window.console.error('FB: Error while saving mozContact: ',
devContact.id, e.target.error);
outReq.failed(e.target.error);
};
}; // auxReq.onsuccess
auxReq.onerror = function(e) {
outReq.failed(e.target.error);
}; // auxReq.onerror
// And now doing the update
auxDoUpdate(contactData, auxReq);
},0);
return outReq;
};
function asArrayOfValues(value) {
return Array.isArray(value) ? value : [value];
}
function copyNames(source, destination) {
destination.name = asArrayOfValues(source.name);
destination.givenName = asArrayOfValues(source.givenName);
destination.familyName = asArrayOfValues(source.familyName);
destination.additionalName = asArrayOfValues(source.additionalName);
}
// Merges mozContact data with Facebook data
this.merge = function(fbdata) {
return fb.mergeContact(devContact, fbdata);
};
// Gets the data
this.getData = function() {
return fb.getData(devContact);
};
this.getDataAndValues = function() {
var outReq = new fb.utils.Request();
window.setTimeout(function do_getData() {
var uid = doGetFacebookUid(devContact);
if (uid) {
var fbreq = fb.contacts.get(uid);
fbreq.onsuccess = (function() {
var fbdata = fbreq.result;
var out1 = this.merge(fbdata);
var out2 = {};
var duplicates = {};
Object.keys(fbdata).forEach(function(key) {
var dataElement = fbdata[key];
if (dataElement && Array.isArray(dataElement) && key !== 'photo') {
dataElement.forEach(function(item) {
if (item.value && item.value.length > 0) {
// Check for duplicates. Those duplicates are annotated to
// be later removed from the out2 array
var dupList = fb.checkDuplicates(key, item, devContact[key],
fbdata.shortTelephone);
dupList.forEach(function(aDup) {
duplicates[aDup] = true;
});
out2[item.value] = true;
}
else if (typeof item === 'string' && item.length > 0) {
out2[item] = true;
}
else if (key === 'adr') {
if (item.locality) {
out2[item.locality] = true;
}
if (item.countryName) {
out2[item.countryName] = true;
}
}
});
}
else if (key === 'photo') {
out2.hasPhoto = true;
}
else if (dataElement) {
out2[dataElement] = true;
}
});
Object.keys(duplicates).forEach(function(aDup) {
delete out2[aDup];
});
outReq.done([out1, out2, fbdata]);
}).bind(this);
fbreq.onerror = function() {
outReq.failed(fbreq.error);
};
}
else {
outReq.done([devContact, {}]);
}
}.bind(this), 0);
return outReq;
};
this.promoteToLinked = function() {
promoteToLinked(devContact);
};
this.linkTo = function(fbFriend) {
var out = new fb.utils.Request();
window.setTimeout(function do_linkTo() {
if (!devContact) {
// We need to get the Contact data
var req = fb.utils.getContactData(contactid);
req.onsuccess = function() {
devContact = req.result;
doLink(devContact, fbFriend, out);
}; // req.onsuccess
req.onerror = function() {
throw 'FB: Error while retrieving contact data';
};
} // devContact
else {
doLink(devContact, fbFriend, out);
}
},0);
return out;
};
function propagateField(field, from, to) {
var copied = false;
// The field is copied when it is undefined in the target object
if (!Array.isArray(to[field]) || !to[field][0] || !to[field][0].trim()) {
to[field] = from[field];
copied = true;
}
return copied;
}
function createName(contact) {
contact.name = [];
if (Array.isArray(contact.givenName)) {
contact.name[0] = contact.givenName[0] + ' ';
}
if (Array.isArray(contact.familyName)) {
contact.name[0] += contact.familyName[0];
}
}
function propagateNames(from, to) {
var isGivenNamePropagated = propagateField('givenName', from, to);
var isFamilyNamePropagated = propagateField('familyName', from, to);
if (isGivenNamePropagated || isFamilyNamePropagated) {
// We are going to mark the propagation in the category field
if (isGivenNamePropagated) {
fb.setPropagatedFlag('givenName', to);
}
if (isFamilyNamePropagated) {
fb.setPropagatedFlag('familyName', to);
}
createName(to);
}
}
// This method copies names to a contact when they are propagated from fb
function revisitPropagatedNames(from, to) {
if (fb.isPropagated('givenName', to)) {
to.givenName = from.givenName;
}
if (fb.isPropagated('familyName', to)) {
to.familyName = from.familyName;
}
createName(to);
}
function doLink(contactdata, fbFriend, out) {
if (contactdata) {
if (fbFriend.uid) {
// When marking as linked is needed to store a reference to the profile
// picture URL
markAsLinked(contactdata, fbFriend.uid);
if (fbFriend.photoUrl) {
fb.setFriendPictureUrl(contactdata, fbFriend.photoUrl);
}
else if (fbFriend.mozContact) {
contactdata.url = fbFriend.mozContact.url;
}
}
else if (fbFriend.mozContact) {
markAsLinked(contactdata, doGetFacebookUid(fbFriend.mozContact));
contactdata.url = fbFriend.mozContact.url;
}
propagateNames(fbFriend.mozContact, contactdata);
var mozContactsReq = navigator.mozContacts.save(
utils.misc.toMozContact(contactdata));
mozContactsReq.onsuccess = function(e) {
// The FB contact on mozContacts needs to be removed
if (fbFriend.mozContact && !fb.isFbLinked(fbFriend.mozContact)) {
var deleteReq = navigator.mozContacts.remove(
utils.misc.toMozContact(fbFriend.mozContact));
deleteReq.onsuccess = function(e) {
out.done(e.target.result);
};
deleteReq.onerror = function(e) {
window.console.error('FB: Error while linking');
out.failed(e.target.error);
};
}
else {
out.done(e.target.result);
}
}; // mozContactsReq.onsuccess
mozContactsReq.onerror = function(e) {
out.failed(e.target.error);
}; // mozContactsReq.onerror
} // if(dev.contact)
else {
throw 'FB: Contact data not defined';
}
}
// if type === 'hard' the FB Friend is removed from the cache
this.unlink = function(type) {
var out = new fb.utils.Request();
window.setTimeout(function do_unlink() {
if (!devContact) {
// We need to get the Contact data
var req = fb.utils.getContactData(contactid);
req.onsuccess = function() {
devContact = req.result;
doUnlink(devContact, out, type);
}; // req.onsuccess
req.onerror = function() {
throw 'FB: Error while retrieving contact data';
};
} // devContact
else {
doUnlink(devContact, out, type);
}
}, 0);
return out;
};
// Reset givenName and familyName if it is needed after unlinking
function resetNames(dContact) {
fb.resetNames(dContact);
}
function doUnlink(dContact, out, type) {
var theType = type || 'soft';
var uid = doGetFacebookUid(dContact);
resetNames(dContact);
fb.markAsUnlinked(dContact);
var req = navigator.mozContacts.save(utils.misc.toMozContact(dContact));
req.onsuccess = function(e) {
if (theType !== 'hard') {
// Then the original FB imported contact is restored
var fbNumReq = fb.utils.getNumberMozContacts(uid);
fbNumReq.onsuccess = function() {
// Because we have already unlinked one
if (fbNumReq.result >= 1) {
out.done();
} else {
var fbDataReq = fb.contacts.get(uid);
fbDataReq.onsuccess = function() {
var imported = fbDataReq.result;
var data = {};
copyNames(imported, data);
data.url = imported.url;
doSetFacebookUid(data, uid);
// The FB contact is restored
var aMozContact = utils.misc.toMozContact(data);
var reqRestore = navigator.mozContacts.save(aMozContact);
reqRestore.onsuccess = function(e) {
out.done(aMozContact.id);
};
reqRestore.onerror = function(e) {
out.failed(e.target.error);
};
};
fbDataReq.onerror = function() {
window.console.error('FB: Error while unlinking contact data');
out.failed(fbDataReq.error);
};
}
};
fbNumReq.onerror = function() {
window.console.error('FB: Error while unlinking contact data');
out.failed(fbNumReq.error);
};
}
else {
// FB Data is removed from the cache
var removeReq = fb.contacts.remove(uid, true);
removeReq.onsuccess = function() {
out.done(removeReq.result);
};
removeReq.onerror = function() {
out.failed(removeReq.error);
};
}
};
req.onerror = function(e) {
out.failed(e.target.error);
};
}
this.remove = function(pforceFlush) {
var forceFlush = (pforceFlush === true) ? pforceFlush : false;
var out = new fb.utils.Request();
window.setTimeout(function do_remove() {
var uid = doGetFacebookUid(devContact);
var fbNumReq = fb.utils.getNumberMozContacts(uid);
fbNumReq.onsuccess = function num_onsuccess() {
var removeReq;
// If there is only one Device Contact associated
// Then corresponding FB Data is removed otherwise only
// the device contact is removed
if (fbNumReq.result === 1) {
removeReq = navigator.mozContacts.remove(
utils.misc.toMozContact(devContact));
removeReq.onsuccess = function(e) {
var fbReq = fb.contacts.remove(uid, forceFlush);
fbReq.onsuccess = function() {
out.done(fbReq.result);
};
fbReq.onerror = function() {
out.failed(fbReq.error);
};
};
removeReq.onerror = function(e) {
out.failed(e.target.error);
};
}
else {
removeReq = navigator.mozContacts.remove(
utils.misc.toMozContact(devContact));
removeReq.onsuccess = function(e) {
out.done();
};
removeReq.onerror = function(e) {
out.failed(e.target.error);
};
}
};
}, 0);
return out;
};
}; // fb.Contact

View file

@ -0,0 +1,260 @@
'use strict';
/* global fb, utils */
/* jshint ignore:start */
var fb = this.fb || {};
/* jshint ignore:end */
fb.PROPAGATED_PREFIX = 'fb_propagated_';
// Types of URLs for FB Information
fb.PROFILE_PHOTO_URI = 'fb_profile_photo';
fb.FRIEND_URI = 'fb_friend';
fb.DEFAULT_PHONE_TYPE = 'other';
fb.DEFAULT_EMAIL_TYPE = 'other';
// This year indicates that the year can be ignored (yearless date)
fb.FLAG_YEAR_IGNORED = 9996;
fb.CONTACTS_APP_ORIGIN = location.origin;
fb.isPropagated = function fcu_isPropagated(field, devContact) {
return (devContact.category && devContact.category.
indexOf(fb.PROPAGATED_PREFIX + field) !== -1);
};
fb.removePropagatedFlag = function fcu_removePropagatedFlag(field, devContact) {
var idx = devContact.category.indexOf(fb.PROPAGATED_PREFIX + field);
if (idx !== -1) {
devContact.category.splice(idx, 1);
}
};
fb.setPropagatedFlag = function fcu_setPropagatedFlag(field, devContact) {
var idx = devContact.category.indexOf(fb.PROPAGATED_PREFIX + field);
if (idx === -1) {
devContact.category.push(fb.PROPAGATED_PREFIX + field);
}
};
fb.getFriendPictureUrl = function(devContact) {
var out;
var urls = devContact.url;
if (urls) {
for (var c = 0; c < urls.length; c++) {
var aurl = urls[c];
if (aurl.type.indexOf(fb.PROFILE_PHOTO_URI) !== -1) {
out = aurl.value;
break;
}
}
}
return out;
};
fb.setFriendPictureUrl = function(devContact, url) {
var urls = devContact.url || [];
urls.push({
type: [fb.PROFILE_PHOTO_URI],
value: url
});
devContact.url = urls;
};
// Adapts data to the mozContact format names
fb.friend2mozContact = function(f) {
function normalizeFbPhoneNumber(phone) {
var out = phone.number;
if (phone.country_code && out.indexOf('+') !== 0) {
out = '+' + phone.country_code + out;
}
return out;
}
// Check whether this has been already normalized to mozContact
if (Array.isArray(f.familyName)) {
return f;
}
// givenName is put as name but it should be f.first_name
f.familyName = [f.last_name ? f.last_name.trim() : (f.last_name || '')];
var middleName = f.middle_name ? f.middle_name.trim() : (f.middle_name || '');
f.additionalName = middleName;
var firstName = f.first_name ? f.first_name.trim() : (f.first_name || '');
f.givenName = [(firstName + ' ' + middleName).trim()];
delete f.last_name;
delete f.middle_name;
delete f.first_name;
if (f.email) {
f.email1 = f.email;
f.email = [{
type: [fb.DEFAULT_EMAIL_TYPE],
value: f.email
}];
}
else {
f.email1 = '';
}
if (Array.isArray(f.phones) && f.phones.length > 0) {
f.tel = [];
f.shortTelephone = [];
f.phones.forEach(function(aphone) {
f.tel.push({
type: [fb.DEFAULT_PHONE_TYPE],
value: normalizeFbPhoneNumber(aphone)
});
// Enabling to find FB phones by short number
f.shortTelephone.push(aphone.number);
});
}
delete f.phones;
f.uid = f.uid.toString();
return f;
};
/**
* Auxiliary function to know where a contact works
*
*/
fb.getWorksAt = function(fbdata) {
var out = '';
if (fbdata.work && fbdata.work.length > 0) {
// It is assumed that first is the latest
out = fbdata.work[0].employer.name;
}
return out;
};
/**
* Facebook dates are MM/DD/YYYY
*
* Returns the birth date
*
*/
fb.getBirthDate = function getBirthDate(sbday) {
var out = new Date(0);
var imonth = sbday.indexOf('/');
var smonth = sbday.substring(0, imonth);
var iyear = sbday.lastIndexOf('/');
if (iyear === imonth) {
iyear = sbday.length;
}
var sday = sbday.substring(imonth + 1, iyear);
var syear = sbday.substring(iyear + 1, sbday.length);
out.setUTCDate(parseInt(sday, 10));
out.setUTCMonth(parseInt(smonth, 10) - 1, parseInt(sday, 10));
if (syear && syear.length > 0) {
out.setUTCFullYear(parseInt(syear, 10));
}
else {
// 9996 is the year that flags a not known year
out.setUTCFullYear(fb.FLAG_YEAR_IGNORED);
}
out.setUTCHours(0);
out.setUTCMinutes(0);
out.setUTCSeconds(0);
out.setUTCMilliseconds(0);
return out;
};
fb.getAddresses = function(fbdata) {
function fillAddress(fbAddress, type) {
var outAddr = {};
outAddr.type = [type];
outAddr.locality = fbAddress.city || '';
outAddr.region = fbAddress.state || '';
outAddr.countryName = fbAddress.country || '';
return outAddr;
}
var out = [];
var addrTypes = {
'home': 'hometown_location',
'current': 'current_location'
};
Object.keys(addrTypes).forEach(function onAddressType(type) {
var addrObj = fbdata[addrTypes[type]];
if (addrObj) {
out.push(fillAddress(addrObj, type));
}
});
return out;
};
// The contact is now totally unlinked
// [...,facebook, fb_not_linked, 123456,....]
fb.markAsUnlinked = function(devContact) {
var category = devContact.category;
var updatedCategory = [];
if (category) {
var idx = category.indexOf(fb.CATEGORY);
if (idx !== -1) {
for (var c = 0; c < idx; c++) {
updatedCategory.push(category[c]);
}
// The facebook category, the linked mark and the UID are skipped
for (c = idx + 3; c < category.length; c++) {
updatedCategory.push(category[c]);
}
}
}
devContact.category = updatedCategory;
return devContact;
};
// Unlinks a FB Contact. This must only be called from the clean all process
fb.unlinkClearAll = function(devContact) {
fb.resetNames(devContact);
fb.markAsUnlinked(devContact);
};
// Reset givenName and familyName if it is needed after unlinking
fb.resetNames = function resetNames(dContact) {
if (fb.isPropagated('givenName', dContact)) {
dContact.givenName = [''];
fb.removePropagatedFlag('givenName', dContact);
}
if (fb.isPropagated('familyName', dContact)) {
dContact.familyName = [''];
fb.removePropagatedFlag('familyName', dContact);
}
dContact.name = [dContact.givenName[0] + ' ' + dContact.familyName[0]];
};
// Marks that FB Cleaning is in progress or not
fb.markFbCleaningInProgress = function(value) {
utils.cookie.update({
fbCleaningInProgress: value
});
};

View file

@ -0,0 +1,380 @@
'use strict';
/* global LazyLoader, TelIndexer, SimplePhoneMatcher */
// WARNING: This file lazy loads:
// 'shared/js/fb/fb_data_reader.js
// 'shared/js/fb/fb_tel_index.js'
// 'shared/js/binary_search.js'
// 'shared/js/simple_phone_matcher.js' (will be removed once bug 938265 lands)
var fb = window.fb || {};
(function() {
var contacts = fb.contacts || {};
fb.contacts = contacts;
var Reader;
var readerLoaded = false;
// Record Id for the index
var INDEX_ID = 1;
var isIndexDirty = false;
var READER_LOADED_EV = 'reader_loaded';
var TEL_INDEXER_JS = '/shared/js/fb/fb_tel_index.js';
var PHONE_MATCHER_JS = '/shared/js/simple_phone_matcher.js';
var FB_READER_JS = '/shared/js/fb/fb_data_reader.js';
var BINARY_SEARCH_JS = '/shared/js/binary_search.js';
contacts.UID_NOT_FOUND = 'UIDNotFound';
contacts.ALREADY_EXISTS = 'AlreadyExists';
// This is needed for having proxy methods setted and ready before
// the real reader methods (in fb_data_reader) are loaded
if (!contacts.init) {
var proxyMethods = ['get', 'getLength', 'getByPhone', 'search',
'refresh', 'init', 'restart'];
proxyMethods.forEach(function(aMethod) {
contacts[aMethod] = defaultFunction.bind(null, aMethod);
});
LazyLoader.load(FB_READER_JS, onreaderLoaded);
}
else {
onreaderLoaded();
}
function onreaderLoaded() {
readerLoaded = true;
Reader = fb.contacts;
document.dispatchEvent(new CustomEvent(READER_LOADED_EV));
}
function setIndex(index) {
Reader.dsIndex = index;
isIndexDirty = false;
}
function datastore() {
return Reader.datastore;
}
function index() {
return Reader.dsIndex;
}
function defaultFunction(target) {
var args = [];
for (var j = 1; j < arguments.length; j++) {
args.push(arguments[j]);
}
if (!readerLoaded) {
document.addEventListener(READER_LOADED_EV, function rd_loaded() {
document.removeEventListener(READER_LOADED_EV, rd_loaded);
Reader[target].apply(this, args);
});
}
else {
// As the reader load will overwrite those functions probably this
// will never be called
/*jshint validthis:true */
Reader[target].apply(this, args);
}
}
function initError(outRequest, error) {
outRequest.failed(error);
}
// Ensures a proper error object is returned
function safeError(err) {
if (err && err.name) {
return err;
}
return {
name: 'UnknownError'
};
}
// Creates a default handler for errors
function defaultError(request) {
return defaultErrorCb.bind(null, request);
}
// Creates a default handler for success
function defaultSuccess(request) {
return defaultSuccessCb.bind(null, request);
}
function defaultErrorCb(request, error) {
request.failed(safeError(error));
}
function defaultSuccessCb(request, result) {
request.done(result);
}
function doSave(obj, outRequest) {
LazyLoader.load([TEL_INDEXER_JS, PHONE_MATCHER_JS,
BINARY_SEARCH_JS] , function() {
var uid = obj.uid;
datastore().add(obj, uid).then(function success() {
indexByPhone(obj, uid);
isIndexDirty = true;
outRequest.done();
}, function error(err) {
if (err.name === 'ConstraintError') {
err = {
name: contacts.ALREADY_EXISTS
};
}
outRequest.failed(err);
});
});
}
function indexByPhone(obj, newId) {
// Update index by tel
// As this is populated by FB importer we don't need to have
// extra checks
if (Array.isArray(obj.tel)) {
obj.tel.forEach(function(aTel) {
var variants = SimplePhoneMatcher.generateVariants(aTel.value);
variants.forEach(function(aVariant) {
index().byTel[aVariant] = newId;
});
// To avoid the '+' char
TelIndexer.index(index().treeTel, aTel.value.substring(1), newId);
});
}
}
function reIndexByPhone(oldObj, newObj, dsId) {
removePhoneIndex(oldObj);
indexByPhone(newObj, dsId);
TelIndexer.orderTree(index().treeTel);
}
function removePhoneIndex(deletedFriend) {
// Need to update the tel indexes
if (Array.isArray(deletedFriend.tel)) {
deletedFriend.tel.forEach(function(aTel) {
TelIndexer.remove(index().treeTel, aTel.value.substring(1));
var variants = SimplePhoneMatcher.generateVariants(aTel.value);
variants.forEach(function(aVariant) {
delete index().byTel[aVariant];
});
});
}
}
/**
* Allows to save FB Friend Information
*
*/
contacts.save = function(obj) {
var retRequest = new fb.utils.Request();
window.setTimeout(function save() {
contacts.init(function() {
doSave(obj, retRequest);
},
function(err) {
initError(retRequest, err);
});
}, 0);
return retRequest;
};
/**
* Allows to update FB Friend Information
*
*
*/
contacts.update = function(obj) {
var retRequest = new fb.utils.Request();
window.setTimeout(function save() {
contacts.init(function() {
doUpdate(obj, retRequest);
},
function(err) {
initError(retRequest, err);
});
}, 0);
return retRequest;
};
function doUpdate(obj, outRequest) {
LazyLoader.load([TEL_INDEXER_JS, PHONE_MATCHER_JS,
BINARY_SEARCH_JS], function() {
var uid = obj.uid;
var successCb = successUpdate.bind(null, outRequest);
var errorCb = errorUpdate.bind(null, outRequest, uid);
// It is necessary to get the old object and delete old indexes
datastore().get(uid).then(function success(oldObj) {
if (!oldObj) {
errorCb({
name: contacts.UID_NOT_FOUND
});
return;
}
reIndexByPhone(oldObj, obj, uid);
datastore().put(obj, uid).then(function success() {
return datastore().put(index(), INDEX_ID);
}, errorCb).then(successCb, errorCb);
}, errorCb); // datastore.get
});
}
function successUpdate(outRequest) {
outRequest.done();
}
function errorUpdate(outRequest, uid, error) {
window.console.error('Error while updating datastore for: ', uid);
outRequest.failed(error);
}
function doRemove(uid, outRequest, forceFlush) {
LazyLoader.load([TEL_INDEXER_JS, PHONE_MATCHER_JS,
BINARY_SEARCH_JS], function() {
var errorCb = errorRemove.bind(null, outRequest, uid);
var objToDelete;
datastore().get(uid).then(function success_get_remove(object) {
objToDelete = object;
if (!objToDelete) {
errorRemove(outRequest, uid, {
name: contacts.UID_NOT_FOUND
});
return;
}
datastore().remove(uid).then(function success_rm(removed) {
successRemove(outRequest, objToDelete, forceFlush, removed);
}, errorCb);
}, errorCb);
}); // LazyLoader.load
}
// Needs to update the index data conveniently
function successRemove(outRequest, deletedFriend, forceFlush, removed) {
if (removed === true) {
isIndexDirty = true;
removePhoneIndex(deletedFriend);
if (forceFlush) {
var flushReq = fb.contacts.flush();
flushReq.onsuccess = function() {
isIndexDirty = false;
outRequest.done(true);
};
flushReq.onerror = function() {
outRequest.failed(flushReq.error);
};
}
else {
outRequest.done(true);
}
}
else {
outRequest.done(false);
}
}
function errorRemove(outRequest, uid, error) {
error = safeError(error);
window.console.error('FB Data: Error while removing ', uid, ': ',
error.name);
outRequest.failed(error);
}
/**
* Allows to remove FB contact from the DB
*
*/
contacts.remove = function(uid, flush) {
var hasToFlush = (flush === true ? flush : false);
var retRequest = new fb.utils.Request();
window.setTimeout(function remove() {
contacts.init(function() {
doRemove(uid, retRequest, hasToFlush);
},
function(err) {
initError(retRequest, err);
});
}, 0);
return retRequest;
};
/**
* Removes all the FB Friends and the index
*
* The index is restored as empty
*
*/
contacts.clear = function() {
var outRequest = new fb.utils.Request();
window.setTimeout(function clear() {
contacts.init(function() {
doClear(outRequest);
},
function(err) {
initError(outRequest, err);
});
}, 0);
return outRequest;
};
function doClear(outRequest) {
datastore().clear().then(function success() {
setIndex(null);
// TODO:
// This is working but there are open questions on the mailing list
datastore().put(index(), INDEX_ID).then(defaultSuccess(outRequest),
function error(err) {
err = safeError(err);
window.console.error('Error while re-creating the index: ',
err.name);
outRequest.failed(err);
}
);
}, defaultError(outRequest));
}
/**
* Persists the index on the datastore
*
*/
contacts.flush = function() {
var outRequest = new fb.utils.Request();
window.setTimeout(function do_Flush() {
if (!(datastore()) || !isIndexDirty) {
window.console.warn(
'The datastore has not been initialized or is not dirty');
outRequest.done();
return;
}
TelIndexer.orderTree(index().treeTel);
datastore().put(index(), INDEX_ID).then(
defaultSuccess(outRequest),
defaultError(outRequest));
}, 0);
return outRequest;
};
})();

View file

@ -0,0 +1,163 @@
'use strict';
/* global fb */
/* jshint ignore:start */
var fb = this.fb || {};
// In an only worker execution context this would not be necessary
var self = this;
fb.utils = this.fb.utils || {};
/* jshint ignore:end */
fb.DEFAULT_TIMEOUT = 30000;
// Runs a query against Facebook FQL. Callback is a string!!
fb.utils.runQuery = function(query, callback, access_token) {
// Auxiliary function for canceling a request
function QueryRequest() {
this.cancel = function() {
if (typeof this.oncancel === 'function') {
window.setTimeout(function() {
this.oncancel();
}.bind(this), 0);
}
};
}
var outReq = new QueryRequest();
var queryService = 'https://graph.facebook.com/fql?q=';
queryService += encodeURIComponent(query);
var params = ['access_token' + '=' + access_token,
'format=json'];
var queryParams = params.join('&');
var remote = queryService + '&' + queryParams;
var xhr = new XMLHttpRequest({
mozSystem: true
});
// To enable xhr.abort if user cancels
outReq.xhr = xhr;
outReq.oncancel = function() {
this.xhr.abort();
};
xhr.open('GET', remote, true);
xhr.responseType = 'json';
xhr.timeout = fb.operationsTimeout || fb.DEFAULT_TIMEOUT;
xhr.onload = function(e) {
if (xhr.status === 200 || xhr.status === 400 || xhr.status === 0) {
if (callback && typeof callback.success === 'function') {
self.setTimeout(function() {
callback.success(xhr.response);
},0);
}
}
else {
self.console.error('FB: HTTP error executing query. ',
query, ' Status: ', xhr.status);
if (callback && typeof callback.error === 'function') {
self.setTimeout(callback.error, 0);
}
}
}; // onload
xhr.ontimeout = function(e) {
self.console.error('FB: Timeout!!! while executing query', query);
if (callback && typeof callback.timeout === 'function') {
self.setTimeout(callback.timeout, 0);
}
}; // ontimeout
xhr.onerror = function(e) {
self.console.error('FB: Error while executing query: ', query,
': ', e);
if (callback && typeof callback.error === 'function') {
self.setTimeout(function() {
callback.error(e);
},0);
}
}; // onerror
xhr.send();
return outReq;
};
/**
* Obtains a img DOM Element with the Contact's img
*
*/
fb.utils.getFriendPicture = function(uid, callback, access_token,
targetPictureSize) {
// Access token is necessary just in case the image is not public
// When passing an access token to FB https must be used
var imgSrc = 'https://graph.facebook.com/' + uid + '/picture?';
var params = [
'width=' + targetPictureSize,
'access_token=' + access_token,
't=' + Date.now()
];
var imgService = imgSrc + params.join('&');
var xhr = new XMLHttpRequest({
mozSystem: true
});
xhr.open('GET', imgService, true);
xhr.responseType = 'blob';
xhr.timeout = fb.operationsTimeout || fb.DEFAULT_TIMEOUT;
xhr.onload = function(e) {
if (xhr.status === 200 || xhr.status === 0) {
var mblob = e.target.response;
if (typeof callback === 'function') {
self.setTimeout(function() {
callback(mblob);
},0);
}
}
else {
self.console.error('FB: HTTP error retrieving img for uid: ',
uid, ' Status: ', xhr.status);
if (typeof callback === 'function') {
self.setTimeout(function() {
callback(null);
},0);
}
}
}; // onload
xhr.ontimeout = function(e) {
self.console.error('FB: Timeout!!! while retrieving img for uid: ', uid);
if (typeof callback === 'function') {
self.setTimeout(function() {
callback(null);
},0);
}
}; // ontimeout
xhr.onerror = function(e) {
self.console.error('FB: Error while retrieving img for uid: ', uid,
'Error: ', e);
if (typeof callback === 'function') {
self.setTimeout(function() {
callback(null);
},0);
}
}; // onerror
xhr.send();
};

View file

@ -0,0 +1,526 @@
'use strict';
/* global ImportStatusData, oauthflow, utils */
/* Ignoring function declaration in loops */
/* jshint -W083 */
var fb = window.fb || {};
window.fb = fb;
(function(document) {
var Utils = fb.utils || {};
fb.utils = Utils;
var FRIEND_COUNT_QUERY = 'select friend_count from user where uid=me()';
var CACHE_FRIENDS_KEY = Utils.CACHE_FRIENDS_KEY = 'numFacebookFriends';
var LAST_UPDATED_KEY = Utils.LAST_UPDATED_KEY = 'lastUpdatedTime';
Utils.ALARM_ID_KEY = 'nextAlarmId';
Utils.SCHEDULE_SYNC_KEY = 'facebookShouldHaveScheduledAt';
var REDIRECT_LOGOUT_URI = window.oauthflow ?
oauthflow.params.facebook.redirectLogout : '';
var STORAGE_KEY = Utils.TOKEN_DATA_KEY = 'tokenData';
// For controlling data synchronization
Utils.setLastUpdate = function(value, cb) {
window.asyncStorage.setItem(LAST_UPDATED_KEY, {
data: value
}, cb);
};
Utils.getLastUpdate = function(callback) {
window.asyncStorage.getItem(LAST_UPDATED_KEY, function(obj) {
var out = 0;
if (obj) {
out = obj.data || out;
}
if (typeof callback === 'function') {
callback(out);
}
});
};
Utils.getNonCacheableUrl = function(url) {
if (url.indexOf('?') === -1) {
url += '?';
}
return url + '&burst_cache=' + Date.now();
};
Utils.getContactData = function(cid) {
var outReq = new Utils.Request();
var req = navigator.mozContacts.find({
filterBy: ['id'],
filterValue: cid,
filterOp: 'equals'
});
req.onsuccess = function(e) {
if (e.target.result && e.target.result.length > 0) {
outReq.done(e.target.result[0]);
}
else {
outReq.done(null);
}
};
req.onerror = function(e) {
outReq.failed(e.target.error);
};
return outReq;
};
// Returns the mozContact associated to a UID in FB
Utils.getMozContact = function(uid) {
var outReq = new Utils.Request();
window.setTimeout(function get_mozContact_ByUid() {
fb.getMozContactByUid(uid,
function onsuccess(result) {
if (Array.isArray(result) && result.length > 0) {
outReq.done(result[0]);
} else {
outReq.done(null);
}
},
function onerror(e) {
outReq.failed(e.target.error);
}
);
}, 0);
return outReq;
};
// Returns the number of mozContacts associated to a UID in FB
Utils.getNumberMozContacts = function(uid) {
var outReq = new Utils.Request();
window.setTimeout(function get_mozContact_ByUid() {
fb.getMozContactByUid(uid,
function onsuccess(result) {
if (Array.isArray(result)) {
outReq.done(result.length);
} else {
outReq.done(0);
}
},
function onerror(error) {
outReq.failed(error);
}
);
},0);
return outReq;
};
Utils.getAllFbContacts = function() {
var outReq = new Utils.Request();
window.setTimeout(function get_all_fb_contacts() {
var filter = {
filterValue: fb.CATEGORY,
filterOp: 'contains',
filterBy: ['category']
};
var req = navigator.mozContacts.find(filter);
req.onsuccess = function(e) {
outReq.done(e.target.result);
};
req.onerror = function(e) {
outReq.failed(e.target.error);
};
}, 0);
return outReq;
};
// On the device
Utils.getNumFbContacts = function() {
var outReq = new Utils.Request();
window.setTimeout(function get_num_fb_contacts() {
var req = fb.contacts.getLength();
req.onsuccess = function() {
outReq.done(req.result);
};
req.onerror = function() {
outReq.failed(req.error);
};
}, 0);
return outReq;
};
// Requests the number remotely
Utils.getNumFbFriends = function(callback, access_token) {
fb.utils.runQuery(FRIEND_COUNT_QUERY, callback, access_token);
};
Utils.getCachedAccessToken = function(callback) {
ImportStatusData.get(STORAGE_KEY).then(function(data) {
var out = null;
if (data) {
out = data.access_token || null;
}
if (typeof callback === 'function') {
callback(out);
}
});
};
Utils.setCachedAccessToken = function(data, callback) {
ImportStatusData.put(STORAGE_KEY, data).then(function done() {
if (typeof callback === 'function') {
callback();
}
}, function error() {
console.error('Error while saving access token');
if (typeof callback === 'function') {
callback();
}
});
};
// Obtains the number locally
Utils.getCachedNumFbFriends = function(callback) {
ImportStatusData.get(CACHE_FRIENDS_KEY).then(function(data) {
if (typeof callback === 'function' && typeof data === 'number') {
callback(data);
}
});
};
Utils.setCachedNumFriends = function(value, cb) {
ImportStatusData.put(CACHE_FRIENDS_KEY, value).then(cb);
};
// Removes cached number of friends from datastore to keep state of sync.
Utils.removeCachedNumFriends = function(callback) {
ImportStatusData.remove(CACHE_FRIENDS_KEY).then(function() {
typeof callback === 'function' && callback();
});
};
Utils.getImportChecked = function(callback) {
// If we have an access token Import should be checked
Utils.getCachedAccessToken(function(access_token) {
var out = 'logged-out';
if (access_token) {
out = 'logged-in';
}
else {
// In this case is needed to know whether the access_token has
// been invalidated
Utils.getCachedNumFbFriends(function(value) {
if (value) {
out = 'renew-pwd';
if (typeof callback === 'function') {
callback(out);
}
}
});
}
if (typeof callback === 'function') {
callback(out);
}
});
};
// Obtains the number locally (cached) and tries to get them remotely
Utils.numFbFriendsData = function(callback) {
var localCb = callback.local;
var remoteCb = callback.remote;
Utils.getCachedNumFbFriends(localCb);
function auxCallback(response) {
if (response.data && response.data[0] &&
response.data[0].friend_count) {
remoteCb(response.data[0].friend_count);
}
}
if (typeof remoteCb === 'function' && navigator.onLine === true) {
var remoteCallbacks = {
success: auxCallback,
error: null,
timeout: null
};
Utils.getCachedAccessToken(function(access_token) {
if (access_token) {
Utils.getNumFbFriends(remoteCallbacks, access_token);
}
});
}
};
// Clears all Fb data (use with caution!!)
Utils.clearFbData = function() {
// First step all Contacts which are FB Friends are obtained
// then those not linked are directly removed
// Those linked are unlinked
var outReq = new Utils.Request();
window.setTimeout(function do_clearFbData() {
// First a clear request is issued
var ireq = fb.contacts.clear();
ireq.onsuccess = function() {
var req = Utils.getAllFbContacts();
req.onsuccess = function() {
var cleaner = new Utils.FbContactsCleaner(req.result, 'clear');
// And now success notification is sent
outReq.done(cleaner);
// The cleaning activity should be starting immediately
window.setTimeout(cleaner.start, 0);
};
req.onerror = function() {
window.console.error('FB Clean. Error retrieving FB Contacts');
outReq.failed(req.error);
};
};
ireq.onerror = function(e) {
window.console.error('Error while clearing the FB Cache');
outReq.failed(ireq.error);
};
},0);
return outReq;
};
Utils.logout = function() {
var outReq = new Utils.Request();
// Duration of the timer that checks window close by the user (1/2 sec)
var WINDOW_TIMER_INTERVAL = 500;
// Maximum time we are going to wait for a logout operation (5 secs)
var LOGOUT_TIMEOUT = 5000;
var MAX_TIMER_TICKS = LOGOUT_TIMEOUT / WINDOW_TIMER_INTERVAL;
window.setTimeout(function do_logout() {
Utils.getCachedAccessToken(function getAccessToken(access_token) {
if (access_token) {
var logoutService = 'https://www.facebook.com/logout.php?';
var params = [
'next' + '=' + encodeURIComponent(REDIRECT_LOGOUT_URI),
'access_token' + '=' + access_token
];
var logoutParams = params.join('&');
var logoutUrl = logoutService + logoutParams;
var loggedOut = false;
var m_listen = function(e) {
if (e.origin !== fb.CONTACTS_APP_ORIGIN) {
return;
}
if (e.data === 'closed') {
loggedOut = true;
ImportStatusData.remove(STORAGE_KEY);
outReq.done();
}
e.stopImmediatePropagation();
window.removeEventListener('message', m_listen);
};
window.addEventListener('message', m_listen);
var logoutWindow = window.open(logoutUrl, '', 'dialog');
// This timer is run in order to monitor whether the window is
// closed by the user during the logout process (bug 1084224).
// This will have to be removed once bug 966216 is fixed
var timerTicks = 0;
var timerId = window.setInterval(function() {
timerTicks++;
var closed = (logoutWindow.closed === true);
var timedOut = (timerTicks >= MAX_TIMER_TICKS);
if (loggedOut || closed || timedOut) {
window.clearInterval(timerId);
window.removeEventListener('message', m_listen);
}
if (closed && !loggedOut) {
outReq.failed('UserCancelled');
}
else if (timedOut && !loggedOut) {
outReq.failed('Timeout');
}
}, WINDOW_TIMER_INTERVAL);
} // if
else {
outReq.done();
}
}); // cachedToken
}, 0); // setTimeout
return outReq;
}; // logout
// FbContactsCleaner Object
// Mode can be 'update' or 'cleanAll'
Utils.FbContactsCleaner = function(contacts, pmode) {
this.lcontacts = contacts;
var total = contacts.length;
var next = 0;
var self = this;
var CHUNK_SIZE = 5;
var numResponses = 0;
var mustUpdate = (pmode === 'update');
var notifyClean = false;
var mustHold = false;
var holded = false;
var mustFinish = false;
this.start = function() {
mustHold = holded = mustFinish = false;
if (total > 0) {
cleanContacts(0);
}
else if (typeof self.onsuccess === 'function') {
window.setTimeout(self.onsuccess);
}
};
this.hold = function() {
mustHold = true;
};
this.finish = function() {
mustFinish = true;
if (holded) {
notifySuccess();
}
};
this.resume = function() {
mustHold = holded = mustFinish = false;
window.setTimeout(function resume_clean() {
cleanContacts(next);
});
};
function successHandler(e) {
if (notifyClean || typeof self.oncleaned === 'function') {
notifyClean = true;
// Avoiding race condition so the cleaned element is cached
var cleaned = e.target.number;
window.setTimeout(function() {
self.oncleaned(cleaned);
},0);
}
continueCb();
}
function errorHandler(contactid, error) {
if (typeof self.onerror === 'function') {
self.onerror(contactid, error);
}
continueCb();
}
function cleanContacts(from) {
for (var idx = from; idx < (from + CHUNK_SIZE) && idx < total; idx++) {
var contact = contacts[idx];
var number = idx;
var req, fbContact;
if (fb.isFbLinked(contact)) {
if (mustUpdate) {
fbContact = new fb.Contact(contact);
req = fbContact.unlink('hard');
}
else {
// Here we only need to mark this contact as unlinked
// as the FB Data would have been already removed
fb.unlinkClearAll(contact);
req = navigator.mozContacts.save(
utils.misc.toMozContact(contact));
}
}
else {
if (mustUpdate) {
fbContact = new fb.Contact(contact);
req = fbContact.remove();
}
else {
req = navigator.mozContacts.remove(
utils.misc.toMozContact(contact));
}
}
req.number = number;
req.onsuccess = successHandler;
req.onerror = function(e) {
errorHandler(contact.id, e.target.error);
};
}
}
function notifySuccess() {
if (typeof self.onsuccess === 'function') {
window.setTimeout(self.onsuccess);
}
}
function finishHandler() {
// It is needed to flush in order to properly update the index
var req = fb.contacts.flush();
req.onsuccess = notifySuccess;
req.onerror = function cleaner_flushError() {
errorHandler(null, req.error);
};
}
function continueCb() {
next++;
numResponses++;
if (next < total && numResponses === CHUNK_SIZE) {
numResponses = 0;
if (!mustHold && !mustFinish) {
cleanContacts(next);
}
else if (mustFinish && !holded) {
finishHandler();
}
if (mustHold) {
holded = true;
}
}
else if (next >= total) {
// End has been reached
finishHandler();
}
} // function
}; // FbContactsCleaner
})(document);

View file

@ -0,0 +1,21 @@
;'use strict';var FriendListRenderer=(function(){var orderCriteria={firstName:['givenName','familyName','email1'],lastName:['familyName','givenName','email1']};var HEADER_LETTERS=['ABCDEFGHIJKLMNOPQRSTUVWXYZ','ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ','АБВГДЂЕЁЖЗИЙЈКЛЉМНЊОПРСТЋУФХЦЧЏШЩЭЮЯ'].join('');var defaults={container:'#groups-list',orderBy:'lastName'};var finishCb;var totalContacts;var lock;var CHUNK_SIZE=10;var render=function render(contacts,cb,options){for(var key in defaults){if(typeof options[key]==='undefined'){options[key]=defaults[key];}}
finishCb=cb;totalContacts=contacts.length;lock=navigator.requestWakeLock('cpu');doRender(contacts,cb,options);};var doRender=function doRender(contacts,cb,options){var groupsList=document.querySelector(options.container);var orderBy=options.orderBy;var order=orderCriteria[orderBy];contacts.sort(function(a,b){return getStringToBeOrdered(a,order).localeCompare(getStringToBeOrdered(b,order));});var groups={};contacts.forEach(function(contact){var groupName=getGroupName(contact,order);if(!groups[groupName]){groups[groupName]=[];}
groups[groupName].push(contact);});var notRenderedParagraph=groupsList.querySelector('[data-order-by="'+
(orderBy==='firstName'?'lastName':'firstName')+'"]');if(notRenderedParagraph){notRenderedParagraph.parentNode.removeChild(notRenderedParagraph);}
var letterStart=0;doRenderGroupChunk(letterStart,HEADER_LETTERS[letterStart],groupsList,groups);};function doRenderGroupChunk(index,group,groupsList,groups){renderGroup(groupsList,group,groups[group],function(fragment){if(fragment){groupsList.appendChild(fragment);}
fragment=null;var headerLettersLength=HEADER_LETTERS.length;if(index+1<=headerLettersLength){window.setTimeout(function renderNextGroup(){doRenderGroupChunk(index+1,HEADER_LETTERS[index+1],groupsList,groups);});}
else if(group!='#'){window.setTimeout(function renderNextGroup(){doRenderGroupChunk(index+1,'#',groupsList,groups);});}
else{groupsList.removeChild(groupsList.firstElementChild);if(typeof finishCb==='function'){window.setTimeout(finishCb,totalContacts*2);}
if(lock){lock.unlock();}}});}
function doRenderGroupItems(from,groupsList,group,friends,element,cb){var end=from+CHUNK_SIZE;var list=element.children[1];for(var i=from;i<end&&i<friends.length;i++){var friend=friends[i];if(friend.search&&friend.search.length>0){var box=utils.misc.getPreferredPictureBox();friend.picwidth=box.width;friend.picheight=box.height;friend.search=Normalizer.toAscii(friend.search);utils.templates.append(list,friend);}}
if(i<friends.length){window.setTimeout(function renderNextChunk(){doRenderGroupItems(end,groupsList,group,friends,element,cb);});}
else{list.removeChild(list.firstElementChild);cb();}}
function renderGroup(groupsList,group,friends,cb){if(!friends||friends.length===0){window.setTimeout(cb);return;}
var fragment=document.createDocumentFragment();var element=utils.templates.append(groupsList,{group:group},fragment);doRenderGroupItems(0,groupsList,group,friends,element,function(){cb(fragment);});}
function getStringToBeOrdered(contact,order){var ret=contact.search;if(!ret){ret=[];order.forEach(function(field){ret.push(getValue(contact,field));});ret=contact.search=ret.join(' ');}
return ret;}
function getValue(contact,field){var out=contact[field];if(out){out=Array.isArray(out)?out[0]:out;}else{out='';}
return out;}
function getGroupName(contact,order){var ret=getStringToBeOrdered(contact,order);ret=ret.charAt(0).toUpperCase();ret=ret.replace(/[ÁÀ]/ig,'A');ret=ret.replace(/[ÉÈ]/ig,'E');ret=ret.replace(/[ÍÌ]/ig,'I');ret=ret.replace(/[ÓÒ]/ig,'O');ret=ret.replace(/[ÚÙ]/ig,'U');if(HEADER_LETTERS.indexOf(ret)<0){ret='#';}
return ret;}
return{'render':render};})();

View file

@ -0,0 +1,475 @@
/* globals Rest */
/* exported GmailConnector */
'use strict';
/*
Gmail Contacts connector
Provides the capabilities to connect to the Gmail Contacts service
and return the contacts on a readable and importable format.
*/
var GmailConnector = (function GmailConnector() {
// Google contacts service end point,
// force a huge number of contacts to not paginate :S
var END_POINT =
'https://www.google.com/m8/feeds/contacts/default/full/?max-results=10000';
var GROUPS_END_POINT =
'https://www.google.com/m8/feeds/groups/default/full/';
var EXTRA_HEADERS = {
'GData-Version': '3.0'
};
var GD_NAMESPACE = 'http://schemas.google.com/g/2005';
var CATEGORY = 'gmail';
var URN_IDENTIFIER = 'urn:service:gmail:uid:';
// Will be used as a cache for the thumbnail url for each contact
var photoUrls = {};
// In some cases we will need the access token, cache a copy
var accessToken = null;
// We have a xml response from Google, all the entries in an array,
// no matter if they are xml entries.
// Despite of being a node, inject a 'uid' as this will be necessary
// for the selection
var nodeListToArray = function nodeListToArray(response) {
var entries = response.getElementsByTagName('entry');
var contacts = [];
var numContacts = entries.length;
for (var i = 0; i < numContacts; i++) {
contacts.push(gContactToJson(entries[i]));
}
return contacts;
};
// Returns the object used to build the headers necesary by the service
var buildRequestHeaders = function buildRequestHeaders(access_token) {
var requestHeaders = EXTRA_HEADERS;
requestHeaders.Authorization = 'OAuth ' + access_token;
return requestHeaders;
};
// Gets a list of all contacts giving a valid access token
var listAllContacts = function listAllContacts(access_token,
callbacks) {
// Copy the access_token
accessToken = access_token;
photoUrls = {};
getContactsGroup(access_token, callbacks);
};
var getContactsGroup = function getContactsGroup(access_token,
callbacks) {
var groupCallbacks = {
success: function onSuccess(response) {
// Locate the entry witch systemGroup id is 'Contacts'
var feed = response.querySelector('feed');
if (feed === null) {
callbacks.error();
return;
}
var sgc = feed.querySelector('systemGroup[id="Contacts"]');
if (sgc !== null) {
var id = sgc.parentNode.querySelector('id').textContent;
getContactsByGroup(id, access_token, callbacks);
} else {
callbacks.error();
}
},
error: function(e) {
if (e && e.status === 401) {
// This is a token expired / invalid token problem
window.console.warn('GMail Access token expired or invalid. ',
'restarting flow!');
callbacks.success({
error: {
code: 190
}
});
}
else {
callbacks.error();
}
},
timeout: callbacks.timeout
};
return performAPIRequest(GROUPS_END_POINT, groupCallbacks, access_token);
};
// Retrieve all the contacts for the specific groupId
var getContactsByGroup = function getContactsByGroup(groupId, access_token,
callbacks) {
var listingCallbacks = {
success: function onSuccess(response) {
callbacks.success({
'data': nodeListToArray(response)
});
},
error: callbacks.error,
timeout: callbacks.timeout
};
var groupUrl = END_POINT + '&group=' + groupId;
return performAPIRequest(groupUrl, listingCallbacks, access_token);
};
// Given a Google contacts api url add the authentication and
// extra headers to perform the correct request
var performAPIRequest = function performAPIRequest(url, callbacks,
access_token) {
return Rest.get(url, callbacks, {
'requestHeaders': buildRequestHeaders(access_token),
'responseType': 'xml'
});
};
// Return the list of contacts on the device imported using this connector
var listDeviceContacts = function listDeviceContacts(callbacks) {
var filterOptions = {
filterValue: CATEGORY,
filterOp: 'contains',
filterBy: ['category']
};
var req = navigator.mozContacts.find(filterOptions);
req.onsuccess = function() {
callbacks.success(req.result);
};
req.onerror = function onError() {
callbacks.success([]);
};
};
var getImporter = function getImporter(contactsList, access_token) {
return new window.ContactsImporter(contactsList, access_token, this);
};
var cleanContacts = function cleanContacts(contactsList, mode, cb) {
var cleaner = new window.ContactsCleaner(contactsList);
window.setTimeout(cleaner.start, 0);
if (cb) {
cb(cleaner);
}
};
var getValueForNode = function getValueForNode(doc, name, def) {
var defaultValue = def || '';
if (doc == null || name == null) {
return defaultValue;
}
var node = doc.querySelector(name);
if (node && node.textContent) {
return node.textContent;
}
return defaultValue;
};
/*
Given an xml contact entry from gmail contacts we will need to output
a json object that contains the minimun viable information to display
that contact into a list.
The fields for that minimun visualization object are:
uid
givenName
familyName
email1
*/
var adaptDataForShowing = function adaptDataForShowing(contact) {
var output = {
'uid': '-1',
'givenName': '',
'familyName': '',
'email1': '',
'contactPictureUri': ''
};
output.uid = contact.uid;
if (contact.familyName) {
output.familyName = contact.familyName;
}
if (contact.email && contact.email.length > 0) {
output.email1 = contact.email[0].value;
}
var tel;
if (contact.tel && contact.tel.length > 0) {
tel = contact.tel[0].value;
}
if(contact.givenName) {
output.givenName = contact.givenName;
} else if(!contact.familyName) {
output.givenName = output.email1 || contact.org || tel;
} else if(contact.familyName) {
output.givenName = '' ;
}
var photoUrl = buildContactPhotoURL(contact, accessToken);
if (photoUrl) {
output.contactPictureUri = photoUrl;
}
return output;
};
var adaptDataForSaving = function adaptDataForSaving(contact) {
return contact;
};
// Transform a Google contact entry into json format.
// The json format is the same used in Contacts api ;P
var gContactToJson = function gContactToJson(googleContact) {
var output = {};
// This field will be needed for indexing within the
// import process, not for the api
output.uid = getUid(googleContact);
output.name = [getValueForNode(googleContact, 'title')];
// Store the photo url, not in the contact itself
var photoUrl = googleContact.querySelector('link[type="image/*"]');
if (photoUrl) {
photoUrl = photoUrl.getAttribute('href');
} else {
// No image link
photoUrl = '';
}
photoUrls[output.uid] = photoUrl;
var name = googleContact.querySelector('name');
if (name) {
var contactName = getValueForNode(name, 'givenName');
if (contactName) {
output.givenName = [contactName];
}
var contactFamilyName = getValueForNode(name, 'familyName');
if (contactFamilyName) {
output.familyName = [contactFamilyName];
}
var contactSuffix = getValueForNode(name, 'additionalName');
if (contactSuffix) {
output.additionalName = [contactSuffix];
}
}
output.email = parseEmails(googleContact);
output.adr = parseAddress(googleContact);
output.tel = parsePhones(googleContact);
var org = googleContact.querySelector('organization');
if (org) {
output.org = [getValueForNode(org, 'orgName')];
output.jobTitle = [getValueForNode(org, 'orgTitle')];
}
var bday = googleContact.querySelector('birthday');
if (bday) {
var bdayMS = Date.parse(bday.getAttribute('when'));
if (!isNaN(bdayMS)) {
output.bday = new Date(bdayMS);
}
}
var content = googleContact.querySelector('content');
if (content) {
output.note = [content.textContent];
}
output.category = [CATEGORY];
output.url = [{
type: ['source'],
value: getContactURI(output)
}];
return output;
};
var getContactURI = function getContactURI(contact) {
return URN_IDENTIFIER + contact.uid;
};
// This will be a full url like:
// http://www.google.com/m8/feeds/contacts/<email>/base/<contact_id>
// for a specific contact node
var getUid = function getUid(contact) {
return contact.querySelector('id').textContent;
};
// Returns an array with the possible emails found in a contact
// as a ContactField format
var parseEmails = function parseEmails(googleContact) {
var DEFAULT_EMAIL_TYPE = 'other';
var emails = [];
var fields = googleContact.getElementsByTagNameNS(GD_NAMESPACE,
'email');
if (fields && fields.length > 0) {
for (var i = 0; i < fields.length; i++) {
var emailField = fields.item(i);
// Type format: rel="http://schemas.google.com/g/2005#home"
var type = emailField.getAttribute('rel') || DEFAULT_EMAIL_TYPE;
if (type.indexOf('#') > -1) {
type = type.substr(type.indexOf('#') + 1);
}
emails.push({
'type': [type],
'value': emailField.getAttribute('address')
});
}
}
return emails;
};
// Given a google contact returns an array of ContactAddress
var parseAddress = function parseAddress(googleContact) {
var addresses = [];
var fields = googleContact.getElementsByTagNameNS(GD_NAMESPACE,
'structuredPostalAddress');
if (fields && fields.length > 0) {
for (var i = 0; i < fields.length; i++) {
var field = fields.item(i);
var address = {};
address.streetAddress = getValueForNode(field, 'street');
address.locality = getValueForNode(field, 'city');
address.region = getValueForNode(field, 'region');
address.postalCode = getValueForNode(field, 'postcode');
address.countryName = getValueForNode(field, 'country');
addresses.push(address);
}
}
return addresses;
};
// Given a google contact this function returns an array of
// ContactField with the pones stored for that contact
var parsePhones = function parsePhones(googleContact) {
var DEFAULT_PHONE_TYPE = 'other';
var GMAIL_MAP = {
'work_fax' : 'faxOffice',
'home_fax' : 'faxHome',
'pager' : 'other',
'main' : 'other'
};
var phones = [];
var fields = googleContact.getElementsByTagNameNS(GD_NAMESPACE,
'phoneNumber');
if (fields && fields.length > 0) {
for (var i = 0; i < fields.length; i++) {
var field = fields.item(i);
// Type format: rel="http://schemas.google.com/g/2005#home"
var type = field.getAttribute('rel') || DEFAULT_PHONE_TYPE;
if (type.indexOf('#') > -1) {
type = type.substr(type.indexOf('#') + 1);
}
phones.push({
'type': [GMAIL_MAP[type] || type],
'value': field.textContent
});
}
}
return phones;
};
// Given a contact from the mozcontact api, fetch the Google Contacts
// identifier
var getContactUid = function getContactUid(deviceContact) {
var out = -1;
var url = deviceContact.url;
if (Array.isArray(url)) {
var targetUrls = url.filter(function(aUrl) {
return Array.isArray(aUrl.type) &&
aUrl.type.indexOf('source') !== -1 &&
aUrl.value;
});
if (targetUrls[0]) {
out = resolveURI(targetUrls[0].value);
}
}
return out;
};
// From a contact URL, that we expect to be a URI
// return the google contact id if we find it on
// the uri, -1 otherwise
var resolveURI = function resolveURI(uri) {
if (uri && uri.indexOf(URN_IDENTIFIER) === 0) {
var output = uri.substr(URN_IDENTIFIER.length);
if (output && output.length > 0) {
return output;
}
}
return -1;
};
var downloadContactPicture = function downloadContactPicture(googleContact,
access_token, callbacks) {
var url = buildContactPhotoURL(googleContact, access_token);
return Rest.get(url, callbacks, {
'responseType': 'blob'
});
};
// Build the url of the photo with the access token
var buildContactPhotoURL = function contactPhotoURL(contact, access_token) {
if (photoUrls && photoUrls[contact.uid]) {
return photoUrls[contact.uid] + '?access_token=' + access_token;
}
return null;
};
var startSync = function startSync() {
//We don't sync Google contacts (yet)
};
var getServiceName = (function getServiceName() {
return 'gmail';
})();
var getAutomaticLogout = (function getAutomaticLogout() {
return true;
})();
return {
'listAllContacts': listAllContacts,
'listDeviceContacts': listDeviceContacts,
'getImporter': getImporter,
'cleanContacts': cleanContacts,
'adaptDataForShowing': adaptDataForShowing,
'adaptDataForSaving': adaptDataForSaving,
'getContactUid': getContactUid,
'downloadContactPicture': downloadContactPicture,
'startSync': startSync,
'name': getServiceName,
'automaticLogout': getAutomaticLogout,
'gContactToJson': gContactToJson
};
})();

View file

@ -0,0 +1,12 @@
;'use strict';(function(document){var params=parseParams(window.location.search.substring(1));var serviceName=getServiceName();var allowedOrigin=location.origin;function parseParams(paramsStr){var out={};var paramsList;if(paramsStr.indexOf('&')!=-1){paramsList=paramsStr.split('&');}
else{paramsList=[paramsStr];}
paramsList.forEach(function(param){var paramsValues=param.split('=');out[paramsValues[0]]=paramsValues[1];});return out;}
function getServiceName(){return params.service;}
var servicesConnectors={'live':function(cb){LazyLoader.load('/shared/js/contacts/import/live/live_connector.js',function(){cb(LiveConnector);});},'gmail':function gmailLoader(cb){LazyLoader.load('/shared/js/contacts/import/gmail/gmail_connector.js',function onLoad(){cb(GmailConnector);});},'facebook':function(cb){var files=['/shared/js/fb/fb_request.js','/shared/js/contacts/import/facebook/fb_data.js','/shared/js/contacts/import/facebook/fb_utils.js','/shared/js/fb/fb_reader_utils.js','/shared/js/contacts/import/facebook/fb_contact_utils.js','/shared/js/contacts/import/facebook/fb_query.js','/shared/js/contacts/import/facebook/fb_contact.js','/shared/js/contacts/import/facebook/facebook_connector.js','/shared/pages/import/style/facebook.css'];if(!params.ftu){files.push('/facebook/js/fb_sync.js');}
LazyLoader.load(files,function(){cb(FacebookConnector);});}};function getServiceConnector(cb){if(serviceName){servicesConnectors[serviceName](cb);}
else{throw new Error('Service Connector not found !!!');}}
function tokenReady(access_token){if(document.readyState==='complete'){onLoad(access_token);}
else{window.addEventListener('load',function do_load(){onLoad(access_token);window.removeEventListener('load',do_load);});}}
function onLoad(access_token){utils.listeners.add({'#import-action':importer.ui.importAll,'#done-search':window.Search.exitSearchMode,'#groups-list':importer.ui.selection,'#header':[{event:'action',handler:importer.ui.end}],'#search-start':[{event:'click',handler:window.Search.enterSearchMode}]});document.querySelector('#select-all').onclick=importer.ui.selectAll;document.querySelector('#deselect-all').onclick=importer.ui.unSelectAll;importer.ui.init();getServiceConnector(function(connector){importer.start(access_token,connector,allowedOrigin);});}
window.addEventListener('message',function getAccessToken(e){if(e.origin!==allowedOrigin){return;}
window.removeEventListener('message',getAccessToken);if(e.data.type==='token'){tokenReady(e.data.data);}});parent.postMessage({type:'messaging_ready',data:''},allowedOrigin);})(document);

View file

@ -0,0 +1,4 @@
;;;'use strict';if(!window.ImportStatusData){var ImportStatusData=(function(){var store=null;var DS_NAME='Import_Status_Data';var datastoreLoading=false;var datastoreLoaded=false;var DS_LOADED_EVENT='datastore_loaded';var getDatastore=function getDatastore(){return new Promise(function(resolve,reject){if(!navigator.getDataStores){reject({name:'DatastoreNotEnabled'});return;}
if(datastoreLoaded){resolve(store);return;}
if(datastoreLoading){document.addEventListener(DS_LOADED_EVENT,function loadedHandler(){document.removeEventListener(DS_LOADED_EVENT,loadedHandler);resolve(store);});}
datastoreLoading=true;navigator.getDataStores(DS_NAME).then(function(stores){store=stores[0];},reject).then(function(){datastoreLoading=false;datastoreLoaded=true;document.dispatchEvent(new CustomEvent(DS_LOADED_EVENT));resolve(store);},reject);});};var put=function put(key,obj){return new Promise(function(resolve,reject){getDatastore().then(function success(store){store.put(obj,key).then(resolve,reject);},reject);});};var remove=function remove(key){return new Promise(function(resolve,reject){getDatastore().then(function success(store){store.remove(key).then(resolve,reject);},reject);});};var get=function get(key){return new Promise(function(resolve,reject){getDatastore().then(function success(store){store.get(key).then(resolve,reject);},reject);});};var clear=function clear(){return new Promise(function(resolve,reject){getDatastore().then(function success(store){store.clear().then(resolve,reject);},reject);});};return{put:put,remove:remove,get:get,clear:clear};})();}

View file

@ -0,0 +1,69 @@
;'use strict';if(typeof window.importer==='undefined'){(function(document){var Importer=window.importer={};var UI=Importer.ui={};var TOKEN_EXPIRED_STR='request_token_expired';var TOKEN_EXPIRED_CODE=190;var serviceConnector;var targetApp;var access_token;var startCallback;var selectedContacts={};var unSelectedContacts={};var selectableFriends={};var myFriends,myFriendsByUid;var checked=0;var checkNodeList;var existingContacts=[];var existingContactsByUid={};var contactsLoaded=false,friendsLoaded=false;var currentRequest;var currentNetworkRequest=null;var cancelled=false;var friendsImported;var syncOngoing=false;var updateButton,selectAllButton,deSelectAllButton,contactList,headerElement,friendsMsgElement,scrollableElement;var imgLoader;var HARD_LIMIT_SYNC=300;var tokenKey;var isOnLine=navigator.onLine;var ongoingImport=false;var ongoingClean=false;var theImporter,theCleaner;window.addEventListener('online',onLineChanged);window.addEventListener('offline',onLineChanged);function notifyParent(message,origin){parent.postMessage({type:message.type||'',data:message.data||'',messageId:message.messageId,additionalMessageId:message.additionalMessageId},origin);}
function showOfflineDialog(yesCb,noCb){var recommend=serviceConnector.name==='facebook';var dialog=parent.document.getElementById('confirmation-message');parent.LazyLoader.load(dialog,function(){LazyLoader.load('/shared/js/confirm.js',function(){ConfirmDialog.show('connectionLost','connectionLostMsg',{title:'noOption',isRecommend:!recommend,callback:function(){ConfirmDialog.hide();noCb();}},{title:'yesOption',isRecommend:recommend,callback:function(){ConfirmDialog.hide();yesCb();}},{zIndex:'10000'});});});}
function onLineChanged(){isOnLine=navigator.onLine;if(isOnLine===false&&ongoingImport){theImporter.hold();showOfflineDialog(function(){theImporter.resume();},function(){theImporter.finish();});}}
function getTokenKey(){var key='tokenData';var serviceName=serviceConnector.name;if(serviceName!=='facebook'){key+=('_'+serviceName);}
return key;}
var searchSource={getNodes:function(){return contactList.querySelectorAll('section > ol > li');},getFirstNode:function(){return contactList.querySelector('section > ol > li');},getNextNode:function(contact){var out=contact.nextElementSibling;var nextParent=contact.parentNode.parentNode.nextElementSibling;while(!out&&nextParent){out=nextParent.querySelector('ol > li:first-child');nextParent=nextParent.nextElementSibling;}
return out;},expectMoreNodes:function(){return false;},clone:function(node){return node.cloneNode(true);},getNodeById:function(id){return contactsList.querySelector('[data-uuid="'+id+'"]');},getSearchText:function(node){return node.dataset.search;},click:onSearchResultCb};UI.init=function(){var overlay=document.querySelector('nav[data-type="scrollbar"] p');var jumper=document.querySelector('nav[data-type="scrollbar"] ol');updateButton=document.getElementById('import-action');selectAllButton=document.querySelector('#select-all');deSelectAllButton=document.querySelector('#deselect-all');contactList=document.querySelector('#groups-list');headerElement=document.querySelector('header');friendsMsgElement=document.querySelector('#friends-msg');scrollableElement=document.querySelector('#mainContent');var params={overlay:overlay,jumper:jumper,groupSelector:'#group-',scrollToCb:scrollToCb};utils.alphaScroll.init(params);window.Search.init(searchSource,true);};function notifyLogout(){var msg={type:'logout_finished',data:''};parent.postMessage(msg,targetApp);}
UI.end=function(event){notifyParent({type:'window_close'},targetApp);notifyLogout();};function removeToken(cb){var theCb=(typeof cb==='function')?cb:function(){};ImportStatusData.remove(tokenKey).then(theCb,theCb);}
function markPendingLogout(url,service,cb){var PENDING_LOGOUT_KEY='pendingLogout';window.asyncStorage.getItem(PENDING_LOGOUT_KEY,function(data){var obj=data||{};obj[service]=url;var theCb=(typeof cb==='function')?cb:function(){};window.asyncStorage.setItem(PENDING_LOGOUT_KEY,obj,theCb,theCb);});}
function serviceLogout(cb){if(serviceConnector.automaticLogout){var serviceName=serviceConnector.name;var logoutUrl=oauthflow.params[serviceName].logoutUrl;var callbacks={error:function(){window.console.warn('Error while logging out user ',logoutUrl);removeToken();markPendingLogout(logoutUrl,serviceName,cb);},timeout:function(){window.console.warn('Timeout while logging out user ',logoutUrl);removeToken();markPendingLogout(logoutUrl,serviceName,cb);},success:function(){window.console.log('Successfully logged out');removeToken(cb);}};if(navigator.onLine===true){Rest.get(logoutUrl,callbacks);}
else{removeToken();markPendingLogout(logoutUrl,serviceName,cb);}}
else{cb();}}
function tokenExpired(error){return(error.code===TOKEN_EXPIRED_CODE||error===TOKEN_EXPIRED_STR);}
function scrollToCb(groupContainer){scrollableElement.scrollTop=groupContainer.offsetTop;}
function onSearchResultCb(e){var target=e.target;var uid=target.dataset.uuid;var checkbox=target.querySelector('input[type="checkbox"]');checkbox.checked=!checkbox.checked;var realNode=contactList.querySelector('[data-uuid='+'"'+uid+'"'+']');UI.selection({target:realNode});}
Importer.reset=function(){selectedContacts={};unSelectedContacts={};selectableFriends={};myFriends=[];myFriendsByUid={};checked=0;existingContacts=[];existingContactsByUid={};contactsLoaded=false;friendsLoaded=false;currentRequest=null;currentNetworkRequest=null;friendsImported=false;syncOngoing=false;ongoingImport=ongoingClean=false;selectAllButton=document.getElementById('select-all');deSelectAllButton=document.getElementById('deselect-all');};Importer.start=function(acc_tk,connector,ptargetApp,startCb){startCallback=startCb;var serviceName=connector.name;document.querySelector('#content h1').setAttribute('data-l10n-id',serviceName+'-serviceName');document.body.classList.add(serviceName);serviceConnector=connector;targetApp=ptargetApp;tokenKey=getTokenKey();setCurtainHandlers();if(acc_tk){access_token=acc_tk;Importer.getFriends(acc_tk);}
else{oauth2.getAccessToken(function(new_acc_tk){access_token=new_acc_tk;Importer.getFriends(new_acc_tk);},'friends',serviceConnector.name);}};function contactsReady(result){existingContacts=result;contactsLoaded=true;if(friendsLoaded){markExisting(existingContacts);}}
function syncSuccess(numChanged){window.console.log('Synchronization ended!!!');syncOngoing=false;serviceConnector.scheduleNextSync();var msg={type:'sync_finished',data:numChanged||''};parent.postMessage(msg,targetApp);}
function startSync(){var totalExisting=existingContacts.length;if(totalExisting>0&&totalExisting<HARD_LIMIT_SYNC&&!syncOngoing){syncOngoing=true;serviceConnector.startSync(existingContacts,myFriendsByUid,syncSuccess);}
else{var msg={type:'sync_finished',data:0};parent.postMessage(msg,targetApp);}}
function friendsAvailable(){imgLoader=new ImageLoader('#mainContent','.block-item:not([data-uuid="#uid#"])');var s='.block-item:not([data-uuid="#uid#"]) input[type="checkbox"]';checkNodeList=contactList.querySelectorAll(s);Array.prototype.slice.call(checkNodeList,0,checkNodeList.length);friendsLoaded=true;if(contactsLoaded){window.addEventListener('message',function importOnViewPort(e){if(e.origin!==targetApp){return;}
var data=e.data;if(data&&data.type==='dom_transition_end'){window.removeEventListener('message',importOnViewPort);window.setTimeout(startSync,0);}});markExisting(existingContacts);}
Curtain.hide(sendReadyEvent);if(typeof startCallback==='function'){window.setTimeout(startCallback,0);}}
function sendReadyEvent(){parent.postMessage({type:'ready',data:''},targetApp);}
function markExisting(deviceFriends){updateButton.setAttribute('data-l10n-id',deviceFriends.length===0?'import':'update');var reallyExisting=0;deviceFriends.forEach(function(fbContact){var uid=serviceConnector.getContactUid(fbContact);if(myFriendsByUid[uid]){reallyExisting++;}
delete selectableFriends[uid];var ele=document.querySelector('[data-uuid="'+uid+'"]');if(ele){setChecked(ele.querySelector('input[type="checkbox"]'),true);}
if(existingContactsByUid[uid]){existingContactsByUid[uid].push(fbContact);}else{existingContactsByUid[uid]=[fbContact];}});if(myFriends.length<1){friendsMsgElement.setAttribute('data-l10n-id','fbNoFriends');}else{var newValue=myFriends.length-
Object.keys(existingContactsByUid).length;navigator.mozL10n.setAttributes(friendsMsgElement,'fbFriendsFound',{numFriends:newValue<0?0:newValue});}
checkDisabledButtons();}
Importer.setSelected=function(friends){selectedContacts=friends;};function setCurtainHandlers(){Curtain.oncancel=cancelCb;}
Importer.getFriends=function(acc_tk){currentNetworkRequest=serviceConnector.listAllContacts(acc_tk,{success:importer.friendsReady,error:importer.errorHandler,timeout:importer.timeoutHandler});if(!navigator.mozContacts){return;}
var callbacks={success:contactsReady,error:function(errorName){window.console.error('Error while retrieving existing dev contacts: ',errorName);}};serviceConnector.listDeviceContacts(callbacks);};Importer.friendsReady=function(response){if(typeof response.error==='undefined'){var lmyFriends=response.data;if(lmyFriends.length===0){showNoFriends(function(){Curtain.hide(UI.end);});return;}
if(typeof serviceConnector.oncontactsloaded==='function'){serviceConnector.oncontactsloaded(lmyFriends);}
myFriendsByUid={};myFriends=[];var totalMyFriends=lmyFriends.length;for(var i=0;i<totalMyFriends;i++){var aFriend=lmyFriends[i];aFriend._idxFriendsArray=i;myFriends.push(serviceConnector.adaptDataForShowing(aFriend));myFriendsByUid[aFriend.uid]=aFriend;selectableFriends[aFriend.uid]=aFriend;}
asyncStorage.getItem('order.lastname',function orderValue(lastName){var options={container:'#groups-list',orderBy:lastName?'lastName':'firstName'};FriendListRenderer.render(myFriends,friendsAvailable,options);});}
else{window.console.error('Error, while retrieving friends',response.error.message);if(!tokenExpired(response.error)){setCurtainHandlersErrorFriends();Curtain.show('error','friends');}
else{window.console.warn('Access Token expired or revoked');Curtain.hide(notifyParent.bind(null,{type:'token_error'},targetApp));ImportStatusData.remove(tokenKey).then(function token_removed(){oauth2.getAccessToken(function(new_acc_tk){access_token=new_acc_tk;Importer.getFriends(new_acc_tk);},'friends',serviceConnector.name);});}}};function showNoFriends(callback){var dialog=parent.document.getElementById('confirmation-message');parent.LazyLoader.load(dialog,function(){LazyLoader.load('/shared/js/confirm.js',function(){ConfirmDialog.show(null,'emptyAccount',{title:'ok',isRecommend:true,callback:function(){ConfirmDialog.hide();if(typeof callback==='function'){callback();}}},null,{zIndex:'10000'});if(typeof startCallback==='function'){window.setTimeout(startCallback,0);}});});}
function cancelImport(){cancelled=true;var cancelFunc=onUpdate;if(ongoingImport){cancelFunc=theImporter.finish;}else if(ongoingClean){cancelFunc=theCleaner.finish;}
cancelFunc();Curtain.show('message','canceling');}
function cancelCb(){if(currentNetworkRequest){currentNetworkRequest.cancel();currentNetworkRequest=null;}
Curtain.hide(notifyParent.bind(null,{type:'abort'},targetApp));}
function setCurtainHandlersErrorFriends(){Curtain.oncancel=function friends_cancel(){Curtain.hide(notifyParent.bind(null,{type:'abort'},targetApp));};Curtain.onretry=function get_friends(){Curtain.oncancel=cancelCb;Curtain.show('wait','friends');Importer.getFriends(access_token);};}
function checkUpdateButton(){if(Object.keys(selectedContacts).length>0||Object.keys(unSelectedContacts).length>0){updateButton.disabled=false;}else{updateButton.disabled=true;}}
function checkDisabledButtons(){checkUpdateButton();switch(checked){case 0:deSelectAllButton.disabled=true;selectAllButton.disabled=false;break;case myFriends.length:selectAllButton.disabled=true;deSelectAllButton.disabled=false;break;default:deSelectAllButton.disabled=false;selectAllButton.disabled=false;break;}}
function setChecked(element,value){if(element.checked!==value){value?++checked:--checked;}
element.checked=value;}
Importer.baseHandler=function(type){setCurtainHandlersErrorFriends();Curtain.show(type,'friends');};Importer.timeoutHandler=function(){Importer.baseHandler('timeout');};Importer.errorHandler=function(){Importer.baseHandler('error');};function onUpdate(numFriends,numMergedDuplicated){serviceLogout(notifyLogout);if(Importer.getContext()==='ftu'){Curtain.hide(notifyParent.bind(null,{type:'window_close',messageId:cancelled?null:{id:'friendsUpdated',args:{numFriends:numFriends}},additionalMessageId:(cancelled||!numMergedDuplicated)?null:{id:'friendsMerged',args:{numDups:numMergedDuplicated}}},targetApp));}else{notifyParent({type:'import_updated'},targetApp);window.addEventListener('message',function finished(e){if(e.origin!==targetApp){return;}
if(e.data.type==='contacts_loaded'){var messageId=cancelled?null:{id:'friendsUpdated',args:{numFriends:numFriends}};var additionalMessageId=(cancelled||!numMergedDuplicated)?null:{id:'friendsMerged',args:{numDups:numMergedDuplicated}};Curtain.hide(notifyParent.bind(null,{type:'window_close',messageId:messageId,additionalMessageId:additionalMessageId},targetApp));window.removeEventListener('message',finished);}});}}
function cleanContacts(onsuccess,progress){if(cancelled){return;}
var contacts=[];var unSelectedKeys=Object.keys(unSelectedContacts);unSelectedKeys.forEach(function iterator(uid){var deviceContacts=unSelectedContacts[uid];for(var i=0;i<deviceContacts.length;i++){contacts.push(deviceContacts[i]);}});var mode='update';if(unSelectedKeys.length===existingContacts.length&&Object.keys(selectedContacts).length===0){mode='clear';Curtain.hideMenu();}
serviceConnector.cleanContacts(contacts,mode,function gotCleaner(cleaner){if(cleaner){theCleaner=cleaner;ongoingClean=true;cleaner.oncleaned=progress.update;cleaner.onsuccess=function(){ongoingClean=false;theCleaner=null;onsuccess();};}
else{Importer.errorHandler();}});}
function getTotalUnselected(){var total=0;Object.keys(unSelectedContacts).forEach(function surfing(uid){total=total+unSelectedContacts[uid].length;});return total;}
UI.importAll=function(e){imgLoader.unload();var selected=Object.keys(selectedContacts).length;var unSelected=getTotalUnselected();var total=selected+unSelected;cancelled=false;var progress;if(selected>0){progress=Curtain.show('progress','import');progress.setTotal(total);Curtain.oncancel=cancelImport;Importer.importAll(function on_all_imported(totalImported,numDups){if(typeof serviceConnector.oncontactsimported==='function'){serviceConnector.oncontactsimported(existingContacts,friendsImported,function(){friendsImported=true;});}
if(!cancelled&&unSelected>0){progress.setFrom('update');cleanContacts(function callback(){onUpdate(progress.getValue(),numDups);},progress);}else{onUpdate(progress.getValue(),numDups);}},progress);}else if(unSelected>0){progress=Curtain.show('progress','update');progress.setTotal(total);Curtain.oncancel=cancelImport;cleanContacts(function callback(){onUpdate(progress.getValue());},progress);}};UI.selectAll=function(e){deSelectAllButton.disabled=false;selectAllButton.disabled=true;window.setTimeout(function doSelectAll(){bulkSelection(true);unSelectedContacts={};selectedContacts={};for(var uid in selectableFriends){selectedContacts[uid]=selectableFriends[uid];}
checkUpdateButton();},0);return false;};UI.unSelectAll=function(e){deSelectAllButton.disabled=true;selectAllButton.disabled=false;window.setTimeout(function doUnSelectAll(){bulkSelection(false);selectedContacts={};unSelectedContacts={};for(var uid in existingContactsByUid){unSelectedContacts[uid]=existingContactsByUid[uid];}
checkUpdateButton();},0);return false;};function bulkSelection(value){window.setTimeout(function(){doSelect(value,checkNodeList,0,10);},0);}
function doSelect(value,list,from,chunkSize){var total=list.length;for(var j=from;j<from+chunkSize&&j<total;j++){setChecked(list[j],value);}
var leftInterval=from+chunkSize;var rightInterval=leftInterval+chunkSize<total?leftInterval+chunkSize:total;window.setTimeout(function(){doSelect(value,list,leftInterval,rightInterval);},0);}
UI.selection=function(e){var out=false;var target=e.target;if(target&&target.dataset.uuid){var uuid=target.dataset.uuid;var checkbox=target.querySelector('input[type="checkbox"]');setChecked(checkbox,!checkbox.checked);if(checkbox.checked===true){if(unSelectedContacts[uuid]){delete unSelectedContacts[uuid];}else{selectedContacts[uuid]=myFriendsByUid[uuid];}}else{delete selectedContacts[uuid];if(existingContactsByUid[uuid]){unSelectedContacts[uuid]=existingContactsByUid[uuid];}}
checkDisabledButtons();out=true;}
return out;};Importer.getContext=function(){var out='contacts';if(window.location.search.indexOf('ftu')!==-1){out='ftu';}
return out;};function releaseObjs(cfdata){if(!cfdata){return;}
selectedContacts[cfdata.uid]=null;myFriendsByUid[cfdata.uid]=null;selectableFriends[cfdata.uid]=null;myFriends[cfdata._idxFriendsArray]=null;if(cfdata.fbInfo&&Array.isArray(cfdata.fbInfo.photo)){cfdata.fbInfo.photo=null;}}
function doImportAll(importedCB,progress){if(cancelled){return;}
checkNodeList=null;theImporter=serviceConnector.getImporter(selectedContacts,access_token);var cpuLock,screenLock;theImporter.oncontactimported=function(cfdata){releaseObjs(cfdata);progress.update();};theImporter.onsuccess=function(totalImported,totalMerged){ongoingImport=false;window.setTimeout(function imported(){utils.misc.setTimestamp(serviceConnector.name,()=>{importedCB(totalImported,totalMerged);});},0);if(cpuLock){cpuLock.unlock();}
if(screenLock){screenLock.unlock();}};cpuLock=navigator.requestWakeLock('cpu');screenLock=navigator.requestWakeLock('screen');document.querySelector('#groups-list').innerHTML='';ongoingImport=true;theImporter.start();}
Importer.importAll=function(importedCB,progress){if(isOnLine===true){doImportAll(importedCB,progress);}
else{window.console.warn('User is not online!!');showOfflineDialog(function(){doImportAll(importedCB,progress);},function(){Curtain.hide(UI.end);});}};})(document);}

View file

@ -0,0 +1,210 @@
'use strict';
/* global Rest */
/* global utils */
if (!window.LiveConnector) {
window.LiveConnector = (function() {
var LIVE_ENDPOINT = 'https://apis.live.net/v5.0/';
var CONTACTS_RESOURCE = 'me/contacts';
var PICTURE_RESOURCE = '/picture';
var LIVE_CATEGORY = 'live';
var itemsTypeMap = {
'personal': 'personal',
'mobile': 'mobile',
'business': 'work',
'other': 'other',
'preferred': 'personal'
};
function fillAddress(addrType, addrValue) {
var out = {
type: [addrType],
streetAddress: addrValue.street || '',
locality: addrValue.city || '',
region: addrValue.state || '',
countryName: addrValue.region || '',
postalCode: addrValue.postal_code || ''
};
return out;
}
function getURI(liveContact) {
return 'urn:uuid:' + (liveContact.user_id || liveContact.id);
}
function resolveURI(uri) {
var components = uri.split(':');
// The third element is the user id of the live contact
return components[2];
}
function LiveConnector() {
}
LiveConnector.prototype = {
listAllContacts: function(access_token, callbacks) {
var uriElements = [LIVE_ENDPOINT, CONTACTS_RESOURCE, '?',
'access_token', '=', access_token];
return Rest.get(uriElements.join(''), callbacks);
},
listDeviceContacts: function(callbacks) {
var filterOptions = {
filterValue: LIVE_CATEGORY,
filterOp: 'contains',
filterBy: ['category']
};
var req = navigator.mozContacts.find(filterOptions);
req.onsuccess = function() {
callbacks.success(req.result);
};
req.onerror = function() {
callbacks.error(req.error);
};
},
getImporter: function(contactsList, access_token) {
return new window.ContactsImporter(contactsList, access_token, this);
},
cleanContacts: function(contactsList, mode, cb) {
// Just a placeholder for the moment
var cleaner = new window.ContactsCleaner(contactsList);
window.setTimeout(cleaner.start, 0);
cb(cleaner);
},
adaptDataForShowing: function(source) {
var out = source;
out.uid = source.user_id || source.id;
out.givenName = [source.first_name || ''];
out.familyName = [source.last_name || ''];
out.email1 = source.emails.account ||
source.emails.preferred ||
source.emails.personal ||
'';
out.contactPictureUri = [LIVE_ENDPOINT, out.uid,
PICTURE_RESOURCE, '?type=medium'].join('');
return out;
},
adaptDataForSaving: function live2MozContact(liveContact) {
var out = {
givenName: [liveContact.first_name || ''],
familyName: [liveContact.last_name || ''],
name: [liveContact.name || ''],
tel: [],
email: [],
adr: [],
photo: liveContact.photo,
category: [LIVE_CATEGORY],
url: [{
type: ['source'],
value: getURI(liveContact)
}]
};
// Months in JS goes from 0 to 11 :(
var bmonth = liveContact.birth_month - 1;
var bday = liveContact.birth_day;
if (bmonth > -1 && bday) {
var birthdate = out.bday = new Date();
birthdate.setUTCDate(bday);
birthdate.setUTCMonth(bmonth, bday);
birthdate.setUTCFullYear(utils.misc.FLAG_YEAR_IGNORED);
}
var liveEmails = liveContact.emails || {};
var alreadyAddedEmails = {};
Object.keys(liveEmails).forEach(function(emailType) {
var emailValue = liveEmails[emailType];
var present = (typeof alreadyAddedEmails[emailValue] === 'boolean');
if (emailValue && !present) {
out.email.push({
type: [itemsTypeMap[emailType]],
value: emailValue
});
alreadyAddedEmails[emailValue] = true;
}
});
var livePhones = liveContact.phones || {};
Object.keys(livePhones).forEach(function(phoneType) {
var phoneValue = livePhones[phoneType];
if (phoneValue) {
out.tel.push({
type: [itemsTypeMap[phoneType]],
value: phoneValue
});
}
});
var liveAddresses = liveContact.addresses || {};
Object.keys(liveAddresses).forEach(function(addrType) {
var addrValue = liveAddresses[addrType];
if (addrValue) {
out.adr.push(fillAddress(itemsTypeMap[addrType], addrValue));
}
});
return out;
},
// It would allow to know the UID of a service contact already imported
// on the device. That is needed by the generic importer, for live
// a dummy implementation as we are currently not supporting updates
getContactUid: function(deviceContact) {
var out = '-1';
var url = deviceContact.url;
if (Array.isArray(url)) {
var targetUrls = url.filter(function(aUrl) {
return Array.isArray(aUrl.type) &&
aUrl.type.indexOf('source') !== -1;
});
if (targetUrls[0]) {
out = resolveURI(targetUrls[0].value);
}
}
return out;
},
get name() {
return 'live';
},
get automaticLogout() {
return true;
},
downloadContactPicture: function(contact, access_token, callbacks) {
if (contact.user_id) {
var uriElements = [LIVE_ENDPOINT, contact.user_id, PICTURE_RESOURCE,
'?', 'access_token', '=', access_token];
return Rest.get(uriElements.join(''), callbacks, {
responseType: 'blob'
});
}
else {
callbacks.success(null);
}
},
startSync: function() {
// Sync not supported
}
};
return new LiveConnector();
})();
}

View file

@ -0,0 +1,8 @@
;'use strict';var utils=window.utils||{};if(typeof utils.config==='undefined'){(function(){var configs={};var initializing={};var initialized={};var EVENT_INITIALIZED='config_initialized';utils.config=utils.config||{};utils.config.reset=function(){configs={};initializing={};initialized={};};function onLoad(xhr,file,resolveCb){configs[file]=xhr.response;delete initializing[file];initialized[file]=true;document.dispatchEvent(new CustomEvent(EVENT_INITIALIZED,{detail:{file:file,success:true}}));resolveCb(configs[file]);}
utils.config.load=function(file){return new Promise(function(resolve,reject){var isInitialized=initialized[file];if(isInitialized===true){resolve(configs[file]);return;}
var isInitializing=initializing[file];if(isInitializing){var handler=function(expectedFile,e){document.removeEventListener(EVENT_INITIALIZED,handler);var file=e.detail.file;if(expectedFile!==file){return;}
if(e.detail.success){resolve(configs[file]);}
else{reject();}}.bind(null,file);document.addEventListener(EVENT_INITIALIZED,handler);return;}
initializing[file]=true;var xhr=new XMLHttpRequest();xhr.open('GET',file,true);xhr.responseType='json';xhr.onload=function(){var resolvedCb=resolve;if(xhr.status!==200){resolvedCb=reject;}
onLoad(xhr,file,resolvedCb);};xhr.onerror=function(){document.dispatchEvent(new CustomEvent(EVENT_INITIALIZED,{detail:{file:file,success:false}}));delete initializing[file];reject(xhr.error);};try{xhr.send(null);}
catch(e){console.error('Error while loading config file: ',e.message);onLoad({status:404},file,reject);}});};})();}

View file

@ -0,0 +1,10 @@
;'use strict';var utils=window.utils||{};utils.importFromVcard=function(file,callback){var MERGE_DEPENDENCIES=['/shared/js/simple_phone_matcher.js','/shared/js/contacts/merger_adapter.js','/shared/js/contacts/contacts_merger.js','/shared/js/contacts/contacts_matcher.js'];Loader.utility('Overlay',function(){LazyLoader.load(MERGE_DEPENDENCIES,_loaded);});function _loaded(){var importedContacts=0;var cancelled=false;var importer=null;var text=null;var progress=utils.overlay.show('memoryCardContacts-reading','activityBar');utils.overlay.showMenu();utils.overlay.oncancel=function oncancel(){cancelled=true;if(importer){importer.finish();}else{utils.overlay.hide();}};readVCard(function(vcardText){if(vcardText){processTextFromFile(vcardText);}else{callback();}});function readVCard(cb){var reader;if(typeof file==='string'){cb(file);}else{reader=new FileReader();reader.onloadend=function(){text=reader.result;if(text){cb(text);}};try{reader.readAsText(file);}
catch(ex){console.error('Error reading the file '+ex.message);import_error();cb();}}}
function processTextFromFile(textFromFile){if(cancelled){return;}
var reader=new VCardReader(textFromFile);var match=textFromFile&&textFromFile.match(/END:VCARD/g);var cursor=reader.getAll();var numContacts=match?match.length:0;var numDupsMerged=0;var firstContact=null;var _doContinue=function(savedContact){if(savedContact){if(!firstContact){firstContact=savedContact;}
imported_contact();}
if(!cancelled){cursor.continue();}};cursor.onsuccess=function(evt){if(evt.target.result){var contact=evt.target.result;Matcher.match(contact,'passive',{onmatch:function(matches){var callbacks={success:function(mergedContact){numDupsMerged++;_doContinue(mergedContact);},error:_doContinue};contacts.adaptAndMerge(contact,matches,callbacks);},onmismatch:function(){var req=navigator.mozContacts.save(contact);req.onsuccess=function(){_doContinue(contact);};}});}else{utils.overlay.hide();if(!cancelled){var msgImported={id:'memoryCardContacts-imported3',args:{n:importedContacts}};var msgDupsMerged=numDupsMerged?{id:'contactsMerged',args:{numDups:numDupsMerged}}:null;utils.status.show(msgImported,msgDupsMerged);}
if(importedContacts>0){callback(importedContacts,firstContact.id);}else{callback(importedContacts);}}};import_read(numContacts);}
function import_read(n){progress.setClass('progressBar');progress.setHeaderMsg('memoryCardContacts-importing');progress.setTotal(n);}
function imported_contact(){importedContacts++;if(!cancelled){progress.update();}}
function import_error(e){console.error('Error importing from vcard: '+e.message);var cancel={title:'cancel',callback:function(){ConfirmDialog.hide();}};var retry={title:'retry',isRecommend:true,callback:function(){ConfirmDialog.hide();processTextFromFile(text);}};ConfirmDialog.show(null,'memoryCardContacts-error',cancel,retry);utils.overlay.hide();}}};

View file

@ -0,0 +1,21 @@
;'use strict';function SimContactsImporter(targetIcc){if(targetIcc===null){throw new Error('We need an icc to continue with this operation');}
var pointer=0;var CHUNK_SIZE=5;var numResponses=0;var self=this;var mustFinish=false;var loadedMatch=false;var DEFAULT_TEL_TYPE='other';var icc=targetIcc;var iccId=icc.iccInfo&&icc.iccInfo.iccid;var numDupsMerged=0;function generateIccContactUrl(contactid){var urlValue='urn:'+'uuid:'+(iccId||'iccId')+'-'+contactid;return[{type:['source','sim'],value:urlValue}];}
function notifyFinish(){if(typeof self.onfinish==='function'){window.setTimeout(self.onfinish,0,numDupsMerged,iccId);}}
function notifyImported(){if(typeof self.onimported==='function'){window.setTimeout(self.onimported,0);}}
function continueCb(){numResponses++;pointer++;notifyImported();if(pointer<self.items.length&&numResponses===CHUNK_SIZE){numResponses=0;mustFinish?notifyFinish():importSlice(pointer);}
else if(pointer>=self.items.length){notifyFinish();}}
function startMigration(){if(!mustFinish&&Array.isArray(self.items)&&self.items.length>0){importSlice(0);}
else{notifyFinish();}}
function onContactsReadyForImport(){if(typeof self.onread==='function'){self.onread(self.items.length);}
if(loadedMatch){startMigration();}
else{document.addEventListener('matchLoaded',function mloaded(){document.removeEventListener('matchLoaded',mloaded);startMigration();});}}
this.start=function(){numDupsMerged=0;if(mustFinish){notifyFinish();return;}
LazyLoader.load(['/shared/js/simple_phone_matcher.js','/shared/js/contacts/contacts_matcher.js','/shared/js/contacts/contacts_merger.js','/shared/js/contacts/utilities/image_thumbnail.js','/shared/js/contacts/merger_adapter.js'],function loaded(){loadedMatch=true;document.dispatchEvent(new CustomEvent('matchLoaded'));});var requestAdn,requestSdn;if(icc&&icc.readContacts){requestAdn=icc.readContacts('adn');}else{throw new Error('Not able to obtain a SIM import function from platform');}
requestAdn.onsuccess=function onsuccess(){if(mustFinish){notifyFinish();return;}
self.items=requestAdn.result||[];requestSdn=icc.readContacts('sdn');requestSdn.onsuccess=function onsuccess(){if(mustFinish){notifyFinish();return;}
if(Array.isArray(requestSdn.result)){self.items=self.items.concat(requestSdn.result);}
onContactsReadyForImport();};requestSdn.onerror=function error(){if(mustFinish){notifyFinish();return;}
console.warn('Could not read SDN Contacts from SIM Card',error.name);onContactsReadyForImport();};};requestAdn.onerror=function error(){if(typeof self.onerror==='function'){self.onerror(requestAdn.error);}};};this.finish=function(){mustFinish=true;};function importSlice(from){for(var i=from;i<from+CHUNK_SIZE&&i<self.items.length;i++){var item=self.items[i];var parsedName=utils.misc.parseName(item.name[0]);item.givenName=[parsedName.givenName];item.familyName=[parsedName.familyName];if(Array.isArray(item.tel)){var telItems=[];for(var j=0;j<item.tel.length;j++){var aTel=item.tel[j];if(aTel.value&&aTel.value.trim()){aTel.type=[DEFAULT_TEL_TYPE];telItems.push(aTel);}}
item.tel=telItems;}
item.category=['sim'];item.url=generateIccContactUrl(item.id);var contact=item;var cbs={onmatch:function(results){var mergeCbs={success:function(){numDupsMerged++;continueCb();},error:function(e){window.console.error('Error while merging: ',e.name);continueCb();}};contacts.adaptAndMerge(this,results,mergeCbs);}.bind(contact),onmismatch:function(){saveContact(this);}.bind(contact)};Matcher.match(item,'passive',cbs);}}
function saveContact(contact){var req=window.navigator.mozContacts.save(new mozContact(contact));req.onsuccess=function saveSuccess(){continueCb();};req.onerror=function saveError(){console.error('SIM Import: Error importing ',contact.id,req.error.name);continueCb();};}}

View file

@ -0,0 +1,18 @@
;;'use strict';var utils=window.utils||{};var SCALE_RATIO=window.devicePixelRatio||1;var LAST_IMPORT_TIMESTAMP_SUFFIX='_last_import_timestamp';function scale(size){return Math.round(SCALE_RATIO*size);}
if(!utils.misc){utils.misc={};utils.misc.toMozContact=function(contact){var outContact=contact;if(!(contact instanceof mozContact)){outContact=new mozContact(contact);outContact.id=contact.id||outContact.id;}
return outContact;};const FLAG_YEAR_IGNORED=9996;utils.misc.FLAG_YEAR_IGNORED=FLAG_YEAR_IGNORED;utils.misc.formatDate=function(date,customFormat){var dateString=null;try{var format=customFormat||{month:'short',day:'numeric'};var offset=date.getTimezoneOffset()*60*1000;var normalizedDate=new Date(date.getTime()+offset);var year=normalizedDate.getFullYear();if(year!==FLAG_YEAR_IGNORED){format.year='numeric';}
var f=new Intl.DateTimeFormat(navigator.languages,format);dateString=f.format(normalizedDate);}catch(err){console.error('Error parsing date: ',err);throw err;}
return dateString;};utils.misc.parseName=function(nameString){var MIN_LENGHT_SIGNIFICATIVE=3;var MIN_LENGHT_FN=2;var out={givenName:'',familyName:''};if(!nameString){return out;}
var str=nameString.trim();if(!str){return out;}
function startsWithUpper(str){var firstLetter=str.charAt(0);var capFirstLetter=firstLetter.toLocaleUpperCase();return(capFirstLetter===firstLetter);}
var tokens=nameString.split(/\s+/);var significativeTokens={};for(var j=0;j<tokens.length;j++){var token=tokens[j];if(token.length>MIN_LENGHT_SIGNIFICATIVE||startsWithUpper(token)){significativeTokens[token]=true;}}
var totalTokens=tokens.length;var lastToken=totalTokens-1;var rolePrevToken;var remainingTokens=Object.keys(significativeTokens).length;var outGivenNames=[],outFamilyNames=[];var numFns=0;while(lastToken>=0){if(significativeTokens[tokens[lastToken]]){remainingTokens--;}
var currToken=tokens[lastToken],nextToken=tokens[lastToken+1],prevToken=tokens[lastToken-1];if(!nextToken&&prevToken){outFamilyNames.push(currToken);numFns++;rolePrevToken='FN';}
else if(!prevToken){outGivenNames.push(currToken);rolePrevToken='GN';}
else if(nextToken){if(!significativeTokens[currToken]){if(rolePrevToken==='FN'){outFamilyNames.push(currToken);}
else{outGivenNames.push(currToken);}}
else if(currToken.length<MIN_LENGHT_FN){outGivenNames.push(currToken);rolePrevToken='GN';}
else{if(remainingTokens>=2&&numFns<2){outFamilyNames.push(currToken);numFns++;rolePrevToken='FN';}
else{outGivenNames.push(currToken);rolePrevToken='GN';}}}
lastToken--;}
out.givenName=outGivenNames.reverse().join(' ').trim();out.familyName=outFamilyNames.reverse().join(' ').trim();return out;};utils.misc.getPreferredPictureBox=function(){var imgThumbSize=oauthflow.params.facebook.imgThumbSize;var out={width:scale(imgThumbSize)};out.height=out.width;return out;};utils.misc.getPreferredPictureDetail=function(){var imgDetailWidth=oauthflow.params.facebook.imgDetailWidth;return scale(imgDetailWidth);};utils.misc.setTimestamp=function(type,callback){ImportStatusData.put(type+LAST_IMPORT_TIMESTAMP_SUFFIX,Date.now()).then(callback);};utils.misc.getTimestamp=function(type,callback){ImportStatusData.get(type+LAST_IMPORT_TIMESTAMP_SUFFIX).then(callback);};}

View file

@ -0,0 +1,11 @@
;'use strict';var utils=window.utils||{};(function(){utils.overlay={};var overlay=document.querySelector('#loading-overlay'),statusContainer=overlay.querySelector('p[role="status"]'),progressActivity=document.querySelector('#progress-activity'),progressTitle=document.querySelector('#progress-title'),progressElement=document.querySelector('#progress-element'),progressMsg=document.querySelector('#progress-msg'),menu=document.querySelector('#loading-overlay menu'),cancelButton=document.querySelector('#cancel-overlay');function ProgressBar(pMsgId,pClass){var counter=0;var total=0;var progressTextId=pMsgId||'genericProgress';var clazz=pClass;progressElement.setAttribute('value',0);function showMessage(){var attributes=progressTextId=='infiniteProgress'?{n:counter}:{current:counter,total:total};document.l10n.setAttributes(progressMsg,progressTextId,attributes);}
this.update=function(value){if(value&&value<=total&&value>=counter){counter=value;}else{counter++;}
progressElement.setAttribute('value',((counter*100)/total).toFixed());showMessage();};this.setTotal=function(ptotal){total=ptotal;};this.setClass=function(clazzName){setClass(clazzName);clazz=clazzName;if(clazzName==='activityBar'||clazzName==='spinner'){progressMsg.removeAttribute('data-l10n-id');}};this.setHeaderMsg=function(headerMsgId){progressTitle.setAttribute('data-l10n-id',headerMsgId);};}
utils.overlay.isAnimationPlaying=false;utils.overlay.isShown=false;utils.overlay._show=function _show(messageId,progressClass,textId){progressActivity.classList.remove('hide');if(typeof messageId==='string'){progressTitle.setAttribute('data-l10n-id',messageId);}else{document.l10n.setAttributes(progressTitle,messageId.id,messageId.args);}
progressMsg.textContent=null;progressMsg.removeAttribute('data-l10n-id');if(utils.overlay.isShown){return;}
overlay.classList.remove('hide');overlay.classList.remove('fade-out');overlay.classList.add('fade-in');utils.overlay.isAnimationPlaying=true;window.dispatchEvent(new CustomEvent('loadingoverlayshowing'));overlay.addEventListener('animationend',function ov_onFadeIn(ev){utils.overlay.isAnimationPlaying=false;overlay.removeEventListener('animationend',ov_onFadeIn);overlay.classList.remove('no-opacity');utils.overlay.isShown=true;});};utils.overlay.show=function show(messageId,progressClass,textId){var out;out=new ProgressBar(textId,progressClass);setClass(progressClass);utils.overlay.hideMenu();if(!utils.overlay.isAnimationPlaying){utils.overlay._show(messageId,progressClass,textId);return out;}
overlay.addEventListener('animationend',function ov_showWhenFinished(ev){overlay.removeEventListener('animationend',ov_showWhenFinished);utils.overlay._show(messageId,progressClass,textId);});return out;};utils.overlay.showMenu=function showMenu(){menu.classList.add('showed');};utils.overlay.hideMenu=function hideMenu(){menu.classList.remove('showed');};Object.defineProperty(utils.overlay,'oncancel',{set:function(cancelCb){if(typeof cancelCb==='function'){cancelButton.disabled=false;cancelButton.onclick=function on_cancel(e){delete cancelButton.onclick;cancelCb();return false;};}}});function setAsProgress(){statusContainer.classList.remove('loading-icon');progressElement.setAttribute('max','100');progressElement.setAttribute('value','0');}
function setClass(clazzName){switch(clazzName){case'spinner':progressElement.classList.remove('pack-activity');statusContainer.classList.add('loading-icon');progressElement.removeAttribute('max');progressElement.removeAttribute('value');break;case'activityBar':case'progressActivity':progressElement.classList.add('pack-activity');setAsProgress();break;case'progressBar':progressElement.classList.remove('pack-activity');setAsProgress();break;}}
utils.overlay._hide=function ov__hide(){if(!utils.overlay.isShown){return;}
overlay.classList.remove('fade-in');overlay.classList.add('fade-out');utils.overlay.isAnimationPlaying=true;window.dispatchEvent(new CustomEvent('loadingoverlayhiding'));overlay.addEventListener('animationend',function ov_onFadeOut(ev){utils.overlay.isAnimationPlaying=false;overlay.removeEventListener('animationend',ov_onFadeOut);progressActivity.classList.add('hide');overlay.classList.add('no-opacity');overlay.classList.add('hide');utils.overlay.isShown=false;});};utils.overlay.hide=function ov_hide(){if(!utils.overlay.isAnimationPlaying){utils.overlay._hide();return;}
overlay.addEventListener('animationend',function ov_hideWhenFinished(ev){overlay.removeEventListener('animationend',ov_hideWhenFinished);utils.overlay._hide();});};})();

View file

@ -0,0 +1,7 @@
;'use strict';var utils=window.utils||{};if(!utils.sdcard){var SdCard=utils.sdcard={NOT_INITIALIZED:0,NOT_AVAILABLE:1,AVAILABLE:2,SHARED:3,observers:{}};SdCard.status=SdCard.NOT_INITIALIZED;SdCard.deviceStorages=navigator.getDeviceStorages('sdcard');SdCard.deviceStorage=Array.isArray(SdCard.deviceStorages)&&SdCard.deviceStorages[0];SdCard._toStatus=function toStatus(state){switch(state){case'available':SdCard.status=SdCard.AVAILABLE;break;case'shared':SdCard.status=SdCard.SHARED;break;case'unavailable':case'deleted':SdCard.status=SdCard.NOT_AVAILABLE;break;}};SdCard.updateStorageState=function sd_updateStorageState(state){SdCard._toStatus(state);Object.keys(this.observers).forEach(function onObserver(name){if(typeof(SdCard.observers[name])==='function'){SdCard.observers[name].call(null,state);}});};if(SdCard.deviceStorage){SdCard.deviceStorage.addEventListener('change',function sd_deviceStorageChangeHandler(e){SdCard.updateStorageState(e.reason);});if(SdCard.status===SdCard.NOT_INITIALIZED){SdCard.deviceStorage.available().onsuccess=(function(e){SdCard.updateStorageState(e.target.result);});}}
SdCard.checkStorageCard=function sd_checkStorageCard(){return SdCard.status===SdCard.AVAILABLE;};SdCard.getStatus=function(cb){if(!SdCard.deviceStorage){SdCard._toStatus('unavailable');cb(SdCard.status);return;}
var req=SdCard.deviceStorage.available();req.onsuccess=function(){SdCard._toStatus(req.result);cb(SdCard.status);};req.onerror=function(){console.error('Error while determining SD Status',req.error.name);cb(SdCard.status);};};SdCard.retrieveFiles=function retrieveFilesContent(mimes,exts,cb){var fileArray=[];var forbiddenDirectories=['.Trash'];var storages=navigator.getDeviceStorages('sdcard');var numberOfStorages=storages.length;var currentStorage=0;var cursorOnError=function(){cb(this.error.name);};var reExt=new RegExp('.('+exts.join('|')+')$');var cursorOnSuccess=function(e){var file=e.target.result;if(!file){if(++currentStorage<numberOfStorages){cursor=storages[currentStorage].enumerate();cursor.onsuccess=cursorOnSuccess;cursor.onerror=cursorOnError;}else{cb(null,fileArray);}}else{if((mimes.indexOf(file.type)===-1)&&!reExt.test(file.name)){cursor.continue();}else{if(forbiddenDirectories.indexOf(file.name)===-1){fileArray.push(file);cursor.continue();}}}};var cursor=storages[currentStorage].enumerate();cursor.onsuccess=cursorOnSuccess;cursor.onerror=cursorOnError;};SdCard.getTextFromFile=function(file,cb){if(!file){return Promise.reject();}
return new Promise((resolve,reject)=>{const END_VCARD='end:vcard';const CHUNK_SIZE=1*1024*1024;var fileReader=new FileReader();var result=0;(function readChunk(offset,buffer){if(offset>=file.size){resolve(result);return;}
var chunk=file.slice(offset,offset+CHUNK_SIZE);offset+=CHUNK_SIZE;fileReader.readAsText(chunk);fileReader.onload=function(){buffer+=fileReader.result;if(!buffer.match(/end:vcard/gi)){readChunk(offset,buffer);return;}
var lastContactInChunk=buffer.toLowerCase().lastIndexOf(END_VCARD)+END_VCARD.length;cb(buffer.substr(0,lastContactInChunk)).then(partialResult=>{result+=partialResult;readChunk(offset,buffer.substr(lastContactInChunk+1));}).catch(error=>{console.warn(error);});};})(0,'');});};SdCard.subscribeToChanges=function(name,func,force){if(this.observers[name]!==undefined&&!force){return false;}
this.observers[name]=func;return true;};SdCard.unsubscribeToChanges=function(name){if(this.observers[name]){delete this.observers[name];return true;}else{return false;}};}

View file

@ -0,0 +1,5 @@
;'use strict';var utils=window.utils||{};utils.status=(function(){var STATUS_TIME=2000;var statusMsg=document.querySelector('#statusMsg');var hidingTimeout;var transitionEndTimeout;var additionalLine;var hideAnimationDone=function(){statusMsg.removeEventListener('transitionend',hideAnimationDone);statusMsg.classList.add('hidden');};var showAnimationDone=function(){if(transitionEndTimeout){clearTimeout(transitionEndTimeout);}
statusMsg.removeEventListener('transitionend',showAnimationDone);hidingTimeout=setTimeout(utils.status.hide,STATUS_TIME);};var hideStatus=function(){statusMsg.addEventListener('transitionend',hideAnimationDone);statusMsg.classList.remove('opening');statusMsg.classList.remove('bannerStart');statusMsg.querySelector('p').removeAttribute('data-l10n-id');if(additionalLine){statusMsg.removeChild(additionalLine);additionalLine=null;}};var setL10nAttributes=function(node,l10n){if(l10n&&l10n.id){navigator.mozL10n.setAttributes(node,l10n.id,l10n.args);return true;}else{console.error('Status arguments must be objects');return false;}};var showStatus=function(mainMessage,extra){statusMsg.removeEventListener('transitionend',showAnimationDone);statusMsg.removeEventListener('transitionend',hideAnimationDone);LazyLoader.load([statusMsg],function _loaded(){if(!setL10nAttributes(statusMsg.querySelector('p'),mainMessage)){return;}
if(extra){additionalLine=document.createElement('p');statusMsg.appendChild(additionalLine);setL10nAttributes(additionalLine,extra);}
if(statusMsg.classList.contains('opening')){clearTimeout(hidingTimeout);hidingTimeout=setTimeout(hideStatus,STATUS_TIME);return;}
statusMsg.classList.remove('hidden');statusMsg.addEventListener('transitionend',showAnimationDone);setTimeout(function displaying(){statusMsg.classList.add('opening');statusMsg.classList.add('bannerStart');transitionEndTimeout=setTimeout(showAnimationDone,STATUS_TIME);},10);});};return{show:showStatus,hide:hideStatus};})();

View file

@ -0,0 +1,74 @@
;'use strict';function parseDataUri(str){var re=/^data:(.+);(charset=([^;]+))?;?base64,(.*)$/;var matches=re.exec(str);if(matches){return{mime:matches[1],charset:matches[3],value:matches[4]};}
return null;}
function ensureMimeType(type){type=type.toLowerCase();if(!MimeMapper.isSupportedType(type)){type=MimeMapper.guessTypeFromExtension(type)||type;}
return type||'';}
function b64toBlob(b64Data,contentType,sliceSize){if(!b64Data){console.error('No b64 data provided to convert to blob.');return;}
contentType=contentType||'';sliceSize=sliceSize||1024;function charCodeFromCharacter(c){return c.charCodeAt(0);}
var blob;try{var byteCharacters=atob(b64Data);var byteArrays=[];for(var offset=0;offset<byteCharacters.length;offset+=sliceSize){var slice=byteCharacters.slice(offset,offset+sliceSize);var byteNumbers=[];for(var i=0,l=slice.length;i<l;i++){byteNumbers.push(charCodeFromCharacter(slice[i]));}
var byteArray=new Uint8Array(byteNumbers);byteArrays.push(byteArray);}
blob=new Blob(byteArrays,{type:contentType});}catch(e){console.error('Error parsing base64 data');}
return blob;}
var VCFReader=(function _VCFReader(){var ReBasic=/^([^:]+):(.+)$/;var ReTuple=/([a-zA-Z]+)=(.+)/;var WHITE_SPACE=' ';var DEFAULT_PHONE_TYPE='other';var VCARD_SIMPLE_TYPES={'fax':'faxOther','faxother':'faxOther','home':'home','internet':'internet','cell':'mobile','pager':'pager','personal':'home','pref':'pref','text':'text','textphone':'textphone','voice':'voice','work':'work'};var VCARD_COMPLEX_TYPES={'fax,work':'faxOffice','fax,home':'faxHome','voice,work':'work','voice,home':'home'};var REST_TIMEOUT=5000;function _parseTuple(p){var match=p.match(ReTuple);return match?[match[1].toLowerCase(),match[2]]:['type',p];}
function _parseLine(line){var parsed=ReBasic.exec(line);if(!parsed){return null;}
var tuples=parsed[1].split(/[;,]/);var fieldName=tuples.shift().toLowerCase();var fieldValue=/[^\s;]/.test(parsed[2])?parsed[2].split(';'):[];var meta={type:[]};var len=tuples.length;for(var i=0;i<len;i++){var tuple=_parseTuple(tuples[i]);if(tuple[0]==='type'){meta.type.push(tuple[1]);}else{meta[tuple[0]]=tuple[1];}}
if(fieldName==='photo'){if(meta.type.length){meta.type=ensureMimeType(meta.type[0]);}
fieldValue=parsed[2];if(meta.encoding&&(meta.encoding.toLowerCase()==='b'||meta.encoding.toLowerCase()==='base64')){meta.encoding='base64';}else{var parsedUri=parseDataUri(fieldValue);if(parsedUri){meta.type=ensureMimeType(parsedUri.mime);meta.encoding='base64';fieldValue=parsedUri.value;}}}
return{key:fieldName,data:{meta:meta,value:fieldValue}};}
var _parseEntries=function(cardArray,cb){var parsedCards=[];function sendIfFinished(contactObj){parsedCards.push(contactObj);if(parsedCards.length===cardArray.length){cb(parsedCards);}}
for(var i=0;i<cardArray.length;i++){var lines=cardArray[i];if(!lines){sendIfFinished(null);continue;}
var fields={};var len=lines.length;for(var j=0;j<len;j++){var line=lines[j];var parsedLine=_parseLine(line);if(!parsedLine){continue;}
if(!fields[parsedLine.key]){fields[parsedLine.key]=[];}
fields[parsedLine.key].push(parsedLine.data);}
if(!fields.fn&&!fields.n){sendIfFinished(null);continue;}
vcardToContact(fields,sendIfFinished);}};var qpRegexp=/=([a-zA-Z0-9]{2})/g;var _decodeQuoted=function(str){return decodeURIComponent(str.replace(qpRegexp,'%$1'));};function _decodeQP(metaObj,value){var isQP=metaObj&&metaObj.encoding&&(/quoted-printable/i).test(metaObj.encoding);if(isQP){value=_decodeQuoted(value);}
return value;}
var NAME_PARTS=['familyName','givenName','additionalName','honorificPrefix','honorificSuffix'];function _processName(vcardObj,contactObj){if(vcardObj.fn&&vcardObj.fn.length){var fnMeta=vcardObj.fn[0].meta;var fnValue=vcardObj.fn[0].value[0];contactObj.name=[_decodeQP(fnMeta,fnValue)];}
if(vcardObj.n&&vcardObj.n.length){var values=vcardObj.n[0].value;var meta=vcardObj.n[0].meta;for(var i=0;i<values.length;i++){var namePart=values[i];if(namePart&&NAME_PARTS[i]){contactObj[NAME_PARTS[i]]=_decodeQP(meta,namePart).split(',');}}
if(!contactObj.name){contactObj.name=[_decodeQP(meta,values.join(' ').trim())];}}
contactObj.givenName=contactObj.givenName||contactObj.name;return contactObj;}
var ADDR_PARTS=[null,null,'streetAddress','locality','region','postalCode','countryName'];function _processAddr(vcardObj,contactObj){if(!vcardObj.adr){return contactObj;}
contactObj.adr=[];for(var i=0;i<vcardObj.adr.length;i++){var cur={};var adr=vcardObj.adr[i];if(adr.meta&&adr.meta.type){cur.type=adr.meta.type;}
for(var j=2;j<adr.value.length;j++){var decoded=_decodeQP(adr.meta,adr.value[j]);if(decoded!==''){cur[ADDR_PARTS[j]]=decoded;}}
contactObj.adr.push(cur);}
return contactObj;}
function _processComm(vcardObj,contactObj){contactObj.tel=[];['tel','email','url'].forEach(function field2field(field){if(!vcardObj[field]){return;}
var len=vcardObj[field].length;var hasTypeMapper=function(x){return x.trim().toLowerCase();};var notTypeMapper=function(v,key){return v.meta[key].trim().toLowerCase();};var noType=function(field){return field!=='type';};var noPref=function(field){return field!=='pref';};var typeFilter=function(metaValue){return!!VCARD_SIMPLE_TYPES[metaValue];};for(var i=0;i<len;i++){var v=vcardObj[field][i];var metaValues;var cur={};if(v.meta){if(v.value){cur.value=_decodeQP(v.meta,v.value[0]);cur.value=cur.value.replace(/^tel:/i,'');}
if(v.meta.type){metaValues=([].slice.call(v.meta.type)).map(hasTypeMapper);}else{metaValues=Object.keys(v.meta).filter(noType).map(notTypeMapper.bind(null,v));}
if(metaValues.indexOf('pref')!==-1){cur.pref=true;metaValues=metaValues.filter(noPref);}
switch(metaValues.length){case 0:cur.type=[DEFAULT_PHONE_TYPE];break;case 1:cur.type=[VCARD_SIMPLE_TYPES[metaValues[0]]||DEFAULT_PHONE_TYPE];break;case 2:var complexType1=metaValues[0]+','+metaValues[1];var complexType2=metaValues[1]+','+metaValues[0];cur.type=[VCARD_COMPLEX_TYPES[complexType1]||VCARD_COMPLEX_TYPES[complexType2]||VCARD_SIMPLE_TYPES[metaValues[0]]||VCARD_SIMPLE_TYPES[metaValues[1]]||DEFAULT_PHONE_TYPE];break;default:cur.type=[VCARD_SIMPLE_TYPES[metaValues.filter(typeFilter).shift()]||DEFAULT_PHONE_TYPE];}}
if(!contactObj[field]){contactObj[field]=[];}
contactObj[field].push(cur);}});return contactObj;}
function _isValidDate(dateValue){return!isNaN(Date.parse(dateValue));}
function _processFields(vcardObj,contactObj){['org','title','bday','anniversary'].forEach(function(field){if(!vcardObj[field]){return;}
var v=vcardObj[field][0];if(!v||!Array.isArray(v.value)||v.value.length===0){return;}
var dateValue;if(field==='bday'){dateValue=v.value[0];if(_isValidDate(dateValue)){contactObj.bday=new Date(dateValue);}
return;}
if(field==='anniversary'){dateValue=v.value[0];if(_isValidDate(dateValue)){contactObj.anniversary=new Date(dateValue);}
return;}
if(!v){return;}
if(field==='title'){field='jobTitle';}
switch(typeof v){case'object':contactObj[field]=[_decodeQP(v.meta,v.value[0])];break;case'string':contactObj[field]=[v];break;}});return contactObj;}
function _processPhoto(vcardObj,contactObj,cb){if(!Array.isArray(vcardObj.photo)||vcardObj.photo.length===0||!vcardObj.photo[0]||!vcardObj.photo[0].value){return cb(contactObj);}
var photo=vcardObj.photo[0];var photoContents=photo.value.trim();if(!photoContents){return cb(contactObj);}
if(photoContents&&photo.meta&&photo.meta.encoding==='base64'){var blob=b64toBlob(photoContents,photo.meta.type);if(blob){utils.thumbnailImage(blob,function gotThumbnail(thumbnail){if(blob!==thumbnail){contactObj.photo=[blob,thumbnail];}else{contactObj.photo=[blob];}
cb(contactObj);});}else{cb(contactObj);}}
else{var callbacks={success:function(blobPic){if(blobPic){contactObj.photo=[blobPic];}
cb(contactObj);},error:function(){console.error('Error getting photo for contact');cb(contactObj);},timeout:function(){console.error('Timeout getting photo for contact');cb(contactObj);}};Rest.get(photoContents,callbacks,{responseType:'blob',operationsTimeout:REST_TIMEOUT});}}
var vcardToContact=function(vcard,cb){if(!vcard){return null;}
var obj={};_processName(vcard,obj);_processAddr(vcard,obj);_processComm(vcard,obj);_processFields(vcard,obj);_processPhoto(vcard,obj,function(contactObj){cb(utils.misc.toMozContact(contactObj));});};var VCFReader=function(contents){this.contents=contents;this.processed=0;this.finished=false;this.currentChar=0;this.numDupsMerged=0;};VCFReader.CONCURRENCY=5;VCFReader.prototype.finish=function(){this.finished=true;};VCFReader.prototype.process=function(cb){var self=this;var match=this.contents.match(/end:vcard/gi);if(!match){if(cb){cb();}
return;}
this.importedContacts=[];this.total=match.length;this.onread&&this.onread(this.total);this.ondone=function(numImported){cb(numImported,self.numDupsMerged);};LazyLoader.load(['/shared/js/simple_phone_matcher.js','/shared/js/mime_mapper.js','/shared/js/contact_photo_helper.js','/shared/js/contacts/import/utilities/misc.js','/shared/js/contacts/contacts_matcher.js','/shared/js/contacts/contacts_merger.js','/shared/js/contacts/utilities/image_thumbnail.js','/shared/js/contacts/merger_adapter.js','/shared/js/contacts/utilities/http_rest.js'],function(){this.splitLines();}.bind(this));};VCFReader.prototype.onParsed=function(err,ct){this.processed+=1;this.importedContacts.push(ct);this.onimported&&this.onimported(ct&&ct.name);if(this.finished||this.processed===this.total){this.ondone(this.importedContacts);return;}
var processed=this.processed;if(processed<this.total&&processed%VCFReader.CONCURRENCY===0){this.splitLines();}};VCFReader.prototype.post=function(contactObjects){var _onParsed=this.onParsed.bind(this);var cursor=0;var self=this;function afterSave(ct,e){_onParsed(e,ct);cursor+=1;if(cursor<contactObjects.length){saveContact(contactObjects[cursor]);}}
function saveContact(ct){if(!ct){afterSave(null,null);return;}
var contact=utils.misc.toMozContact(ct);var afterSaveFn=afterSave.bind(null,contact);var matchCbs={onmatch:function(matches){var callbacks={success:function(mergedContact){self.numDupsMerged++;afterSave(mergedContact,null);},error:afterSaveFn};contacts.adaptAndMerge(contact,matches,callbacks);},onmismatch:function(){VCFReader._save(contact,afterSaveFn);}};Matcher.match(contact,'passive',matchCbs);}
saveContact(contactObjects[cursor]);};VCFReader._save=function(item,cb){var req=navigator.mozContacts.save(utils.misc.toMozContact(item));req.onsuccess=cb;req.onerror=cb;};var reBeginCard=/begin:vcard$/i;var reEndCard=/end:vcard$/i;var reVersion=/^VERSION:([\d\.]*)/i;VCFReader.prototype.splitLines=function(){var currentLine='';var currentVersion=0;var inLabel=false;var multiline=false;var cardArray=[[]];var cardsProcessed=0;var i=this.currentChar;var callPost=this.post.bind(this);for(var l=this.contents.length;i<l;i++){this.currentChar=i;var ch=this.contents[i];if(currentVersion>=4&&ch==='"'){inLabel=!inLabel;currentLine+=ch;continue;}
if(multiline===true){if((/[\r\t\s\n]/).test(ch)){continue;}else{multiline=false;}}
var next=this.contents[i+1];if(inLabel||(ch!=='\n'&&ch!=='\r')){if(!(/^PHOTO/i).test(currentLine)&&ch==='='&&next&&next.search(/(\r|\n)/)!==-1){multiline=true;continue;}
currentLine+=ch;if(i!==l-1){continue;}}
var firstCharNextLine=this.contents[i+2];if((firstCharNextLine===WHITE_SPACE)&&(/[\r\t\s\n]/).test(next)&&(/^PHOTO/i).test(currentLine)){multiline=true;continue;}
if(next&&(next===WHITE_SPACE||next==='\t')){multiline=true;continue;}
if(reBeginCard.test(currentLine)){currentLine='';currentVersion=0;continue;}
if(reEndCard.test(currentLine)){cardsProcessed+=1;if(cardsProcessed===VCFReader.CONCURRENCY||(cardsProcessed+this.processed)===this.total){_parseEntries(cardArray,callPost);break;}
currentLine='';cardArray.push([]);continue;}
if(currentLine){var matches=reVersion.exec(currentLine);if(matches===null){cardArray[cardArray.length-1].push(currentLine);}else{currentVersion=parseFloat(matches[1]||'0');}}
currentLine='';}};VCFReader._decodeQuoted=_decodeQuoted;VCFReader.processAddr=_processAddr;VCFReader.processName=_processName;VCFReader.vcardToContact=vcardToContact;VCFReader.utils={parseDataUri:parseDataUri,b64toBlob:b64toBlob};return VCFReader;})();

View file

@ -0,0 +1,2 @@
;'use strict';var contacts=window.contacts||{};contacts.adaptAndMerge=function(incomingContact,matches,callbacks){var listIds=Object.keys(matches);var totalMatches=listIds.length;var masterContact=matches[listIds[0]].matchingContact;var matchingContacts=[];for(var j=1;j<totalMatches;j++){matchingContacts.push(matches[listIds[j]]);}
matchingContacts.push({matchingContact:incomingContact});contacts.Merger.merge(masterContact,matchingContacts,callbacks);};

View file

@ -0,0 +1,56 @@
;'use strict';(function(exports){var inSearchMode=false,searchView,searchBox,searchList,searchNoResult,searchProgress,searchTimer=null,contactNodes=null,selectableForm=null,searchableNodes=null,currentTextToSearch='',currentSearchTerms=[],prevTextToSearch='',currentSet={},searchTextCache={},canReuseSearchables=false,blurList=false,theClones={},CHUNK_SIZE=10,searchPageSize=-1,hardLimit=25,emptySearch=true,remainingPending=true,imgLoader,searchEnabled=false,source=null,navigationController=null;var init=function load(_source,defaultEnabled,navigation){searchView=document.getElementById('search-view');searchList=document.getElementById('search-list');selectableForm=document.getElementById('selectable-form');if(!_source){throw new Error('Search requires a contact source!');}
source=_source;if(typeof source.click==='function'){searchList.addEventListener('click',source.click);}
searchEnabled=!!defaultEnabled;navigationController=navigation||window.MainNavigation;};var initialized=false;var ignoreReturnKey=function ignoreReturnKey(evt){if(evt.keyCode==13){evt.preventDefault();}};var doInit=function doInit(){if(initialized){return;}
utils.listeners.add({'#cancel-search':exitSearchMode,'#search-contact':[{event:'keypress',handler:ignoreReturnKey}]});initialized=true;searchBox=document.getElementById('search-contact');var resetButton=searchBox.nextElementSibling;resetButton.addEventListener('ontouchstart'in window?'touchstart':'mousedown',function(){searchBox.value='';searchBox.focus();resetState();window.setTimeout(fillInitialSearchPage,0);});searchList.parentNode.addEventListener('touchstart',function(){if(searchableNodes&&remainingPending){addRemainingResults(searchableNodes,searchPageSize);}
blurList=true;});searchNoResult=document.getElementById('no-result');searchProgress=document.getElementById('search-progress');searchBox.addEventListener('blur',function(){window.setTimeout(onSearchBlur,0);});searchBox.addEventListener('focus',function(){blurList=false;});LazyLoader.load(['/contacts/js/fb_resolver.js','/shared/js/contacts/utilities/image_loader.js'],function(){imgLoader=new ImageLoader('#groups-list-search','li');imgLoader.setResolver(fb.resolver);});};var clearHighlights=function(node){var highlights=node.getElementsByTagName('mark');while(highlights.length){var parent=highlights[0].parentNode;while(highlights[0].firstChild){parent.insertBefore(highlights[0].firstChild,highlights[0]);}
parent.removeChild(highlights[0]);}};var highlightNode=function(node){function doHighlight(text){if(text===''||text===' '){return document.createTextNode('');}
return HtmlHelper.createHighlightHTML(text,currentSearchTerms);}
function locateHTMLTag(text){var tagLocation=[];tagLocation.push(text.indexOf('<'));tagLocation.push(text.indexOf('>',tagLocation[0])+1);tagLocation.push(text.indexOf('<',tagLocation[1]));tagLocation.push(text.indexOf('>',tagLocation[2])+1);return tagLocation;}
var textNode=node.querySelector('.contact-text bdi');if(textNode===null){return;}
var text=textNode.innerHTML;var tagPos=locateHTMLTag(text);var beforeTag=text.substring(0,tagPos[0]);var inTag=text.substring(tagPos[1],tagPos[2]);var afterTag=text.substring(tagPos[3]);var realText=[beforeTag,inTag,afterTag];if(tagPos[0]==-1){var content=doHighlight(Normalizer.unescapeHTML(text));textNode.textContent='';textNode.appendChild(content);}else{for(var i=0,len=realText.length;i<len;i++){realText[i]=doHighlight(Normalizer.unescapeHTML(realText[i]));}
var fragment=document.createDocumentFragment();var strongTag=document.createElement('strong');strongTag.appendChild(realText[1]);fragment.appendChild(realText[0]);fragment.appendChild(strongTag);fragment.appendChild(realText[2]);textNode.textContent='';textNode.appendChild(fragment);}};var updateSearchList=function updateSearchList(cb){if(!inSearchMode){if(cb){cb();}
return;}
window.setTimeout(function(){resetStateAndCache();search(cb);});};var resetStateAndCache=function resetStateAndCache(){contactNodes=null;searchTextCache={};resetState();};var exitSearchMode=function exitSearchMode(evt){if(evt){evt.preventDefault();}
searchView.classList.remove('insearchmode');if(selectableForm){selectableForm.classList.remove('insearchmode');}
if(navigationController){navigationController.back();}
window.setTimeout(function exit_search(){hideProgressResults();searchBox.value='';contactNodes=null;searchTextCache={};resetState();inSearchMode=false;},0);window.removeEventListener('input',onInput);};function resetState(){prevTextToSearch='';currentTextToSearch='';currentSearchTerms=[];searchableNodes=null;canReuseSearchables=false;currentSet={};theClones={};utils.dom.removeChildNodes(searchList);emptySearch=true;remainingPending=true;}
function addRemainingResults(nodes,from){if(remainingPending!==true){return;}
var fragment=document.createDocumentFragment();for(var i=from;i<hardLimit&&i<nodes.length;i++){var node=nodes[i].node;var clon=getClone(node);highlightNode(clon);fragment.appendChild(clon);currentSet[node.dataset.uuid]=clon;}
if(fragment.hasChildNodes()){searchList.appendChild(fragment);imgLoader.reload();}
remainingPending=false;}
function onSearchBlur(e){if(canReuseSearchables&&searchableNodes&&searchView.classList.contains('insearchmode')&&blurList){addRemainingResults(searchableNodes,searchPageSize);}
else if(emptySearch===true&&remainingPending===true){var lastNode=searchList.querySelector('li:last-child');if(lastNode){var lastNodeUid=lastNode.dataset.uuid;var startNode=source.getNextNode(source.getNodeById(lastNodeUid));fillIdentityResults(startNode,hardLimit-searchPageSize);remainingPending=false;imgLoader.reload();}}}
function fillIdentityResults(startNode,number){var fragment=document.createDocumentFragment();var contact=startNode;for(var i=0;i<number&&contact;i++){var clonedNode=getClone(contact);fragment.appendChild(clonedNode);currentSet[contact.dataset.uuid]=clonedNode;contact=source.getNextNode(contact);}
if(fragment.hasChildNodes()){searchList.appendChild(fragment);}}
function getClone(node){var id=node.dataset.uuid;var out=theClones[id];if(!out){out=source.clone(node);cacheClone(id,out);}
return out;}
function cacheClone(id,clone){theClones[id]=clone;}
function onInput(e){if(e.target.id===searchBox.id){search();}}
var enterSearchMode=function searchMode(evt){evt.preventDefault();if(!inSearchMode){window.addEventListener('input',onInput);searchView.classList.add('insearchmode');if(selectableForm){selectableForm.classList.add('insearchmode');}
doInit();fillInitialSearchPage();inSearchMode=true;emptySearch=true;if(navigationController){navigationController.go('search-view','none');}
setTimeout(function nextTick(){searchBox.focus();});}};function fillInitialSearchPage(done){hideProgressResults();var startContact=source.getFirstNode();var numToFill=searchPageSize;if(startContact&&searchPageSize<1){fillIdentityResults(startContact,1);var viewHeight=searchList.getBoundingClientRect().height;var rowHeight=searchList.children[0].getBoundingClientRect().height;searchPageSize=Math.ceil(viewHeight/rowHeight);hardLimit=~~(3.5*searchPageSize);startContact=source.getNextNode(startContact);numToFill=searchPageSize-1;}
fillIdentityResults(startContact,numToFill);if(typeof done==='function'){done();}
if(imgLoader){imgLoader.reload();}}
function doSearch(contacts,from,searchText,pattern,state){if(currentTextToSearch.localeCompare(searchText)!==0){canReuseSearchables=false;window.console.warn('**** Cancelling current search ****');return;}
var end=from+CHUNK_SIZE;currentSearchTerms=pattern.source.split(/\s+/);for(var c=from;c<end&&c<contacts.length;c++){var contact=contacts[c].node||contacts[c];var contactText=contacts[c].text||getSearchText(contacts[c]);contactText=Normalizer.unescapeHTML(contactText);if(!checkContactMatch(currentSearchTerms,contactText)){if(contact.dataset.uuid in currentSet){searchList.removeChild(currentSet[contact.dataset.uuid]);delete currentSet[contact.dataset.uuid];}}else{if(state.count===0){hideProgressResults();}
if(Object.keys(currentSet).length<searchPageSize&&!(contact.dataset.uuid in currentSet)){var clonedNode=getClone(contact);currentSet[contact.dataset.uuid]=clonedNode;searchList.appendChild(clonedNode);}
if(currentSet[contact.dataset.uuid]){clearHighlights(currentSet[contact.dataset.uuid]);highlightNode(currentSet[contact.dataset.uuid]);}
state.searchables.push({node:contact,text:contactText});state.count++;}}
if(c<contacts.length){searchTimer=window.setTimeout(function do_search(){searchTimer=null;doSearch(contacts,from+CHUNK_SIZE,searchText,pattern,state);},0);return;}else if(source.expectMoreNodes()){var delay=250;searchTimer=window.setTimeout(function do_search(){doSearch(contacts,Math.min(end,contacts.length),searchText,pattern,state);},delay);return;}else if(state.count===0){showNoResults();canReuseSearchables=false;}else{imgLoader.reload();searchableNodes=state.searchables;canReuseSearchables=true;if(blurList===true){searchTimer=window.setTimeout(function(){searchTimer=null;addRemainingResults(searchableNodes,searchPageSize);},0);}}
if(typeof state.searchDoneCb==='function'){state.searchDoneCb();}}
var enableSearch=function enableSearch(){if(searchEnabled){return;}
searchEnabled=true;invalidateCache();search();};var appendNodes=function appendNodes(nodes){if(!nodes||!nodes.length||!contactNodes){return;}
contactNodes.push.apply(contactNodes,nodes);if(!currentTextToSearch||!canReuseSearchables||!searchableNodes){return;}
for(var i=0,n=nodes.length;i<n;++i){var node=nodes[i];var nodeText=Normalizer.unescapeHTML(getSearchText(node));if(!checkContactMatch(currentSearchTerms,nodeText)){searchableNodes.push({node:node,text:nodeText});}}};var checkContactMatch=function checkContactMatch(searchTerms,text){for(var i=0,m=searchTerms.length;i<m;i++){if(!RegExp(searchTerms[i],'i').test(text)){return false;}}
return true;};var search=function performSearch(searchDoneCb){prevTextToSearch=currentTextToSearch;currentTextToSearch=Normalizer.toAscii(searchBox.value.trim());currentTextToSearch=Normalizer.escapeRegExp(currentTextToSearch);var thisSearchText=String(currentTextToSearch);if(thisSearchText.length===0){resetState();window.setTimeout(fillInitialSearchPage,0,searchDoneCb);}
else{showProgress();if(!searchEnabled){resetState();return;}
emptySearch=false;remainingPending=true;var pattern=new RegExp(thisSearchText,'i');var contactsToSearch=getContactsToSearch(thisSearchText,prevTextToSearch);var state={count:0,searchables:[],searchDoneCb:searchDoneCb};searchTimer=window.setTimeout(function do_search(){searchTimer=null;doSearch(contactsToSearch,0,thisSearchText,pattern,state);},0);}};function getSearchText(contact){var out='';var uuid=contact.dataset.uuid;if(uuid){out=searchTextCache[uuid];if(!out){out=source.getSearchText(contact);searchTextCache[uuid]=out;}}
else{window.console.error('Search: Not uuid found for the provided node');}
return out;}
var getContactsDom=function contactsDom(){if(!contactNodes){contactNodes=source.getNodes();}
return contactNodes;};var getContactsToSearch=function getContactsToSearch(newText,prevText){var out;if(canReuseSearchables&&newText.length>prevText.length&&prevText.length>0&&newText.indexOf(prevText)===0){out=searchableNodes||getContactsDom();}else{utils.dom.removeChildNodes(searchList);currentSet={};out=getContactsDom();canReuseSearchables=false;}
return out;};var isInSearchMode=function isInSearchMode(){return inSearchMode;};var invalidateCache=function s_invalidate(){if(searchTimer){window.clearTimeout(searchTimer);searchTimer=null;}
currentTextToSearch='';canReuseSearchables=false;searchableNodes=null;contactNodes=null;currentSet={};searchTextCache={};};var removeContact=function s_removeContact(id){var contact=searchList.querySelector('li[data-uuid=\"'+id+'\"]');searchList.removeChild(contact);};var selectRow=function s_selectRow(id,value){var check=searchList.querySelector('#search-view input[value="'+id+'"]');if(check){check.checked=value;}};function showProgress(){searchNoResult.classList.add('hide');searchProgress.classList.remove('hidden');}
function showNoResults(){searchNoResult.classList.remove('hide');searchProgress.classList.add('hidden');}
function hideProgressResults(){searchNoResult.classList.add('hide');searchProgress.classList.add('hidden');}
exports.Search={'init':init,'invalidateCache':invalidateCache,'appendNodes':appendNodes,'removeContact':removeContact,'search':search,'enterSearchMode':enterSearchMode,'exitSearchMode':exitSearchMode,'isInSearchMode':isInSearchMode,'enableSearch':enableSearch,'selectRow':selectRow,'updateSearchList':updateSearchList};})(window);

View file

@ -0,0 +1,2 @@
;'use strict';var utils=window.utils||{};if(!utils.dom){(function(document){var Dom=utils.dom={};Dom.removeChildNodes=function(node){while(node.hasChildNodes()){node.removeChild(node.firstChild);}};Dom.addClassToNodes=function(container,selector,clazz){var nodes=container.querySelectorAll(selector);for(var i=0,n=nodes.length;i<n;++i){nodes[i].classList.add(clazz);}};Dom.removeClassFromNodes=function(container,selector,clazz){var nodes=container.querySelectorAll(selector);for(var i=0,n=nodes.length;i<n;++i){nodes[i].classList.remove(clazz);}};Dom.updatePhoto=function(photo,dest){var background='';if(photo!=null){background='url('+URL.createObjectURL(photo)+')';}
dest.style.backgroundImage=background;dest.dataset.photoReady='true';};})(document);}

View file

@ -0,0 +1,3 @@
;'use strict';var utils=window.utils||{};if(!utils.listeners){(function(document){var Listeners=utils.listeners={};Listeners.add=function(config){try{for(var id in config){var handler=config[id];var nodes=document.querySelectorAll(id);for(var i=0;i<nodes.length;i++){var node=nodes[i];if(Array.isArray(handler)){handler.forEach(function handle(item){if(!item.hasOwnProperty('event')&&!item.hasOwnProperty('handler')){return;}
node.addEventListener(item.event,item.handler);});}else{node.addEventListener('click',handler);}}}}
catch(e){window.console.error('Error while registering listener for: ',id,e);}};})(document);}

View file

@ -0,0 +1,6 @@
;'use strict';if(!window.Rest){window.Rest=(function(){function RestRequest(xhr){var cancelled=false;this.cancel=function oncancel(){cancelled=true;window.setTimeout(xhr.abort.bind(xhr),0);};this.isCancelled=function isCancelled(){return cancelled;};}
function Rest(){}
Rest.prototype={get:function(uri,callback,pOptions){var DEFAULT_TIMEOUT=30000;var options=pOptions||{};var xhr=new XMLHttpRequest({mozSystem:true});var outReq=new RestRequest(xhr);xhr.open('GET',uri,true);var responseType=options.responseType||'json';xhr.responseType=responseType;var responseProperty=responseType==='xml'?'responseXML':'response';xhr.timeout=options.operationsTimeout||DEFAULT_TIMEOUT;if(!xhr.timeout||xhr.timeout===DEFAULT_TIMEOUT&&(parent&&parent.config&&parent.config.operationsTimeout)){xhr.timeout=parent.config.operationsTimeout;}
if(options.requestHeaders){for(var header in options.requestHeaders){xhr.setRequestHeader(header,options.requestHeaders[header]);}}
xhr.onload=function(e){if(xhr.status===200||xhr.status===400||xhr.status===0){if(callback&&typeof callback.success==='function'){setTimeout(function(){callback.success(xhr[responseProperty]);},0);}}
else{console.error('HTTP error executing GET. ',uri,' Status: ',xhr.status);if(callback&&typeof callback.error==='function'){setTimeout(function errorHandler(){callback.error({status:xhr.status});},0);}}};xhr.ontimeout=function(e){console.error('Timeout!!! while HTTP GET: ',uri);if(callback&&typeof callback.timeout==='function'){setTimeout(callback.timeout,0);}};xhr.onerror=function(e){console.error('Error while executing HTTP GET: ',uri,': ',e);if(callback&&typeof callback.error==='function'&&!outReq.isCancelled()){setTimeout(function(){callback.error(e);},0);}};xhr.send();return outReq;}};return new Rest();})();}

View file

@ -0,0 +1,25 @@
;'use strict';if(!window.ImageLoader){var ImageLoader=function ImageLoader(pContainer,pItems){var container,items,itemsSelector,lastScrollTime,scrollLatency=100,scrollTimer,itemHeight,total,imgsLoading=0,loadImage=defaultLoadImage,self=this;init(pContainer,pItems);function init(pContainer,pItems){itemsSelector=pItems;container=document.querySelector(pContainer);attachHandlers();window.addEventListener('image-loader-resume',resuming);window.addEventListener('image-loader-pause',unload);load();}
function resuming(){window.clearTimeout(scrollTimer);attachHandlers();update();}
function onUpdate(evt){evt.stopPropagation();onScroll();}
function load(){window.clearTimeout(scrollTimer);scrollTimer=null;items=container.querySelectorAll(itemsSelector);itemHeight=items[0]?items[0].offsetHeight:1;total=items.length;window.setTimeout(update,0);}
function attachHandlers(){container.addEventListener('scroll',onScroll);document.addEventListener('onupdate',onUpdate);}
function unload(){container.removeEventListener('scroll',onScroll);document.removeEventListener('onupdate',onUpdate);window.clearTimeout(scrollTimer);scrollTimer=null;}
function setResolver(pResolver){loadImage=pResolver;}
function onScroll(){if(imgsLoading>0){window.stop();imgsLoading=0;}
lastScrollTime=Date.now();if(!scrollTimer){scrollTimer=window.setTimeout(updateFromScroll,scrollLatency);}}
function updateFromScroll(){scrollTimer=null;var deltaLatency=lastScrollTime-Date.now()+scrollLatency;if(deltaLatency>0){scrollTimer=window.setTimeout(updateFromScroll,deltaLatency);}else{update();}}
function defaultLoadImage(item){var image=item.querySelector('span[data-type=img][data-src]');if(!image){image=item.querySelector('img[data-src]');if(!image){image=item.querySelector('span[data-type=img][data-group]');if(image){item.dataset.visited='true';}
return;}}
++imgsLoading;var tmp=new Image();var src=tmp.src=image.dataset.src;tmp.onload=function onload(){--imgsLoading;image.style.backgroundImage='url('+src+')';if(tmp.complete){item.dataset.visited='true';}
tmp=null;};tmp.onabort=tmp.onerror=function onerror(){--imgsLoading;item.dataset.visited='false';tmp=null;};}
function update(){if(total===0){return;}
var viewTop=container.scrollTop;var index=Math.floor(viewTop/itemHeight);var containerHeight=container.offsetHeight;for(var i=index;i>=0;i--){var item=items[i];if(item){if(item.offsetTop+itemHeight<viewTop){break;}
if(item.dataset.visited!=='true'&&item.offsetTop<=viewTop+containerHeight){loadImage(item,self);}}}
for(var j=index+1;j<total;j++){var theItem=items[j];if(!theItem){return;}
if(theItem.offsetTop>viewTop+containerHeight){return;}
if(theItem.dataset.visited!=='true'){loadImage(theItem,self);}}}
function releaseImage(item){var image=item.querySelector('span[data-type=img][data-src]');if(!image){image=item.querySelector('span[data-type=img][data-group]');if(image){item.dataset.visited='false';}
return null;}
image.style.backgroundImage='none';item.dataset.visited='false';return image;}
function destroy(){unload();window.removeEventListener('image-loader-pause',unload);window.removeEventListener('image-loader-resume',resuming);container=items=itemsSelector=lastScrollTime=scrollLatency=null;scrollTimer=itemHeight=total=imgsLoading=loadImage=null;}
this.reload=load;this.unload=unload;this.setResolver=setResolver;this.defaultLoad=defaultLoadImage;this.releaseImage=releaseImage;this.destroy=destroy;};}

View file

@ -0,0 +1,2 @@
;'use strict';var utils=window.utils||{};if(typeof utils.squareImage==='undefined'){utils.squareImage=function(blob,callback){var img=document.createElement('img');var url=URL.createObjectURL(blob);img.src=url;function cleanupImg(){img.src='';URL.revokeObjectURL(url);}
img.onerror=cleanupImg;img.onload=function onBlobLoad(){var width=img.width;var height=img.height;if(width===height){cleanupImg();callback(blob);}else{var canvas=document.createElement('canvas');var min=canvas.width=canvas.height=Math.min(width,height);var context=canvas.getContext('2d',{willReadFrequently:true});context.drawImage(img,(width-min)/2,(height-min)/2,min,min,0,0,min,min);cleanupImg();canvas.toBlob(function onCanvasToBlob(canvasBlob){context=null;canvas.width=canvas.height=0;canvas=null;callback(canvasBlob);},'image/jpeg',0.95);}};};}

View file

@ -0,0 +1,4 @@
;'use strict';var utils=window.utils||{};(function(utils){var DEPENDENCIES=['/shared/js/contacts/import/utilities/config.js','/shared/js/image_utils.js'];var dpr=window.devicePixelRatio||1;var CONFIG_FILE='/config-images.json';var DEFAULT_CONFIG={'thumbnail':{'format':'image/jpeg','size':65,'quality':1.0}};var THUMB_CONFIG=DEFAULT_CONFIG.thumbnail;if(typeof utils.thumbnailImage!=='undefined'){return;}
function getThumbsConfig(){return new Promise(function(resolve,reject){utils.config.load(CONFIG_FILE).then(function resolved(config){resolve(config);},function rejected(){resolve(DEFAULT_CONFIG);});});}
function scaleImage(blob,configData){var thumbConfig=configData&&configData.thumbnail||{};var thumbnailEdge=(thumbConfig.size||THUMB_CONFIG.size)*dpr;var format=thumbConfig.format||THUMB_CONFIG.format;var encodingQuality=thumbConfig.quality||THUMB_CONFIG.quality;return ImageUtils.resizeAndCropToCover(blob,thumbnailEdge,thumbnailEdge,format,encodingQuality);}
utils.thumbnailImage=function(blob,callback){LazyLoader.load(DEPENDENCIES,function(){getThumbsConfig().then(function(config){return scaleImage(blob,config);}).then(callback).catch(function(err){console.error('Error while converting image to thumbnail:',err.name);callback(blob);});});};})(utils);

View file

@ -0,0 +1,14 @@
;'use strict';var utils=window.utils||{};if(!utils.templates){(function(){var Templates=utils.templates={};function getTarget(element){var target=element;if(!element.tagName){target=document.querySelector(element);}
return target;}
function getTemplate(target,data){var templates=target.querySelectorAll('*[data-template]');if(templates.length===0){throw new Error('No template declared');}
if(templates.length>1&&templates.item(0).dataset.condition){throw new Error('Only one template supported in this version');}
return{template:templates.item(0),isMulti:false};}
function templateReplace(data){return function(text,property){var out;if(property.indexOf('.')===-1){out=data[property];if(Array.isArray(out)&&out.length>0){out=out[0];}}else{throw new Error('Dotted expressions not supported');}
if(typeof out==='undefined'){out=text;}
return out;};}
function add(element,data,mode,targetNode){var target=getTarget(element);var newElem;var theData=[data];if(data instanceof Array){theData=data;}
var multiTemplate=true;var template;var idx=0;theData.forEach(function(oneData){oneData._idx_=idx++;if(multiTemplate===true){var tresult=getTemplate(target,oneData);template=tresult.template;if(tresult.isMulti===false){multiTemplate=false;}}
if(template){newElem=this.render(template,oneData);target=targetNode||target;if(mode==='A'){target.appendChild(newElem);}else if(mode==='P'){if(target.firstChild){target.insertBefore(newElem,element.firstChild);}else{target.appendChild(newElem);}}}}.bind(Templates));return newElem;}
Templates.append=function(element,data,targetNode){var f=add.bind(this);return f(element,data,'A',targetNode);};Templates.prepend=function(element,data,targetNode){var f=add.bind(this);return f(element,data,'P',targetNode);};Templates.render=function(eleTemplate,data){var newElem=eleTemplate.cloneNode(true);newElem.removeAttribute('data-template');newElem.removeAttribute('data-condition');var pattern=/#(\w+[\w.]*)#/g;var inner=newElem.innerHTML;var replaceFunction=templateReplace(data);var ninner=inner.replace(pattern,replaceFunction);newElem.innerHTML=ninner;var attrs=newElem.attributes;var total=attrs.length;for(var c=0;c<total;c++){var val=attrs[c].value;var nval=val.replace(pattern,replaceFunction);newElem.setAttribute(attrs[c].name,nval);}
if(!newElem.id){if(data.id){newElem.id=data.id;}}
return newElem;};Templates.clear=function(element){var target=getTarget(element);var templates=target.querySelectorAll('*[data-template]');target.innerHTML='';var total=templates.length;for(var c=0;c<total;c++){target.appendChild(templates.item(c));}};})();}