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:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
14
assets/webapps/ftu.gaiamobile.org/shared/js/async_storage.js
Normal file
14
assets/webapps/ftu.gaiamobile.org/shared/js/async_storage.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
;;this.asyncStorage=(function(){'use strict';var DBNAME='asyncStorage';var DBVERSION=1;var STORENAME='keyvaluepairs';var db=null;function withDatabase(f){if(db){f();}else{var openreq=indexedDB.open(DBNAME,DBVERSION);openreq.onerror=function withStoreOnError(){console.error('asyncStorage: can\'t open database:',openreq.error.name);};openreq.onupgradeneeded=function withStoreOnUpgradeNeeded(){openreq.result.createObjectStore(STORENAME);};openreq.onsuccess=function withStoreOnSuccess(){db=openreq.result;f();};}}
|
||||
function withStore(type,callback,oncomplete){withDatabase(function(){var transaction=db.transaction(STORENAME,type);if(oncomplete){transaction.oncomplete=oncomplete;}
|
||||
callback(transaction.objectStore(STORENAME));});}
|
||||
function getItem(key,callback){var req;withStore('readonly',function getItemBody(store){req=store.get(key);req.onerror=function getItemOnError(){console.error('Error in asyncStorage.getItem(): ',req.error.name);};},function onComplete(){var value=req.result;if(value===undefined){value=null;}
|
||||
callback(value);});}
|
||||
function setItem(key,value,callback){withStore('readwrite',function setItemBody(store){var req=store.put(value,key);req.onerror=function setItemOnError(){console.error('Error in asyncStorage.setItem(): ',req.error.name);};},callback);}
|
||||
function removeItem(key,callback){withStore('readwrite',function removeItemBody(store){var req=store.delete(key);req.onerror=function removeItemOnError(){console.error('Error in asyncStorage.removeItem(): ',req.error.name);};},callback);}
|
||||
function clear(callback){withStore('readwrite',function clearBody(store){var req=store.clear();req.onerror=function clearOnError(){console.error('Error in asyncStorage.clear(): ',req.error.name);};},callback);}
|
||||
function length(callback){var req;withStore('readonly',function lengthBody(store){req=store.count();req.onerror=function lengthOnError(){console.error('Error in asyncStorage.length(): ',req.error.name);};},function onComplete(){callback(req.result);});}
|
||||
function key(n,callback){if(n<0){callback(null);return;}
|
||||
var req;withStore('readonly',function keyBody(store){var advanced=false;req=store.openCursor();req.onsuccess=function keyOnSuccess(){var cursor=req.result;if(!cursor){return;}
|
||||
if(n===0||advanced){return;}
|
||||
advanced=true;cursor.advance(n);};req.onerror=function keyOnError(){console.error('Error in asyncStorage.key(): ',req.error.name);};},function onComplete(){var cursor=req.result;callback(cursor?cursor.key:null);});}
|
||||
return{getItem:getItem,setItem:setItem,removeItem:removeItem,clear:clear,length:length,key:key};}());
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
;'use strict';var Basket={DATASTORE_NAME:'Basket_Newsletter',basketUrl:'https://basket.mozilla.org/news/subscribe/',newsletterId:'firefox-os',callback:null,xhr:null,errors:{NETWORK_FAILURE:1,INVALID_EMAIL:2,UNKNOWN_EMAIL:3,UNKNOWN_TOKEN:4,USAGE_ERROR:5,EMAIL_PROVIDER_AUTH_FAILURE:6,AUTH_ERROR:7,SSL_REQUIRED:8,INVALID_NEWSLETTER:9,INVALID_LANGUAGE:10,EMAIL_NOT_CHANGED:11,CHANGE_REQUEST_NOT_FOUND:12,UNKNOWN_ERROR:99},responseHandler:function b_responseHandler(){if(this.xhr.readyState===4){if(this.xhr.status===200){if(typeof this.callback==='function'){this.callback(null,JSON.parse(this.xhr.responseText));}}else{if(typeof this.callback==='function'){if(this.xhr.responseText){this.callback(JSON.parse(this.xhr.responseText));}else{this.callback('Unknown error');}}}}},timeoutHandler:function b_timeoutHandler(){this.callback('Timeout');},send:function b_send(email,callback){var self=this;var params='email='+encodeURIComponent(email)+'&newsletters='+self.newsletterId;self.callback=callback;self.xhr=new XMLHttpRequest({mozSystem:true});self.xhr.onreadystatechange=self.responseHandler.bind(self);self.xhr.open('POST',self.basketUrl,true);self.xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');self.xhr.setRequestHeader('Connection','close');self.xhr.timeout=5000;self.xhr.ontimeout=self.timeoutHandler.bind(self);self.getLanguage(function do_send(language){if(language){params+='&lang='+language;}
|
||||
self.xhr.setRequestHeader('Content-length',params.length);self.xhr.send(params);});},getDataStore:function(){var self=this;return new Promise(function(resolve,reject){navigator.getDataStores(self.DATASTORE_NAME).then(function(stores){if(!stores.length){reject(new Error('DataStore not loaded'));}else{var dataStore=stores[0];if(dataStore.owner==='app://ftu.gaiamobile.org/manifest.webapp'){resolve(dataStore);}else{reject(new Error('Wrong DataStore'));}}});});},store:function b_store(email,callback){this.getDataStore().then(function loaded(store){store.add({'newsletter_email':email}).then(function(id){if(callback){callback(false,id);}});}).catch(function error(err){console.error('Something went wrong: '+err);callback(err);});},getLanguage:function b_getLanguage(callback){var settings=window.navigator.mozSettings;if(!settings||!settings.createLock){callback();return;}
|
||||
var s_name='language.current';var req=settings.createLock().get(s_name);req.onsuccess=function _onsuccess(){callback(req.result[s_name]);};req.onerror=function _onerror(){console.error('Error getting setting: '+s_name);callback();};}};
|
||||
124
assets/webapps/ftu.gaiamobile.org/shared/js/binary_search.js
Normal file
124
assets/webapps/ftu.gaiamobile.org/shared/js/binary_search.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
'use strict';
|
||||
/* exported utils */
|
||||
var utils = window.utils || {};
|
||||
|
||||
/**
|
||||
* This function performs a binary search over an already sorted array
|
||||
* target is the target item to search for
|
||||
* array is the sorted array
|
||||
* options is an optional object which may contain
|
||||
* the start and end position (from, to)
|
||||
* an optional arrayField which indicates the object property that contains the
|
||||
* comparable item and transform and compare functions
|
||||
*
|
||||
* Returns an array with the positions on which the target item was found
|
||||
*
|
||||
*/
|
||||
utils.binarySearch = function(target, array, options) {
|
||||
var arrayField = options.arrayField,
|
||||
transformFunction = options.transformFunction,
|
||||
compareFunction = options.compareFunction;
|
||||
|
||||
// Obtains the comparable item by transforming if necessary
|
||||
function getItem(array, index) {
|
||||
var item = array[index];
|
||||
if (arrayField) {
|
||||
item = item[arrayField];
|
||||
if (typeof transformFunction === 'function') {
|
||||
item = transformFunction(item);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
// Compares the target with an array item
|
||||
function compare(target, item) {
|
||||
var out;
|
||||
if (typeof compareFunction === 'function') {
|
||||
out = compareFunction(target, item);
|
||||
}
|
||||
else {
|
||||
if (typeof target === 'string') {
|
||||
out = target.localeCompare(item);
|
||||
}
|
||||
else {
|
||||
out = target.toString().localeCompare(item);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
var from = options.from;
|
||||
if (typeof from === 'undefined') {
|
||||
from = 0;
|
||||
}
|
||||
var to = options.to;
|
||||
if (typeof to === 'undefined') {
|
||||
to = array.length - 1;
|
||||
}
|
||||
|
||||
if (to < from) {
|
||||
// Not found
|
||||
return [];
|
||||
}
|
||||
|
||||
var middleIndex = Math.floor((to - from) / 2);
|
||||
var item = getItem(array, from + middleIndex);
|
||||
|
||||
var compareResult = compare(target, item);
|
||||
|
||||
if (compareResult === 0) {
|
||||
// Once a result is found let's iterate in both directions to get the rest
|
||||
// Just in case there are more than one result
|
||||
var results = [from + middleIndex];
|
||||
|
||||
var next = from + middleIndex + 1;
|
||||
var finish = false;
|
||||
while (next <= (array.length - 1) && !finish) {
|
||||
item = getItem(array, next);
|
||||
|
||||
if (compare(target, item) === 0) {
|
||||
results.push(next);
|
||||
}
|
||||
else {
|
||||
finish = true;
|
||||
}
|
||||
next++;
|
||||
}
|
||||
|
||||
finish = false;
|
||||
next = from + middleIndex - 1;
|
||||
|
||||
while (next >= 0 && !finish) {
|
||||
item = getItem(array, next);
|
||||
|
||||
if (compare(target, item) === 0) {
|
||||
results.push(next);
|
||||
}
|
||||
else {
|
||||
finish = true;
|
||||
}
|
||||
next--;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
else if (compareResult < 0) {
|
||||
return utils.binarySearch(target, array, {
|
||||
from: from,
|
||||
to: to - middleIndex - 1,
|
||||
arrayField: arrayField,
|
||||
transformFunction: transformFunction,
|
||||
compareFunction: compareFunction
|
||||
});
|
||||
}
|
||||
else {
|
||||
return utils.binarySearch(target, array, {
|
||||
from: from + middleIndex + 1,
|
||||
to: to,
|
||||
arrayField: arrayField,
|
||||
transformFunction: transformFunction,
|
||||
compareFunction: compareFunction
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
;;(function(exports){'use strict';exports.ComponentUtils={style:function(baseUrl){if(window.VulpesCompat){window.VulpesCompat.componentStyle(this,baseUrl).catch(console.error);return;}var style=document.createElement('style');var url=baseUrl+'style.css';var self=this;style.setAttribute('scoped','');style.innerHTML='@import url('+url+');';this.appendChild(style);this.style.visibility='hidden';style.addEventListener('load',function(){if(self.shadowRoot){self.shadowRoot.appendChild(style.cloneNode(true));}
|
||||
self.style.visibility='';});}};}(window));
|
||||
21
assets/webapps/ftu.gaiamobile.org/shared/js/confirm.js
Normal file
21
assets/webapps/ftu.gaiamobile.org/shared/js/confirm.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
;'use strict';var ConfirmDialog=(function(){var isAnimationPlaying=false;var isShown=false;var doc=document;try{if(!document.getElementById("confirmation-message"))doc=parent.document;}catch(_){}var screen=doc.getElementById('confirmation-message');var titleNode=screen.querySelector('h1');var messageNode=screen.querySelector('p');var action1Node=screen.querySelector('button:nth-of-type(1)');var action1Callback;var action2Node=screen.querySelector('button:nth-of-type(2)');var action2Callback;var oldzIndex;var callBackAndPreventDefault=function callBackAndPreventDefault(ev){if(ev.target===action1Node){action1Callback();ev.preventDefault();return false;}else if(ev.target===action2Node){action2Callback();ev.preventDefault();return false;}};var _hide=function _hide(){if(!isShown){return;}
|
||||
titleNode.removeAttribute('data-l10n-id');titleNode.className='';messageNode.removeAttribute('data-l10n-id');messageNode.className='';action1Node.removeAttribute('data-l10n-id');action1Node.onclick=null;action2Node.removeAttribute('data-l10n-id');action2Node.onclick=null;screen.classList.remove('fade-in');screen.classList.add('fade-out');isAnimationPlaying=true;window.dispatchEvent(new CustomEvent('confirmdialoghiding'));screen.addEventListener('animationend',function cd_fadeOut(ev){action1Node.className='';action2Node.className='';isAnimationPlaying=false;screen.removeEventListener('animationend',cd_fadeOut);screen.classList.add('no-opacity');screen.classList.add('hide');isShown=false;});};var _show=function _show(title,msg,action1,action2,options){var setL10nAttributes=function(element,options){if('string'===typeof options){document.l10n.setAttributes(element,options);}
|
||||
if(options.id){document.l10n.setAttributes(element,options.id,options.args);}};if(options&&options.zIndex){oldzIndex=screen.style.zIndex;screen.style.zIndex=options.zIndex;}
|
||||
else{oldzIndex=null;}
|
||||
if(title){setL10nAttributes(titleNode,title);}else{titleNode.classList.add('hide');}
|
||||
if(msg){setL10nAttributes(messageNode,msg);}else{messageNode.classList.add('hide');}
|
||||
if(action1){if(action1.title){setL10nAttributes(action1Node,action1.title);}
|
||||
if(action1.isDanger){action1Node.classList.add('danger');}
|
||||
if(action1.isRecommend){action1Node.classList.add('recommend');}
|
||||
if(action1.callback){action1Callback=action1.callback;action1Node.onclick=callBackAndPreventDefault;}
|
||||
if(!action2){action2Node.classList.add('hide');action1Node.classList.add('full');}}
|
||||
if(action2){if(action2.title){setL10nAttributes(action2Node,action2.title);}
|
||||
if(action2.isDanger){action2Node.classList.add('danger');}
|
||||
if(action2.isRecommend){action2Node.classList.add('recommend');}
|
||||
if(action2.callback){action2Callback=action2.callback;action2Node.onclick=callBackAndPreventDefault;}
|
||||
if(!action1){action1Node.classList.add('hide');action2Node.classList.add('full');}}
|
||||
if(isShown){return;}
|
||||
screen.classList.remove('hide');screen.classList.remove('fade-out');screen.classList.add('fade-in');isAnimationPlaying=true;window.dispatchEvent(new CustomEvent('confirmdialogshowing'));screen.addEventListener('animationend',function cd_fadeIn(ev){isAnimationPlaying=false;screen.removeEventListener('animationend',cd_fadeIn);screen.classList.remove('no-opacity');isShown=true;});};return{hide:function dialog_hide(){if(oldzIndex){screen.style.zIndex=oldzIndex;oldzIndex=null;}
|
||||
if(!isAnimationPlaying){_hide();return;}
|
||||
screen.addEventListener('animationend',function cd_hideWhenFinished(ev){screen.removeEventListener('animationend',cd_hideWhenFinished);_hide();});},show:function dialog_show(title,msg,action1,action2,options){if(!isAnimationPlaying){_show(title,msg,action1,action2,options);return;}
|
||||
screen.addEventListener('animationend',function cd_showWhenFinished(ev){screen.removeEventListener('animationend',cd_showWhenFinished);_show(title,msg,action1,action2,options);});}};}());
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
;(function(exports){'use strict';function getThumbnail(contact){return getOnePhoto(contact,'begin');}
|
||||
function getFullResolution(contact){return getOnePhoto(contact,'end');}
|
||||
function getOnePhoto(contact,position){if(!contact||!contact.photo||!contact.photo.length){return null;}
|
||||
if(contact.photo.length===1){return contact.photo[0];}
|
||||
var photos=contact.photo;var category=contact.category;if(Array.isArray(category)&&category.indexOf('fb_linked')!==-1){if(photos.length>=4){return photos[(position=='begin')?1:0];}
|
||||
return photos[0];}
|
||||
photos=photosBySize(contact);var index=(position=='begin')?0:photos.length-1;return photos[index];}
|
||||
function photosBySize(contact){var photos=contact.photo.slice(0);photos.sort(function(p1,p2){if(size(p1)<size(p2)){return-1;}
|
||||
if(size(p1)>size(p2)){return 1;}
|
||||
return 0;});return photos;}
|
||||
function size(photo){if(typeof photo=='string'){return photo.length;}
|
||||
return photo.size;}
|
||||
exports.ContactPhotoHelper={getThumbnail:getThumbnail,getFullResolution:getFullResolution};})(window);
|
||||
|
|
@ -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();}}};
|
||||
|
|
@ -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));
|
||||
|
|
@ -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};})();
|
||||
|
|
@ -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&¶ms.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);}
|
||||
|
|
@ -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();}}};})();
|
||||
|
|
@ -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;
|
||||
})();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
});
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -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();
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
|
@ -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};})();
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -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);
|
||||
|
|
@ -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};})();}
|
||||
|
|
@ -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);}
|
||||
|
|
@ -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();
|
||||
})();
|
||||
}
|
||||
|
|
@ -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);}});};})();}
|
||||
|
|
@ -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();}}};
|
||||
|
|
@ -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();};}}
|
||||
|
|
@ -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);};}
|
||||
|
|
@ -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();});};})();
|
||||
|
|
@ -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;}};}
|
||||
|
|
@ -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};})();
|
||||
|
|
@ -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;})();
|
||||
|
|
@ -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);};
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);}
|
||||
|
|
@ -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);}
|
||||
|
|
@ -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();})();}
|
||||
|
|
@ -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;};}
|
||||
|
|
@ -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);}};};}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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));}};})();}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
;(function(){'use strict';if(window.navigator.mozHour12||window.navigator.hour12){return;}
|
||||
window.navigator.mozHour12=undefined;var _setMozHour12=function(result){if(result===null){result=undefined;}
|
||||
if(window.navigator.mozHour12!==result){window.navigator.mozHour12=result;window.dispatchEvent(new CustomEvent('timeformatchange'));}};var _hour12Handler=function(event){_setMozHour12(event.settingValue);};var _kLocaleTime='locale.hour12';var req=window.navigator.mozSettings.createLock().get(_kLocaleTime);req.onsuccess=function(){_setMozHour12(req.result[_kLocaleTime]);};window.navigator.mozSettings.addObserver(_kLocaleTime,_hour12Handler);})();
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
;'use strict';function eventSafety(obj,event,callback,timeout){if(typeof callback==='number'){timeout=callback;callback=null;}
|
||||
if(!timeout){throw new Error('You must pass a valid timeout value to `eventSafety`.');}
|
||||
return new Promise((resolve)=>{var finishTimeout;function done(e){if(e&&e.target!==obj&&(e.type==='transitionend'||e.type==='animationend')){return;}
|
||||
clearTimeout(finishTimeout);obj.removeEventListener(event,done);if(callback){resolve(callback.apply(this,arguments));}else{resolve(e);}}
|
||||
finishTimeout=setTimeout(done,timeout);obj.addEventListener(event,done);});}
|
||||
423
assets/webapps/ftu.gaiamobile.org/shared/js/fb/fb_data_reader.js
Normal file
423
assets/webapps/ftu.gaiamobile.org/shared/js/fb/fb_data_reader.js
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
'use strict';
|
||||
/* global LazyLoader, TelIndexer */
|
||||
|
||||
// Reader Module for FB Data in a Datastore
|
||||
// To use this library you need to include 'shared/js/fb/fb_request.js'
|
||||
// WARNING: This file lazy loads 'shared/js/fb/fb_tel_index.js'
|
||||
|
||||
var fb = self.fb || {};
|
||||
self.fb = fb;
|
||||
|
||||
(function() {
|
||||
var contacts = fb.contacts || {};
|
||||
fb.contacts = contacts;
|
||||
|
||||
var datastore;
|
||||
// Datastore name declared on the manifest.webapp
|
||||
var DATASTORE_NAME = 'Gaia_Facebook_Friends';
|
||||
// Record Id for the index
|
||||
var INDEX_ID = 1;
|
||||
|
||||
// Indicates the initialization state
|
||||
var readyState = 'notInitialized';
|
||||
// Custom event for notifying initializations
|
||||
var INITIALIZE_EVENT = 'fb_ds_init';
|
||||
|
||||
// Last known revision Id. Used to determine whether the index has to
|
||||
// be reloaded or not
|
||||
var revisionId;
|
||||
|
||||
// Creates the internal Object in the datastore that will act as an index
|
||||
function createIndex() {
|
||||
return {
|
||||
// By tel number and all its possible variants
|
||||
// (We are not supporting dups right now)
|
||||
byTel: Object.create(null),
|
||||
// Prefix tree for enabling searching by partial tel numbers
|
||||
treeTel: []
|
||||
};
|
||||
}
|
||||
|
||||
// The index we need to keep the correspondance between FB Friends and
|
||||
// datastore Ids
|
||||
var index;
|
||||
|
||||
// Ensures a proper error object is returned
|
||||
function safeError(err) {
|
||||
if (err && err.name) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'UnknownError'
|
||||
};
|
||||
}
|
||||
|
||||
function notifyOpenSuccess(cb) {
|
||||
readyState = 'initialized';
|
||||
if (typeof cb === 'function') {
|
||||
window.setTimeout(cb, 0);
|
||||
}
|
||||
var ev = new CustomEvent(INITIALIZE_EVENT);
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function initError(outRequest, error) {
|
||||
outRequest.failed(error);
|
||||
}
|
||||
|
||||
function handleInitError(err, errorCb) {
|
||||
readyState = 'error';
|
||||
contacts.error = err;
|
||||
|
||||
if (typeof errorCb === 'function') {
|
||||
window.setTimeout(function() {
|
||||
errorCb(err);
|
||||
}, 0);
|
||||
}
|
||||
// We resume other callers state
|
||||
document.dispatchEvent(new CustomEvent(INITIALIZE_EVENT));
|
||||
}
|
||||
|
||||
|
||||
// Creates a default handler for errors
|
||||
function defaultError(request) {
|
||||
return defaultErrorCb.bind(null, request);
|
||||
}
|
||||
|
||||
// Creates a default handler for success
|
||||
function defaultErrorCb(request, error) {
|
||||
request.failed(error);
|
||||
}
|
||||
|
||||
function setIndex(obj) {
|
||||
index = (obj || createIndex());
|
||||
}
|
||||
|
||||
function successGet(outReq, data) {
|
||||
outReq.done(data);
|
||||
}
|
||||
|
||||
function errorGet(outReq, uid, err) {
|
||||
window.console.error('Error while getting object for UID: ', uid);
|
||||
outReq.failed(err.name);
|
||||
}
|
||||
|
||||
Object.defineProperty(contacts, 'datastore', {
|
||||
get: function getDataStore() { return datastore; },
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
Object.defineProperty(contacts, 'dsIndex', {
|
||||
get: function getIndex() { return index; },
|
||||
set: setIndex,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
/**
|
||||
* Allows to obtain the FB friend information by UID
|
||||
*
|
||||
*
|
||||
*/
|
||||
contacts.get = function(uid) {
|
||||
var outRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function get() {
|
||||
contacts.init(function() {
|
||||
doGet(uid, outRequest);
|
||||
}, function(err) {
|
||||
initError(outRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return outRequest;
|
||||
};
|
||||
|
||||
function doGet(uid, outRequest) {
|
||||
var successCb = successGet.bind(null, outRequest);
|
||||
var errorCb = errorGet.bind(null, outRequest, uid);
|
||||
|
||||
datastore.get(uid).then(successCb, errorCb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to get FB information by tel number (short or long)
|
||||
* The tel number must be complete
|
||||
*
|
||||
*/
|
||||
contacts.getByPhone = function(tel) {
|
||||
var outRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function get_by_phone() {
|
||||
contacts.init(function get_by_phone() {
|
||||
doGetByPhone(tel, outRequest);
|
||||
},
|
||||
function(err) {
|
||||
initError(outRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return outRequest;
|
||||
};
|
||||
|
||||
function doGetByPhone(tel, outRequest) {
|
||||
var dsId;
|
||||
var normalizedNumber = navigator.mozPhoneNumberService.normalize(tel);
|
||||
if (datastore.revisionId !== revisionId) {
|
||||
window.console.info('Datastore revision id has changed!');
|
||||
// Refreshing the index just in case
|
||||
datastore.get(INDEX_ID).then(function success(obj) {
|
||||
setIndex(obj);
|
||||
revisionId = datastore.revisionId;
|
||||
dsId = index.byTel[normalizedNumber];
|
||||
|
||||
if (typeof dsId !== 'undefined') {
|
||||
datastore.get(dsId).then(function success(friend) {
|
||||
outRequest.done(friend);
|
||||
}, defaultError(outRequest));
|
||||
}
|
||||
else {
|
||||
outRequest.done(null);
|
||||
}
|
||||
}, function(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('The index cannot be refreshed: ', err.name);
|
||||
outRequest.failed(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
dsId = index.byTel[normalizedNumber];
|
||||
if (typeof dsId !== 'undefined') {
|
||||
datastore.get(dsId).then(function success(friend) {
|
||||
outRequest.done(friend);
|
||||
}, defaultError(outRequest));
|
||||
}
|
||||
else {
|
||||
outRequest.done(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to search for friends by different criteria.
|
||||
* Numbers can be partial and partial matchings will be found
|
||||
*
|
||||
* by === tel : By Tel number
|
||||
*
|
||||
*/
|
||||
contacts.search = function(by, number) {
|
||||
var outRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function do_search() {
|
||||
contacts.init(function do_search_init() {
|
||||
if (by === 'phone') {
|
||||
doSearchByPhone(number, outRequest);
|
||||
}
|
||||
},
|
||||
function(err) {
|
||||
initError(outRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return outRequest;
|
||||
};
|
||||
|
||||
|
||||
function doSearchByPhone(number, outRequest) {
|
||||
var normalizedNumber = navigator.mozPhoneNumberService.normalize(number);
|
||||
LazyLoader.load(['/shared/js/fb/fb_tel_index.js',
|
||||
'/shared/js/binary_search.js'], function() {
|
||||
var toSearchNumber = normalizedNumber;
|
||||
// TODO: Temporal way of searching for international numbers
|
||||
// A follow-up is needed by using PhoneNumber.js exposed to Gaia
|
||||
if (number.charAt(0) === '+') {
|
||||
toSearchNumber = number.substring(1);
|
||||
}
|
||||
if (datastore.revisionId !== revisionId) {
|
||||
window.console.info('Datastore revision id has changed!');
|
||||
// Refreshing the index
|
||||
datastore.get(INDEX_ID).then(function success(obj) {
|
||||
|
||||
setIndex(obj);
|
||||
revisionId = datastore.revisionId;
|
||||
var results = TelIndexer.search(index.treeTel, toSearchNumber);
|
||||
var out = null;
|
||||
if (results.length > 0) {
|
||||
out = datastore.get.apply(datastore, results);
|
||||
}
|
||||
return out;
|
||||
},function(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('The index cannot be refreshed: ', err.name);
|
||||
outRequest.failed(err);
|
||||
}).then(function success(objList) {
|
||||
if (objList && !Array.isArray(objList)) {
|
||||
objList = [objList];
|
||||
}
|
||||
else {
|
||||
objList = [];
|
||||
}
|
||||
outRequest.done(objList);
|
||||
}, function error(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('Error while retrieving result data: ',
|
||||
err.name);
|
||||
outRequest.failed(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
var results = TelIndexer.search(index.treeTel, toSearchNumber);
|
||||
if (results.length > 0) {
|
||||
datastore.get.apply(datastore, results).then(
|
||||
function success(objList) {
|
||||
if (!Array.isArray(objList)) {
|
||||
objList = [objList];
|
||||
}
|
||||
outRequest.done(objList);
|
||||
}, function error(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('Error while retrieving result data: ',
|
||||
err.name);
|
||||
outRequest.failed(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
outRequest.done(results);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the index data
|
||||
*
|
||||
*/
|
||||
contacts.refresh = function() {
|
||||
var outRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function clear() {
|
||||
contacts.init(function() {
|
||||
doRefresh(outRequest);
|
||||
},
|
||||
function(err) {
|
||||
initError(outRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return outRequest;
|
||||
};
|
||||
|
||||
function doRefresh(outRequest) {
|
||||
datastore.get(INDEX_ID).then(function success(obj) {
|
||||
setIndex(obj);
|
||||
outRequest.done();
|
||||
}, defaultError(outRequest));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to obtain how many records contain he FB Database
|
||||
*
|
||||
*/
|
||||
contacts.getLength = function() {
|
||||
var retRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function() {
|
||||
contacts.init(function get_all() {
|
||||
doGetLength(retRequest);
|
||||
},
|
||||
function(err) {
|
||||
initError(retRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return retRequest;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows to return the total number of records in the DataStore (minus 1)
|
||||
* That's because the index object also counts
|
||||
*
|
||||
*/
|
||||
function doGetLength(outRequest) {
|
||||
datastore.getLength().then(function success(length) {
|
||||
outRequest.done(length - 1);
|
||||
},
|
||||
function error(err) {
|
||||
outRequest.failed(safeError(err));
|
||||
});
|
||||
}
|
||||
|
||||
// Needed only for testing purposes
|
||||
contacts.restart = function() {
|
||||
readyState = 'notInitialized';
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialization function
|
||||
*
|
||||
*/
|
||||
contacts.init = function(cb, errorCb) {
|
||||
if (!navigator.getDataStores) {
|
||||
handleInitError({ name: 'DatastoreNotFound' }, errorCb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (readyState === 'initialized') {
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
|
||||
if (readyState === 'initializing') {
|
||||
document.addEventListener(INITIALIZE_EVENT, function oninitalized() {
|
||||
cb();
|
||||
document.removeEventListener(INITIALIZE_EVENT, oninitalized);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
readyState = 'initializing';
|
||||
|
||||
navigator.getDataStores(DATASTORE_NAME).then(function success(ds) {
|
||||
if (ds.length < 1) {
|
||||
handleInitError({ name: 'DatastoreNotFound' }, errorCb);
|
||||
return;
|
||||
}
|
||||
|
||||
datastore = ds[0];
|
||||
// Checking the length as the index should be there
|
||||
datastore.getLength().then(function(length) {
|
||||
if (length === 0 && datastore.readOnly === false) {
|
||||
window.console.info('Adding index as length is 0');
|
||||
setIndex(createIndex());
|
||||
return datastore.add(index);
|
||||
}
|
||||
else if (length > 0) {
|
||||
return datastore.get(INDEX_ID);
|
||||
}
|
||||
else if (datastore.readOnly === true) {
|
||||
window.console.warn('The datastore is empty and readonly');
|
||||
}
|
||||
return null;
|
||||
}).then(function add_index_success(v) {
|
||||
// Index is created in order not to cause errors
|
||||
if (typeof v !== 'number') {
|
||||
setIndex(v);
|
||||
}
|
||||
revisionId = datastore.revisionId;
|
||||
notifyOpenSuccess(cb);
|
||||
}, function add_index_error(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('Error while setting the index: ', err.name);
|
||||
handleInitError(err, errorCb);
|
||||
});
|
||||
}, function error(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('FB: Error while opening the DataStore: ',
|
||||
err.name);
|
||||
handleInitError(err, errorCb);
|
||||
});
|
||||
};
|
||||
})();
|
||||
39
assets/webapps/ftu.gaiamobile.org/shared/js/fb/fb_request.js
Normal file
39
assets/webapps/ftu.gaiamobile.org/shared/js/fb/fb_request.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
var fb = self.fb || {};
|
||||
self.fb = fb;
|
||||
|
||||
// Only will be executed in the case of not loading fb.utils previously
|
||||
// i.e. dialer and call log FB integration
|
||||
fb.utils = self.fb.utils || {};
|
||||
self.fb.utils = fb.utils;
|
||||
|
||||
if (typeof fb.utils.Request !== 'function') {
|
||||
/**
|
||||
* Request auxiliary object to support asynchronous calls
|
||||
*
|
||||
*/
|
||||
fb.utils.Request = function() {
|
||||
this.done = function(result) {
|
||||
this.result = result;
|
||||
if (typeof this.onsuccess === 'function') {
|
||||
var ev = {};
|
||||
ev.target = this;
|
||||
window.setTimeout(function() {
|
||||
this.onsuccess(ev);
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
|
||||
this.failed = function(error) {
|
||||
this.error = error;
|
||||
if (typeof this.onerror === 'function') {
|
||||
var ev = {};
|
||||
ev.target = this;
|
||||
window.setTimeout(function() {
|
||||
this.onerror(ev);
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
279
assets/webapps/ftu.gaiamobile.org/shared/js/fb/fb_tel_index.js
Normal file
279
assets/webapps/ftu.gaiamobile.org/shared/js/fb/fb_tel_index.js
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
'use strict';
|
||||
|
||||
/* globals utils */
|
||||
/* exported TelIndexer */
|
||||
|
||||
// This object implements a prefix tree (aka trie) for FB tel numbers
|
||||
|
||||
function TrieNode(number) {
|
||||
this.keys = Object.create(null);
|
||||
this.leaves = null;
|
||||
this.value = number;
|
||||
}
|
||||
|
||||
var TelIndexer = {
|
||||
_MIN_TEL_LENGTH: 3,
|
||||
_INSERT_MODE: 'insert',
|
||||
|
||||
_indexOf: function(array, value, byPrefix) {
|
||||
var out = -1;
|
||||
|
||||
if (!Array.isArray(array)) {
|
||||
return out;
|
||||
}
|
||||
|
||||
for (var j = 0, l = array.length; j < l; j++) {
|
||||
if (array[j].value === value || (byPrefix &&
|
||||
array[j].value.startsWith(value))) {
|
||||
out = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
_getStringVariants: function(number) {
|
||||
var out = [];
|
||||
|
||||
var limit = number.length - this._MIN_TEL_LENGTH + 1;
|
||||
for (var j = 0; j < limit; j++) {
|
||||
out.push(number.substr(j));
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
// Allows to index the number passed as parameter
|
||||
index: function(tree, number, dsId) {
|
||||
var self = this;
|
||||
var variants = this._getStringVariants(number);
|
||||
|
||||
variants.forEach(function(aVariant) {
|
||||
self._insert(tree, aVariant, dsId);
|
||||
});
|
||||
},
|
||||
|
||||
// Inserts a substring in a node. dsId is the object id (uid) associated
|
||||
_insertInNode: function(node, str, dsId) {
|
||||
if (str.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStr = str;
|
||||
var insertPointFound = false;
|
||||
var insertObj;
|
||||
var pointerOriginal = str.length;
|
||||
while (!insertPointFound && currentStr.length > 0) {
|
||||
var inextObj = this._indexOf(node.leaves, currentStr, 'prefix');
|
||||
if (inextObj !== -1) {
|
||||
insertPointFound = true;
|
||||
insertObj = node.leaves[inextObj];
|
||||
}
|
||||
else {
|
||||
currentStr = currentStr.substring(0, currentStr.length - 1);
|
||||
}
|
||||
pointerOriginal--;
|
||||
}
|
||||
|
||||
if (insertPointFound) {
|
||||
if (insertObj.value === str) {
|
||||
// Exact insertion point
|
||||
insertObj.keys[dsId] = true;
|
||||
}
|
||||
else {
|
||||
var keys = Object.keys(insertObj.keys);
|
||||
// Let's check what's the gap
|
||||
var newString = insertObj.value.substring(0, currentStr.length);
|
||||
var restString = insertObj.value.substring(currentStr.length);
|
||||
|
||||
insertObj.value = newString;
|
||||
|
||||
if (restString.length > 0) {
|
||||
var newNode = new TrieNode(restString);
|
||||
for (var j = 0; j < keys.length; j++) {
|
||||
newNode.keys[keys[j]] = true;
|
||||
}
|
||||
if (Array.isArray(insertObj.leaves)) {
|
||||
newNode.leaves = [];
|
||||
insertObj.leaves.forEach(function(aLeave) {
|
||||
newNode.leaves.push(aLeave);
|
||||
});
|
||||
}
|
||||
insertObj.leaves = [];
|
||||
insertObj.leaves.push(newNode);
|
||||
}
|
||||
insertObj.keys[dsId] = true;
|
||||
|
||||
var restOriginal = str.substring(pointerOriginal + 1);
|
||||
// And the rest of the original string is inserted in the insertObj
|
||||
this._insertInNode(insertObj, restOriginal, dsId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var nextObj = new TrieNode(str);
|
||||
nextObj.keys[dsId] = true;
|
||||
node.leaves = node.leaves || [];
|
||||
node.leaves.push(nextObj);
|
||||
}
|
||||
},
|
||||
|
||||
// Compare function used for binary search
|
||||
_compare: function(a, b) {
|
||||
return a.localeCompare(b);
|
||||
},
|
||||
|
||||
// Find the first level node (corresponding to the 3 first numbers)
|
||||
_getFirstLevelNode: function(tree, str, mode) {
|
||||
var out = null;
|
||||
|
||||
var index;
|
||||
if (mode === this._INSERT_MODE) {
|
||||
index = this._indexOf(tree, str);
|
||||
if (index !== -1) {
|
||||
out = tree[index];
|
||||
}
|
||||
}
|
||||
else {
|
||||
index = utils.binarySearch(str, tree, {
|
||||
compareFunction: this._compare,
|
||||
arrayField: 'value'
|
||||
});
|
||||
|
||||
if (index.length > 0) {
|
||||
out = tree[index[0]];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
// Orders the tree enabling binary search on the first levels
|
||||
orderTree: function(tree) {
|
||||
tree.sort(function(a, b) {
|
||||
return a.value.localeCompare(b.value);
|
||||
});
|
||||
},
|
||||
|
||||
// Inserts a number on the tree
|
||||
_insert: function(tree, str, dsId) {
|
||||
var firstThreeStr = str.substring(0, this._MIN_TEL_LENGTH);
|
||||
var rootObj = this._getFirstLevelNode(tree, firstThreeStr,
|
||||
this._INSERT_MODE);
|
||||
if (!rootObj) {
|
||||
rootObj = new TrieNode(firstThreeStr);
|
||||
tree.push(rootObj);
|
||||
}
|
||||
rootObj.keys[dsId] = true;
|
||||
|
||||
this._insertInNode(rootObj, str.substring(this._MIN_TEL_LENGTH), dsId);
|
||||
},
|
||||
|
||||
// Search a substring in a node
|
||||
_searchInNode: function(node, str) {
|
||||
var currentStr = str;
|
||||
var found = false;
|
||||
var pointerOriginal = str.length;
|
||||
var searchObj;
|
||||
while (!found && currentStr.length > 0) {
|
||||
var idxObj = this._indexOf(node.leaves, currentStr, 'prefix');
|
||||
if (idxObj !== -1) {
|
||||
found = true;
|
||||
searchObj = node.leaves[idxObj];
|
||||
}
|
||||
else {
|
||||
currentStr = currentStr.substring(0, currentStr.length - 1);
|
||||
}
|
||||
pointerOriginal--;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
if (searchObj.value === str) {
|
||||
return searchObj;
|
||||
}
|
||||
// Let's calculate what is the gap
|
||||
var restString = str.substr(pointerOriginal + 1);
|
||||
if (restString.length > 0) {
|
||||
return this._searchInNode(searchObj, restString);
|
||||
}
|
||||
else {
|
||||
return searchObj;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// Search for a number (which can be partial) on the tree
|
||||
search: function(tree, number) {
|
||||
var out = [];
|
||||
var totalLength = number.length;
|
||||
var MIN_TEL_LENGTH = this._MIN_TEL_LENGTH;
|
||||
|
||||
if (totalLength >= MIN_TEL_LENGTH) {
|
||||
var firstThreeStr = number.substring(0, MIN_TEL_LENGTH);
|
||||
var rootObj = this._getFirstLevelNode(tree, firstThreeStr);
|
||||
|
||||
if (rootObj) {
|
||||
if (totalLength === MIN_TEL_LENGTH) {
|
||||
out = Object.keys(rootObj.keys);
|
||||
}
|
||||
else {
|
||||
var node = this._searchInNode(rootObj,
|
||||
number.substring(MIN_TEL_LENGTH));
|
||||
if (node) {
|
||||
out = Object.keys(node.keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
// Removes a number from the tree
|
||||
remove: function(tree, number, dsId) {
|
||||
var self = this;
|
||||
var variants = this._getStringVariants(number);
|
||||
variants.forEach(function(aVariant) {
|
||||
self._unIndex(tree, aVariant, dsId);
|
||||
});
|
||||
},
|
||||
|
||||
// Removes a partial string from a node
|
||||
_removeInNode: function(node, str, dsId) {
|
||||
if (str.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStr = str;
|
||||
var found = false;
|
||||
var pointerOriginal = str.length;
|
||||
var searchObj;
|
||||
while (!found && currentStr.length > 0) {
|
||||
var idxObj = this._indexOf(node.leaves, currentStr, 'prefix');
|
||||
if (idxObj !== -1) {
|
||||
found = true;
|
||||
searchObj = node.leaves[idxObj];
|
||||
}
|
||||
else {
|
||||
currentStr = currentStr.substring(0, currentStr.length - 1);
|
||||
}
|
||||
pointerOriginal--;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
delete searchObj.keys[dsId];
|
||||
this._removeInNode(searchObj, str.substr(pointerOriginal + 1), dsId);
|
||||
}
|
||||
},
|
||||
|
||||
// Removes a number from the tree
|
||||
// TODO: Compact the tree when a number is removed
|
||||
_unIndex: function(tree, number, dsId) {
|
||||
var firstThreeStr = number.substring(0, this._MIN_TEL_LENGTH);
|
||||
|
||||
var rootObj = this._getFirstLevelNode(tree, firstThreeStr);
|
||||
if (rootObj) {
|
||||
delete rootObj.keys[dsId];
|
||||
this._removeInNode(rootObj, number.substr(this._MIN_TEL_LENGTH), dsId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
;'use strict';var FxAccountsIACHelper=function FxAccountsIACHelper(){var DEFAULT_CONNECTION_STRING='fxa-mgmt';var default_rules={'manifestURLs':['app://system.gaiamobile.org/manifest.webapp','app://smart-system.gaiamobile.org/manifest.webapp']};var CONNECTION_STRING=DEFAULT_CONNECTION_STRING;var rules=default_rules;var port;var callbacks={};var eventListeners={};var init=function init(options){if(!options){return;}
|
||||
if(options.keyword&&typeof(options.keyword)=='string'&&options.keyword.length>0){CONNECTION_STRING=options.keyword;}
|
||||
if(options.rules&&typeof(options.rules)=='object'){rules=options.rules;}};var addEventListener=function addEventListener(eventName,listener){if(!eventListeners[eventName]){eventListeners[eventName]=[];}
|
||||
eventListeners[eventName].push(listener);};var removeEventListener=function removeEventListener(eventName,listener){var listeners=eventListeners[eventName];if(!listeners){return;}
|
||||
var index=listeners.indexOf(listener);if(index===-1){return;}
|
||||
listeners.splice(index,1);};var reset=function reset(){CONNECTION_STRING=DEFAULT_CONNECTION_STRING;rules=default_rules;eventListeners={};callbacks={};port=null;};var onMessage=function onMessage(evt){if(evt&&evt.data){var message=evt.data;if(message.eventName){var listeners=eventListeners[message.eventName];if(!listeners){return;}
|
||||
for(var listener in listeners){if(listeners[listener]&&typeof listeners[listener]==='function'){listeners[listener]();}}
|
||||
return;}
|
||||
var cbs,cb;if(message.methodName){cbs=callbacks[message.methodName];if(!cbs||!cbs.length){console.warn('No callbacks for method '+message.methodName);return;}
|
||||
while(cbs.length){cb=cbs.shift();if(!message.error){cb.successCb(message.data);}else{cb.errorCb(message.error);}}}}else{console.error('Unknown');}};var getSelf=function getSelf(cb,error){var request=navigator.mozApps.getSelf();request.onsuccess=function onSuccess(evt){cb(evt.target.result);};request.onerror=function onError(){if(error&&typeof(error)==='function'){error();}};};var requestQueue=[];var isConnecting=false;var connect=function connect(cb){if(isConnecting){return requestQueue.push(cb);}
|
||||
isConnecting=true;_connect(function onConnect(err){var next;isConnecting=false;if(typeof cb==='function'){cb(err);}
|
||||
while(!!(next=requestQueue.shift())){next&&next(err);}});};var _connect=function _connect(callback){getSelf(function onApp(app){if(!app){return;}
|
||||
app.connect(CONNECTION_STRING,rules).then(function(ports){if(!ports||ports.length!==1){return;}
|
||||
port=ports[0];callback&&callback();}).catch(function(error){callback&&callback(error);});});};var sendMessage=function sendMessage(message,successCb,errorCb){if(port){_sendMessage(message,successCb,errorCb);return;}
|
||||
connect(function(error){if(error){errorCb&&errorCb(error);return;}_sendMessage(message,successCb,errorCb);});};var _sendMessage=function _sendMessage(message,successCb,errorCb){var name=message.name;if(!name){return;}
|
||||
if(!callbacks[name]){callbacks[name]=[];}
|
||||
callbacks[name].push({successCb:successCb,errorCb:errorCb});port.onmessage=onMessage;port.postMessage(message);};var getAccount=function getAccount(successCb,errorCb){sendMessage({'name':'getAccount'},successCb,errorCb);};var openFlow=function openFlow(successCb,errorCb){sendMessage({'name':'openFlow'},successCb,errorCb);};var logout=function logout(successCb,errorCb){sendMessage({'name':'logout'},successCb,errorCb);};var refreshAuthentication=function refreshAuthentication(email,successCb,errorCb){sendMessage({'name':'refreshAuthentication','email':email},successCb,errorCb);};var resendVerificationEmail=function resendVerificationEmail(email,successCb,errorCb){sendMessage({'name':'resendVerificationEmail','email':email},successCb,errorCb);};connect();return{'addEventListener':addEventListener,'getAccount':getAccount,'init':init,'logout':logout,'openFlow':openFlow,'refreshAuthentication':refreshAuthentication,'removeEventListener':removeEventListener,'resendVerificationEmail':resendVerificationEmail,'reset':reset};}();
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
;'use strict';(function(exports){var _iccManager=navigator.mozIccManager;var _iccProxy=null;Object.defineProperty(exports,'IccHelper',{configurable:true,get:function(){return _iccProxy;},set:function(value){_iccProxy=value;}});if(_iccManager&&_iccManager.getIccById){var activeIccObj=null;var eventListeners={};var cachedEventListeners={};var events=['cardstatechange','iccinfochange','stkcommand','stksessionend'];var getters=['iccInfo','cardState'];var methods=['sendStkResponse','sendStkMenuSelection','sendStkTimerExpiration','sendStkEventDownload'];var domRequests=['getCardLock','unlockCardLock','setCardLock','getCardLockRetryCount','readContacts','updateContact','iccOpenChannel','iccExchangeAPDU','iccCloseChannel'];var getterTemplate=function(name){return function(){if(activeIccObj){return activeIccObj[name];}else{return null;}};};var methodTemplate=function(name){return function(){if(activeIccObj){return activeIccObj[name].apply(activeIccObj,arguments);}};};var createIccProxy=function(){var iccProxy={addEventListener:function(eventName,callback){if(typeof callback!=='function'){return;}
|
||||
if(events.indexOf(eventName)===-1){return;}
|
||||
var listeners=eventListeners[eventName];if(listeners==null){listeners=eventListeners[eventName]=[];}
|
||||
if(listeners.indexOf(callback)===-1){listeners.push(callback);}},removeEventListener:function(eventName,callback){var listeners=eventListeners[eventName];if(listeners){var index=listeners.indexOf(callback);if(index!==-1){listeners.splice(index,1);}}}};getters.forEach(function(getter){Object.defineProperty(iccProxy,getter,{enumerable:true,get:getterTemplate(getter)});});methods.forEach(function(method){iccProxy[method]=methodTemplate(method);});domRequests.forEach(function(request){iccProxy[request]=methodTemplate(request);});events.forEach(function(eventName){Object.defineProperty(iccProxy,'on'+eventName,{enumerable:true,set:function(newListener){var oldListener=cachedEventListeners[eventName];if(oldListener){iccProxy.removeEventListener(eventName,oldListener);}
|
||||
cachedEventListeners[eventName]=newListener;if(newListener){iccProxy.addEventListener(eventName,newListener);}},get:function(){return cachedEventListeners[eventName];}});});return iccProxy;};_iccProxy=createIccProxy();if(_iccManager.iccIds&&_iccManager.iccIds.length){activeIccObj=_iccManager.getIccById(_iccManager.iccIds[0]);if(activeIccObj){events.forEach(function(eventName){activeIccObj.addEventListener(eventName,function(event){var listeners=eventListeners[eventName];if(listeners){listeners.forEach(function(listener){listener(event);});}});});}}
|
||||
_iccManager.oniccdetected=function(event){if(_iccProxy.cardState==null){activeIccObj=_iccManager.getIccById(event.iccId);if(activeIccObj){events.forEach(function(eventName){activeIccObj.addEventListener(eventName,function(event){var listeners=eventListeners[eventName];if(listeners){listeners.forEach(function(listener){listener(event);});}});});['cardstatechange','iccinfochange'].forEach(function(eventName){if(eventListeners[eventName]){eventListeners[eventName].forEach(function(listener){listener();});}});}}};}else{_iccProxy=_iccManager;}})(window);
|
||||
32
assets/webapps/ftu.gaiamobile.org/shared/js/icons_helper.js
Normal file
32
assets/webapps/ftu.gaiamobile.org/shared/js/icons_helper.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
;'use strict';(function IconsHelper(exports){const ICON_CACHE_PERIOD=24*60*60*1000;const FETCH_XHR_TIMEOUT=10000;const DEBUG=false;var dataStore=null;function getDefaultIconSize(){var dpr=window.devicePixelRatio;return(dpr&&dpr>1)?142:84;}
|
||||
function sizeIsNearer(size1,size2,targetSize){var delta1=Math.abs(targetSize-size1);var delta2=Math.abs(targetSize-size2);return(delta1<=delta2);}
|
||||
function getIcon(uri,iconTargetSize,placeObj={},siteObj={}){var iconUrl=null;iconTargetSize=iconTargetSize*window.devicePixelRatio;if(siteObj.webManifestUrl&&siteObj.webManifest){iconUrl=getBestIconFromWebManifest(siteObj.webManifest,iconTargetSize);if(DEBUG&&iconUrl){console.log('Icon from Web Manifest');}}
|
||||
if(!iconUrl&&siteObj.manifest&&siteObj.manifest.icons){iconUrl=getBestIconFromWebManifest({icons:_convertToWebManifestIcons(siteObj.manifest,siteObj.origin||siteObj.manifest.origin)},iconTargetSize);if(DEBUG&&iconUrl){console.log('Icon from Firefox App Manifest');}}
|
||||
if(!iconUrl&&placeObj&&placeObj.icons){iconUrl=getBestIconFromMetaTags(placeObj.icons,iconTargetSize);if(DEBUG&&iconUrl){console.log('Icon from Meta tags');}}
|
||||
if(!iconUrl){var a=document.createElement('a');a.href=uri;iconUrl=a.origin+'/favicon.ico';if(iconTargetSize){iconUrl+='#-moz-resolution='+iconTargetSize+','+iconTargetSize;}
|
||||
DEBUG&&console.log('Icon from favicon.ico');}
|
||||
return new Promise(resolve=>{resolve(iconUrl);});}
|
||||
function getIconBlob(uri,iconTargetSize,placeObj={},siteObj={}){return new Promise((resolve,reject)=>{getIcon(uri,iconTargetSize,placeObj,siteObj).then(iconUrl=>{getStore().then(iconStore=>{iconStore.get(iconUrl).then(iconObj=>{if(!iconObj||!iconObj.timestamp||Date.now()-iconObj.timestamp>=ICON_CACHE_PERIOD){return fetchIconBlob(iconUrl).then(iconBlob=>{var img=document.createElement('img');var icon=new Icon(img,uri);icon.renderBlob(iconBlob,{size:iconTargetSize,onLoad:function(blob){var iconObj={blob:blob,originalUrl:iconUrl.toString(),timestamp:Date.now()};resolve(iconObj);iconStore.add(iconObj,iconUrl);},onerror:function(e){reject(`Failed to fetch icon ${iconUrl}`);}});}).catch(err=>{reject(`Failed to fetch icon ${iconUrl}: ${err}`);});}
|
||||
return resolve(iconObj);}).catch(err=>{reject(`Failed to get icon from dataStore: ${err}`);});}).catch(err=>{reject(`Error opening the dataStore: ${err}`);});});});}
|
||||
function setElementIcon(icon,targetSize){return getIconBlob(icon.bookmark.url,targetSize,icon.bookmark,icon.bookmark).then(iconObj=>{if(iconObj.blob){icon.icon=iconObj.blob;return Promise.resolve();}else if(icon.bookmark.icon){return fetchIconBlob(icon.bookmark.icon).then(iconBlob=>{icon.icon=iconBlob;return Promise.resolve();},Promise.reject.bind(Promise));}
|
||||
return Promise.reject('No icon data found');},Promise.reject.bind(Promise));}
|
||||
function getBestIconFromWebManifest(webManifest,iconSize){var icons=webManifest.icons;if(!icons){return null;}
|
||||
var maxSize=10000;var bestSize=maxSize;var iconURL=null;iconSize=iconSize||getDefaultIconSize();icons.forEach((potentialIcon)=>{if(!iconURL){iconURL=potentialIcon.src;}
|
||||
var sizes=Array.from(potentialIcon.sizes);var nearestSize=getNearestSize(sizes,iconSize,bestSize);if(nearestSize!==bestSize&&sizeIsNearer(nearestSize,bestSize,iconSize)){iconURL=potentialIcon.src;bestSize=nearestSize;}});return iconURL?iconURL:null;}
|
||||
function _convertToWebManifestIcons(manifest,origin){return Object.keys(manifest.icons).map(function(size){var url=manifest.icons[size];var sizes=[size+'x'+size];url=url.indexOf('http')>-1?url:origin+url;return{src:new URL(url),sizes:sizes};});}
|
||||
function getBestIconFromMetaTags(icons,iconSize){if(!icons){return null;}
|
||||
iconSize=iconSize||getDefaultIconSize();var iconURL=null;var bestSize=10000;Object.keys(icons).forEach((uri)=>{var potentialIcon=icons[uri];if(!iconURL){iconURL=uri;}
|
||||
var sizes=Array.from(potentialIcon.sizes);var nearestSize=getNearestSize(sizes,iconSize,bestSize);if(nearestSize!==bestSize&&sizeIsNearer(nearestSize,bestSize,iconSize)){iconURL=uri;bestSize=nearestSize;}
|
||||
if(potentialIcon.rel==='apple-touch-icon'||potentialIcon.rel==='apple-touch-icon-precomposed'){var moreInfoUrl='https://developer.mozilla.org/en-US/'+'Apps/Build/Icon_implementation_for_apps#General_icons_for_web_apps';console.warn('Warning: The apple-touch icons are being used '+'as a fallback only. They will be deprecated in '+'the future. See '+moreInfoUrl);}});return iconURL||null;}
|
||||
function getNearestSize(sizes,iconSize,bestSize){var bogusSize=10000;if(!bestSize){bestSize=bogusSize;}
|
||||
var nearestSize=sizes.reduce(function(nearestSize,sizeString,idx){var size=widthFromSizeString(sizeString);if(isNaN(size)){return nearestSize;}
|
||||
if(sizeIsNearer(size,nearestSize,iconSize)){return size;}else{return nearestSize;}},bestSize);return nearestSize===bogusSize?-1:nearestSize;}
|
||||
function widthFromSizeString(size){size=size||'';var xIndex=size.indexOf('x');if(!xIndex){return NaN;}
|
||||
return parseInt(size.substr(0,xIndex));}
|
||||
function getStore(){return new Promise(resolve=>{if(dataStore){return resolve(dataStore);}
|
||||
navigator.getDataStores('icons').then(stores=>{dataStore=stores[0];return resolve(dataStore);});});}
|
||||
function clear(){return getStore().then(iconStore=>{iconStore.clear();});}
|
||||
function fetchIcon(iconUrl){return new Promise((resolve,reject)=>{fetchIconBlob(iconUrl).then((iconBlob)=>{var img=document.createElement('img');img.src=URL.createObjectURL(iconBlob);img.onload=()=>{var iconSize=Math.max(img.naturalWidth,img.naturalHeight);resolve({blob:iconBlob,url:iconUrl,size:iconSize,timestamp:Date.now()});};img.onerror=()=>{reject(new Error(`Error while loading image.`));};}).catch((e)=>{reject(new Error(`Error while loading image: ${e}`));});});}
|
||||
function fetchIconBlob(iconUrl){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest({mozAnon:true,mozSystem:true});xhr.open('GET',iconUrl,true);xhr.responseType='blob';xhr.timeout=FETCH_XHR_TIMEOUT;xhr.send();xhr.onload=()=>{if(xhr.readyState===XMLHttpRequest.DONE&&xhr.status===200){var iconBlob=xhr.response;resolve(iconBlob);return;}
|
||||
reject(new Error(`Got HTTP status ${xhr.status} trying to load ${iconUrl}.`));};xhr.onerror=xhr.ontimeout=()=>{reject(new Error(`Error while getting ${iconUrl}.`));};});}
|
||||
exports.IconsHelper={getIcon:getIcon,getIconBlob:getIconBlob,setElementIcon:setElementIcon,getBestIconFromWebManifest:getBestIconFromWebManifest,getBestIconFromMetaTags:getBestIconFromMetaTags,fetchIcon:fetchIcon,fetchIconBlob:fetchIconBlob,get defaultIconSize(){return getDefaultIconSize();},clear:clear,getNearestSize:getNearestSize,};})(window);
|
||||
572
assets/webapps/ftu.gaiamobile.org/shared/js/image_utils.js
Normal file
572
assets/webapps/ftu.gaiamobile.org/shared/js/image_utils.js
Normal file
|
|
@ -0,0 +1,572 @@
|
|||
/*
|
||||
* This shared module defines a single ImageUtils object in the global scope.
|
||||
*
|
||||
* ImageUtils.getSizeAndType() takes a Blob as its argument and
|
||||
* returns a Promise. If the promise resolves, the resolve method is
|
||||
* passed an object with width, height, and type properties. Width and
|
||||
* height specify the width and height of the image in pixels. Type
|
||||
* specifies the MIME type of the image. If the promise is rejected,
|
||||
* the reject method will be passed a string error message.
|
||||
*
|
||||
* Supported image types are JPEG, PNG, GIF and BMP. The MIME type
|
||||
* strings returned when a promise resolves are "image/jpeg",
|
||||
* "image/png", "image/gif", and "image/bmp".
|
||||
*
|
||||
* If you pass a blob that holds an image of some other type (or a
|
||||
* blob that is not an image at all) then promise will be rejected.
|
||||
*
|
||||
* getSizeAndType() does not decode the image to determine its size
|
||||
* because can require large amounts of memory. Instead, it parses the
|
||||
* image file to determine its size and typically only needs to read a
|
||||
* prefix of the file into memory.
|
||||
*
|
||||
* getSizeAndType() differs from the getImageSize() function defined
|
||||
* in shared/js/media/immage_size.js. That function is similar but
|
||||
* also parses EXIF metadata for JPEG images and is only suitable if
|
||||
* you need to access EXIF data.
|
||||
*/
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
/* global Promise */
|
||||
|
||||
var ImageUtils = exports.ImageUtils = {};
|
||||
|
||||
// Define constants for the MIME types we use
|
||||
const JPEG = 'image/jpeg';
|
||||
const PNG = 'image/png';
|
||||
const GIF = 'image/gif';
|
||||
const BMP = 'image/bmp';
|
||||
|
||||
// Export them
|
||||
ImageUtils.JPEG = JPEG;
|
||||
ImageUtils.PNG = PNG;
|
||||
ImageUtils.GIF = GIF;
|
||||
ImageUtils.BMP = BMP;
|
||||
|
||||
ImageUtils.getSizeAndType = function getSizeAndType(imageBlob) {
|
||||
if (!(imageBlob instanceof Blob)) {
|
||||
console.log('not blob');
|
||||
return Promise.reject(new TypeError('argument is not a Blob'));
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (imageBlob.size <= 16) {
|
||||
reject('corrupt image file');
|
||||
return;
|
||||
}
|
||||
|
||||
// Start off assuming that we'll read the first 32kb of the file
|
||||
var bytesToRead = 32 * 1024;
|
||||
|
||||
// But if the blob indicates that this is a PNG, GIF, or BMP, then
|
||||
// we can read less since the size information is always close to the
|
||||
// start of the file for those formats.
|
||||
if (imageBlob.type === PNG ||
|
||||
imageBlob.type === GIF ||
|
||||
imageBlob.type === BMP) {
|
||||
bytesToRead = 512;
|
||||
}
|
||||
|
||||
// Try to find the information we want in the first bytes of the file
|
||||
findSizeAndType(imageBlob, bytesToRead, success, tryagain);
|
||||
|
||||
// If we got the size and type data, resolve the promise
|
||||
function success(data) {
|
||||
resolve(data);
|
||||
}
|
||||
|
||||
// If we didn't find it, try again for JPEG files
|
||||
function tryagain(data) {
|
||||
// If we were able to verify that this blob is a jpeg file
|
||||
// then we will try again because in JPEG files, the size
|
||||
// comes after the EXIF data so if there is a large preview image,
|
||||
// the size might be more than 32kb into the file
|
||||
if (data.type === JPEG) {
|
||||
findSizeAndType(imageBlob, imageBlob.size, success, failure);
|
||||
}
|
||||
// For all other known image types the type and size data is
|
||||
// near the start of the file, if we didn't find it on this first
|
||||
// try we need to give up now.
|
||||
else {
|
||||
reject(data.error);
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find the data even after a second attempt then reject.
|
||||
function failure(data) {
|
||||
reject(data.error);
|
||||
}
|
||||
});
|
||||
|
||||
// This is the internal helper function that actually reads the blob
|
||||
// and finds type and size information.
|
||||
function findSizeAndType(imageBlob, amountToRead, success, failure) {
|
||||
var slice = imageBlob.slice(0, Math.min(amountToRead, imageBlob.size));
|
||||
var reader = new FileReader();
|
||||
reader.readAsArrayBuffer(slice);
|
||||
reader.onloadend = function() {
|
||||
parseImageData(reader.result);
|
||||
};
|
||||
|
||||
function parseImageData(buffer) {
|
||||
var header = new Uint8Array(buffer, 0, 16);
|
||||
var view = new DataView(buffer);
|
||||
|
||||
if (header[0] === 0x89 &&
|
||||
header[1] === 0x50 && /* P */
|
||||
header[2] === 0x4e && /* N */
|
||||
header[3] === 0x47 && /* G */
|
||||
header[4] === 0x0d && /* \r */
|
||||
header[5] === 0x0a && /* \n */
|
||||
header[6] === 0x1A &&
|
||||
header[7] === 0x0a &&
|
||||
header[12] === 0x49 && /* I */
|
||||
header[13] === 0x48 && /* H */
|
||||
header[14] === 0x44 && /* D */
|
||||
header[15] === 0x52) /* R */
|
||||
{
|
||||
// This is a PNG file
|
||||
try {
|
||||
success({
|
||||
type: PNG,
|
||||
width: view.getUint32(16, false), // 32 bit big endian width
|
||||
height: view.getUint32(20, false) // 32 bit big endian height
|
||||
});
|
||||
}
|
||||
catch (ex) {
|
||||
failure({ error: ex.toString()});
|
||||
}
|
||||
}
|
||||
else if (header[0] === 0x47 && /* G */
|
||||
header[1] === 0x49 && /* I */
|
||||
header[2] === 0x46 && /* F */
|
||||
header[3] === 0x38 && /* 8 */
|
||||
(header[4] === 0x37 || /* 7 */
|
||||
header[4] === 0x39) && /* or 9 */
|
||||
header[5] === 0x61) /* a */
|
||||
{
|
||||
// This is a GIF file
|
||||
try {
|
||||
success({
|
||||
type: GIF,
|
||||
width: view.getUint16(6, true), // 16-bit little endian width
|
||||
height: view.getUint16(8, true) // 16-big little endian height
|
||||
});
|
||||
}
|
||||
catch (ex) {
|
||||
failure({ error: ex.toString() });
|
||||
}
|
||||
}
|
||||
else if (header[0] === 0x42 && /* B */
|
||||
header[1] === 0x4D && /* M */
|
||||
view.getUint32(2, true) === imageBlob.size)
|
||||
{
|
||||
// This is a BMP file
|
||||
try {
|
||||
var width, height;
|
||||
|
||||
if (view.getUint16(14, true) === 12) { // check format version
|
||||
width = view.getUint16(18, true); // 16-bit little endian width
|
||||
height = view.getUint16(20, true); // 16-bit little endian height
|
||||
}
|
||||
else { // newer versions of the format use 32-bit ints
|
||||
width = view.getUint32(18, true); // 32-bit little endian width
|
||||
height = view.getUint32(22, true); // 32-bit little endian height
|
||||
}
|
||||
|
||||
success({
|
||||
type: BMP,
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
}
|
||||
catch (ex) {
|
||||
failure({ error: ex.toString() });
|
||||
}
|
||||
}
|
||||
else if (header[0] === 0xFF &&
|
||||
header[1] === 0xD8) {
|
||||
var value = {
|
||||
type: JPEG
|
||||
};
|
||||
|
||||
// This is a JPEG file. To find its width and height we need
|
||||
// to skip past the EXIF data and find the start of image data.
|
||||
try {
|
||||
var offset = 2;
|
||||
|
||||
// Loop through the segments of the file until we find an SOF
|
||||
// segment with image size or until we reach the end of our data.
|
||||
for (;;) {
|
||||
// The byte at the current offset should be 0xFF marking
|
||||
// the start of a new segment.
|
||||
if (view.getUint8(offset) !== 0xFF) {
|
||||
failure({ error: 'corrupt JPEG file' });
|
||||
}
|
||||
var segmentType = view.getUint8(offset + 1);
|
||||
var segmentSize = view.getUint16(offset + 2) + 2;
|
||||
|
||||
// If we found an SOF "Start of Frame" segment, we can get
|
||||
// the image size from it.
|
||||
if ((segmentType >= 0xC0 && segmentType <= 0xC3) ||
|
||||
(segmentType >= 0xC5 && segmentType <= 0xC7) ||
|
||||
(segmentType >= 0xC9 && segmentType <= 0xCB) ||
|
||||
(segmentType >= 0xCD && segmentType <= 0xCF))
|
||||
{
|
||||
// 16-bit big-endian dimensions, height first
|
||||
value.height = view.getUint16(offset + 5, false);
|
||||
value.width = view.getUint16(offset + 7, false);
|
||||
success(value);
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, move on to the next segment
|
||||
offset += segmentSize;
|
||||
|
||||
// We may not have read the entire file into our array buffer
|
||||
// so we have to make sure we have not gone past the end of
|
||||
// the buffer before looping again. Note that in this case
|
||||
// the object we pass to the failure callback includes the
|
||||
// image type. This is a signal to getSizeAndType that it
|
||||
// should try again with a larger ArrayBuffer.
|
||||
if (offset + 9 > view.byteLength) {
|
||||
value.error = 'corrupt JPEG file';
|
||||
failure(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
failure({ error: ex.toString() });
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure({ error: 'unknown image type' });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Given a blob containing an image file, this function returns a
|
||||
// Promise that will resolve to a blob for an image file that has
|
||||
// the specified width and height. If the blob already has that
|
||||
// size, it is returned unchanged. If the image is bigger than the
|
||||
// specified size, then it will be shrunk and cropped as necessary
|
||||
// to fit the specified dimensions. If the image is smaller than
|
||||
// the specified size, it will be enlarged and cropped to fit. The
|
||||
// cropping is done in the same way as with the CSS
|
||||
// background-size:cover attribute.
|
||||
//
|
||||
// If the image is resized and outputType is "image/jpeg" or
|
||||
// "image/png" then that type will be used for the resized image. If
|
||||
// a JPEG or PNG image is resized and outputType is not specified,
|
||||
// then the output image will have the same type as the input image.
|
||||
// In all other cases, a resized image will be encoded as a PNG image.
|
||||
// Note that when an image does not need to be resized its type is not
|
||||
// changed, even if outputType is specified.
|
||||
// 'encoderOptions' is passed 'as is' to the 'toBlob' method of canvas:
|
||||
// A Number between 0 and 1 indicating image quality if the requested type
|
||||
// is image/jpeg or image/webp. If this argument is anything else,
|
||||
// the default value for image quality is used.
|
||||
ImageUtils.resizeAndCropToCover = function(inputImageBlob,
|
||||
outputWidth, outputHeight,
|
||||
outputType, encoderOptions)
|
||||
{
|
||||
if (!outputWidth || !isFinite(outputWidth) || outputWidth <= 0 ||
|
||||
!outputHeight || !isFinite(outputHeight) || outputHeight <= 0) {
|
||||
return Promise.reject(new TypeError('invalid output dimensions'));
|
||||
}
|
||||
outputWidth = Math.round(outputWidth);
|
||||
outputHeight = Math.round(outputHeight);
|
||||
|
||||
return ImageUtils.getSizeAndType(inputImageBlob).then(
|
||||
function resolve(data) {
|
||||
var inputWidth = data.width;
|
||||
var inputHeight = data.height;
|
||||
|
||||
// If the image already has the desired size, just return it unchanged
|
||||
if (inputWidth === outputWidth && inputHeight === outputHeight) {
|
||||
return inputImageBlob;
|
||||
}
|
||||
|
||||
// Otherwise, return a promise for the resized image
|
||||
return resize(data);
|
||||
},
|
||||
function reject(error) {
|
||||
// If we couldn't determine the image size and type for some
|
||||
// reason we are still going to assume that it is a valid image,
|
||||
// just one we don't know about. If gecko can decode it we'll
|
||||
// use it and won't reject unless gecko refuses it.
|
||||
return resize({});
|
||||
}
|
||||
);
|
||||
|
||||
// Decode the image in an <img> element, resize and/or crop it into a
|
||||
// <canvas> and the re-encode it to a blob again. Returns a Promise
|
||||
// that resolves to the encoded blob.
|
||||
function resize(data) {
|
||||
var inputType = data.type;
|
||||
var inputWidth = data.width;
|
||||
var inputHeight = data.height;
|
||||
|
||||
// Ignore invalid outputType parameters
|
||||
if (outputType && outputType !== JPEG && outputType !== PNG) {
|
||||
console.warn('Ignoring unsupported outputType', outputType);
|
||||
outputType = undefined;
|
||||
}
|
||||
|
||||
// Determine the output type if none was specified.
|
||||
if (!outputType) {
|
||||
if (inputType === JPEG || inputType === PNG) {
|
||||
outputType = inputType;
|
||||
}
|
||||
else {
|
||||
outputType = PNG;
|
||||
}
|
||||
}
|
||||
|
||||
// Get a URL for the image blob so we can ask gecko to decode it.
|
||||
var url = URL.createObjectURL(inputImageBlob);
|
||||
|
||||
// If we can downsample the image while decoding it, then add a
|
||||
// #-moz-samplesize media fragment to the URL. For large JPEG inputs
|
||||
// this can save megabytes of memory.
|
||||
var mediaFragment;
|
||||
if (inputType === JPEG &&
|
||||
inputWidth > outputWidth && inputHeight > outputHeight) {
|
||||
// How much would we like to scale the image by while decoding?
|
||||
var reduction = Math.max(outputWidth / inputWidth,
|
||||
outputHeight / inputHeight);
|
||||
// Get a media fragment that will downsample the image by up to
|
||||
// this amount.
|
||||
mediaFragment = ImageUtils.Downsample.sizeNoMoreThan(reduction);
|
||||
}
|
||||
else {
|
||||
mediaFragment = '';
|
||||
}
|
||||
|
||||
// The next step is to decode (while possibly downsampling) the
|
||||
// image, but that is asynchronous, so we'll have to return a
|
||||
// Promise to handle that
|
||||
return new Promise(function(resolve, reject) {
|
||||
var offscreenImage = new Image();
|
||||
offscreenImage.src = url + mediaFragment;
|
||||
|
||||
offscreenImage.onerror = function(e) {
|
||||
cleanupImage();
|
||||
reject('failed to decode image');
|
||||
};
|
||||
|
||||
offscreenImage.onload = function() {
|
||||
// The image has been decoded now, so we know its actual size.
|
||||
// If getSizeAndType failed, or if we used a media fragment then
|
||||
// this may be different than the inputWidth and inputHeight
|
||||
// values we used previously.
|
||||
var actualWidth = offscreenImage.width;
|
||||
var actualHeight = offscreenImage.height;
|
||||
|
||||
// Now figure how much we have to scale the decoded image down
|
||||
// (or up) so that it covers the specified output dimensions.
|
||||
var widthScale = outputWidth / actualWidth;
|
||||
var heightScale = outputHeight / actualHeight;
|
||||
var scale = Math.max(widthScale, heightScale);
|
||||
|
||||
// Scaling the output dimensions by this much tells us the
|
||||
// dimensions of the crop area on the decoded image
|
||||
var cropWidth = Math.round(outputWidth / scale);
|
||||
var cropHeight = Math.round(outputHeight / scale);
|
||||
|
||||
// Now center that crop region within the decoded image
|
||||
var cropLeft = Math.floor((actualWidth - cropWidth) / 2);
|
||||
var cropTop = Math.floor((actualHeight - cropHeight) / 2);
|
||||
|
||||
// Set up the canvas we need to copy the crop region into
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = outputWidth;
|
||||
canvas.height = outputHeight;
|
||||
var context = canvas.getContext('2d', { willReadFrequently: true });
|
||||
|
||||
// Copy the image into the canvas
|
||||
context.drawImage(offscreenImage,
|
||||
cropLeft, cropTop, cropWidth, cropHeight,
|
||||
0, 0, outputWidth, outputHeight);
|
||||
|
||||
// We're done with the image now: try to release the decoded
|
||||
// image memory as fast as we can
|
||||
cleanupImage();
|
||||
|
||||
// Now encode the pixels in the canvas as an image blob
|
||||
canvas.toBlob(function(blob) {
|
||||
// We've got the encoded image in the blob, so we don't need
|
||||
// the pixels in the canvas anymore. Try to release that memory
|
||||
// right away, too.
|
||||
canvas.width = 0;
|
||||
resolve(blob);
|
||||
}, outputType, encoderOptions);
|
||||
};
|
||||
|
||||
function cleanupImage() {
|
||||
offscreenImage.onerror = offscreenImage.onload = '';
|
||||
offscreenImage.onload = offscreenImage.onerror = null; offscreenImage.removeAttribute('src');
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// This module defines a Downsample object with static
|
||||
// methods that return objects representing media fragments for
|
||||
// downsampling images while they are decoded. The current implementation
|
||||
// is based on the #-moz-samplesize media fragment. But because of
|
||||
// problems with that fragment (see bug 1004908) it seems likely that a
|
||||
// new syntax or new fragment will be introduced. If that happens, we can
|
||||
// just change this module and not have to change anything else that
|
||||
// depends on it.
|
||||
//
|
||||
// The method Downsample.areaAtLeast(scale) returns an object
|
||||
// representing a media fragment to use to decode an image downsampled by
|
||||
// at least as much as the specified scale. If you are trying to preview
|
||||
// an 8mp image and don't want to use more than 2mp of image memory, for
|
||||
// example, you would pass a scale of .25 (2mp/8mp) here, and the
|
||||
// resulting media fragment could be appended to the url to make the
|
||||
// image decode at a size equal to or smaller than 2mp.
|
||||
//
|
||||
// The method Downsample.sizeNoMoreThan(scale) returns a media fragment
|
||||
// object that you can use to reduce the dimensions of an image as much
|
||||
// as possible without exceeding the specified scale. If you have a
|
||||
// 1600x1200 image and want to decode it to produce an image that is as
|
||||
// small as possible but at least 160x120, you would pass a scale of 0.1.
|
||||
//
|
||||
// The returned objects have a dimensionScale property that specifies how
|
||||
// they affect the dimensions of the image and an areaScale property that
|
||||
// specifies how much they affect the area (number of pixels) in an
|
||||
// image. (The areaScale is just the square of the scale.) To avoid
|
||||
// floating-point rounding issues, the values of these scale properties
|
||||
// are rounded to the nearest hundredth.
|
||||
//
|
||||
// The returned objects also have a scale() method that scales a
|
||||
// dimension with proper rounding (it rounds up to the nearest integer
|
||||
// just as libjpeg does).
|
||||
//
|
||||
// Each object also has a toString() method that returns the required
|
||||
// media fragment (including the hash mark) so you can simply use
|
||||
// string concatentation to append one of these objects to the URL of
|
||||
// the image you want to decode.
|
||||
//
|
||||
// Downsample.NONE is a no-op media fragment object with scale set to
|
||||
// 1, and a toString() method that returns the empty string.
|
||||
//
|
||||
(function(exports) {
|
||||
'use strict'; // jshint ignore:line
|
||||
|
||||
// Round to the nearest hundredth to combat floating point rounding errors
|
||||
function round(x) {
|
||||
return Math.round(x * 100) / 100;
|
||||
}
|
||||
|
||||
//
|
||||
// A factory method for returning an object that represents a
|
||||
// #-moz-samplesize media fragment. The use of Math.ceil() in the
|
||||
// scale method is from jpeg_core_output_dimensions() in
|
||||
// media/libjpeg/jdmaster.c and jdiv_round_up() in media/libjpeg/jutils.c
|
||||
//
|
||||
function MozSampleSize(n, scale) {
|
||||
return Object.freeze({
|
||||
dimensionScale: round(scale),
|
||||
areaScale: round(scale * scale),
|
||||
toString: function() { return '#-moz-samplesize=' + n; },
|
||||
scale: function(x) { return Math.ceil(x * scale); }
|
||||
});
|
||||
}
|
||||
|
||||
// A fragment object that represents no downsampling with no fragment
|
||||
var NONE = Object.freeze({
|
||||
dimensionScale: 1,
|
||||
areaScale: 1,
|
||||
toString: function() { return ''; },
|
||||
scale: function(x) { return x; }
|
||||
});
|
||||
|
||||
//
|
||||
// The five possible #-moz-samplesize values.
|
||||
// The mapping from sample size to scale comes from:
|
||||
// the moz-samplesize code in /image/decoders/nsJPEGDecoder.cpp and
|
||||
// the jpeg_core_output_dimensions() function in media/libjpeg/jdmaster.c
|
||||
//
|
||||
var fragments = [
|
||||
NONE,
|
||||
// samplesize=2 reduces size by 1/2 and area by 1/4, etc.
|
||||
MozSampleSize(2, 1 / 2),
|
||||
MozSampleSize(3, 3 / 8),
|
||||
MozSampleSize(4, 1 / 4),
|
||||
MozSampleSize(8, 1 / 8)
|
||||
];
|
||||
|
||||
// Return the fragment object that has the largest scale and downsamples the
|
||||
// dimensions of an image at least as much as the specified scale.
|
||||
// If none of the choices scales enough, return the one that comes closest
|
||||
function sizeAtLeast(scale) {
|
||||
scale = round(scale);
|
||||
for (var i = 0; i < fragments.length; i++) {
|
||||
var f = fragments[i];
|
||||
if (f.dimensionScale <= scale) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return fragments[fragments.length - 1];
|
||||
}
|
||||
|
||||
// Return the fragment object that downsamples an image as far as possible
|
||||
// without going beyond the specified scale. This might return NONE.
|
||||
function sizeNoMoreThan(scale) {
|
||||
scale = round(scale);
|
||||
for (var i = fragments.length - 1; i >= 0; i--) {
|
||||
var f = fragments[i];
|
||||
if (f.dimensionScale >= scale) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
|
||||
// Return the fragment object that has the largest scale and downsamples the
|
||||
// area of an image at least as much as the specified scale.
|
||||
// If none of the choices scales enough, return the one that comes closest
|
||||
function areaAtLeast(scale) {
|
||||
scale = round(scale);
|
||||
for (var i = 0; i < fragments.length; i++) {
|
||||
var f = fragments[i];
|
||||
if (f.areaScale <= scale) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return fragments[fragments.length - 1];
|
||||
}
|
||||
|
||||
// Return the fragment object that downsamples the area of an image
|
||||
// as far as possible without going beyond the specified scale. This
|
||||
// might return NONE.
|
||||
function areaNoMoreThan(scale) {
|
||||
scale = round(scale);
|
||||
for (var i = fragments.length - 1; i >= 0; i--) {
|
||||
var f = fragments[i];
|
||||
if (f.areaScale >= scale) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
|
||||
exports.Downsample = {
|
||||
sizeAtLeast: sizeAtLeast,
|
||||
sizeNoMoreThan: sizeNoMoreThan,
|
||||
areaAtLeast: areaAtLeast,
|
||||
areaNoMoreThan: areaNoMoreThan,
|
||||
NONE: NONE,
|
||||
MAX_SIZE_REDUCTION: 1 / fragments[fragments.length - 1].dimensionScale,
|
||||
MAX_AREA_REDUCTION: 1 / fragments[fragments.length - 1].areaScale
|
||||
};
|
||||
}(exports.ImageUtils));
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
;'use strict';(function(exports){var InputApp=function(domApp,dynamicInputs){this.domApp=domApp;this.dynamicInputs=dynamicInputs||{};if(!this.domApp.manifest){throw new Error('InputApp: should not be constructed before DOMApplication '+'has manifest ready.');}};InputApp.prototype.getInputs=function(){var dict={};var inputId;for(inputId in this.domApp.manifest.inputs){dict[inputId]=this.domApp.manifest.inputs[inputId];delete dict[inputId].nameL10nId;}
|
||||
for(inputId in this.dynamicInputs){if(inputId in dict){break;}
|
||||
dict[inputId]=this.dynamicInputs[inputId];dict[inputId].isDynamic=true;}
|
||||
if(this.domApp.manifest.type==='certified'){if('emoji'in dict){dict.emoji.nameL10nId='keyboard-layout-emoji';}}
|
||||
return dict;};var InputAppListSettings=function(){this._values={enable3rdPartyApps:undefined,dynamicInputs:undefined};this._bindCallback=null;this.ready=false;this._promise=null;};InputAppListSettings.prototype.onchange=null;InputAppListSettings.prototype.SETTINGS_KEY_THIRD_PARTY_APP_ENABLED='keyboard.3rd-party-app.enabled';InputAppListSettings.prototype.SETTINGS_KEY_DYNAMIC_INPUTS='keyboard.dynamic-inputs';InputAppListSettings.prototype.start=function(){if(navigator.mozSettings){this._bindCallback=this.callback.bind(this);navigator.mozSettings.addObserver(this.SETTINGS_KEY_THIRD_PARTY_APP_ENABLED,this._bindCallback);navigator.mozSettings.addObserver(this.SETTINGS_KEY_DYNAMIC_INPUTS,this._bindCallback);}
|
||||
this.getSettings().then(function(){this.ready=true;}.bind(this));};InputAppListSettings.prototype.getSettings=function(){if(this.ready){return this.getSettingsSync();}
|
||||
return this.getSettingsAsync();};InputAppListSettings.prototype.getSettingsSync=function(){if(!this.ready){console.warn('getSettingsSync: getSettingsSync called before ready.');}
|
||||
return this._values;};InputAppListSettings.prototype.getSettingsAsync=function(){if(this._promise){return this._promise;}
|
||||
var lock=navigator.mozSettings.createLock();var p=this._promise=Promise.all([lock.get(this.SETTINGS_KEY_THIRD_PARTY_APP_ENABLED),lock.get(this.SETTINGS_KEY_DYNAMIC_INPUTS)]).then(function(values){this._values.enable3rdPartyApps=values[0][this.SETTINGS_KEY_THIRD_PARTY_APP_ENABLED];this._values.dynamicInputs=values[1][this.SETTINGS_KEY_DYNAMIC_INPUTS];return this._values;}.bind(this)).catch(function(e){console.error('InputAppListSettings: Fail to get setting.',e);this._promise=null;this._values.enable3rdPartyApps=false;this._values.dynamicInputs={};return this._values;}.bind(this));return p;};InputAppListSettings.prototype.callback=function(obj){switch(obj.settingName){case this.SETTINGS_KEY_THIRD_PARTY_APP_ENABLED:this._values.enable3rdPartyApps=obj.settingValue;break;case this.SETTINGS_KEY_DYNAMIC_INPUTS:this._values.dynamicInputs=obj.settingValue;break;}
|
||||
if(typeof this.onchange==='function'){this.onchange(this._values);}};InputAppListSettings.prototype.stop=function(){this._values={enable3rdPartyApps:undefined,dynamicInputs:undefined};this.ready=false;this._promise=null;if(navigator.mozSettings){navigator.mozSettings.removeObserver(this.SETTINGS_KEY_THIRD_PARTY_APP_ENABLED,this._bindCallback);navigator.mozSettings.removeObserver(this.SETTINGS_KEY_DYNAMIC_INPUTS,this._bindCallback);this._bindCallback=null;}};var InputAppList=function(){this.ready=false;this._inputApps=null;this.settings=null;this._getListPromise=null;};InputAppList.prototype.onready=null;InputAppList.prototype.onupdate=null;InputAppList.prototype.SETTINGS_KEY_THIRD_PARTY_APP_ENABLED='keyboard.3rd-party-app.enabled';InputAppList.prototype.start=function(){if(navigator.mozApps&&navigator.mozApps.mgmt){navigator.mozApps.mgmt.addEventListener('install',this);navigator.mozApps.mgmt.addEventListener('uninstall',this);}
|
||||
this.settings=new InputAppListSettings();this.settings.onchange=this._refresh.bind(this);this.settings.start();this.getList().then(function(){this.ready=true;if(typeof this.onready==='function'){return this.onready(this._inputApps);}}.bind(this)).catch(function(e){console.error(e);});};InputAppList.prototype.getList=function(){if(this._getListPromise){return this._getListPromise;}
|
||||
var p=this._getListPromise=Promise.all([this.settings.getSettings(),this._getAllApps()]).then(this._setInputApps.bind(this)).then(function(){return this._inputApps;}.bind(this)).catch(function(e){this._getListPromise=null;console.error(e);}.bind(this));return p;};InputAppList.prototype.getListSync=function(){if(!this.ready){console.warn('InputAppList: getListSync called when list is not ready.');}
|
||||
return this._inputApps;};InputAppList.prototype.handleEvent=function(evt){var updated=false;switch(evt.type){case'install':var app=evt.application;if(app.downloading){app.addEventListener('downloadsuccess',this);return;}
|
||||
updated=this._addInputApp(app);break;case'downloadsuccess':updated=this._addInputApp(evt.target);break;case'uninstall':updated=this._removeInputApp(evt.application);break;}
|
||||
if(updated&&typeof this.onupdate==='function'){this.onupdate(this._inputApps);}};InputAppList.prototype.stop=function(){this.ready=false;this._inputApps=null;this.settings=null;this._getListPromise=null;if(navigator.mozApps&&navigator.mozApps.mgmt){navigator.mozApps.mgmt.removeEventListener('install',this);navigator.mozApps.mgmt.removeEventListener('uninstall',this);}};InputAppList.prototype._getAllApps=function(){if(!navigator.mozApps||!navigator.mozApps.mgmt){console.error('InputAppList: mozApps.mgmt not available.');return[];}
|
||||
return Promise.resolve(navigator.mozApps.mgmt.getAll()).catch(function(e){console.error('InputAppList: Fail to get app list.',e);return[];});};InputAppList.prototype._setInputApps=function(values){var apps=values[1];var dynamicInputs=this.settings.getSettingsSync().dynamicInputs||{};var inputApps=apps.filter(this._isInputApp,this).map(function(domApp){var inputApp=new InputApp(domApp,dynamicInputs[domApp.manifestURL]);return inputApp;});if(inputApps.length===0){console.error('InputAppList: No input apps installed?');}
|
||||
this._inputApps=inputApps;};InputAppList.prototype._addInputApp=function(domApp){if(!this._isInputApp(domApp)){return false;}
|
||||
this._inputApps.push(new InputApp(domApp));return true;};InputAppList.prototype._removeInputApp=function(domApp){var index=this._inputApps.findIndex(function(appInList){return(appInList.domApp.manifestURL===domApp.manifestURL);});if(index===-1){return false;}
|
||||
this._inputApps.splice(index,1);return true;};InputAppList.prototype._isInputApp=function(domApp){if(!domApp.manifest||'input'!==domApp.manifest.role){return false;}
|
||||
if(domApp.manifest.type!=='certified'&&domApp.manifest.type!=='privileged'){return false;}
|
||||
var settingValues=this.settings.getSettingsSync();if(!settingValues.enable3rdPartyApps&&domApp.manifest.type!=='certified'){console.warn('InputAppList: '+'A 3rd-party input app is installed but the feature is not enabled.',domApp.manifestURL);return false;}
|
||||
if(domApp.manifest.permissions&&(typeof domApp.manifest.permissions.input!=='object')){return false;}
|
||||
if(typeof domApp.manifest.inputs!=='object'){return false;}
|
||||
return true;};InputAppList.prototype._refresh=function(){this._getListPromise=null;this.getList().then(function(){if(typeof this.onupdate==='function'){return this.onupdate(this._inputApps);}}.bind(this)).catch(function(e){console.error(e);});};exports.InputAppListSettings=InputAppListSettings;exports.InputAppList=InputAppList;exports.InputApp=InputApp;}(window));
|
||||
280
assets/webapps/ftu.gaiamobile.org/shared/js/intl/l20n.js
Normal file
280
assets/webapps/ftu.gaiamobile.org/shared/js/intl/l20n.js
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
;var _createClass=(function(){function defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if('value'in descriptor)descriptor.writable=true;Object.defineProperty(target,descriptor.key,descriptor);}}return function(Constructor,protoProps,staticProps){if(protoProps)defineProperties(Constructor.prototype,protoProps);if(staticProps)defineProperties(Constructor,staticProps);return Constructor;};})();function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError('Cannot call a class as a function');}}
|
||||
(function(){'use strict';function emit(listeners,...args){const type=args.shift();if(listeners['*']){listeners['*'].slice().forEach(listener=>listener.apply(this,args));}
|
||||
if(listeners[type]){listeners[type].slice().forEach(listener=>listener.apply(this,args));}}
|
||||
function addEventListener(listeners,type,listener){if(!(type in listeners)){listeners[type]=[];}
|
||||
listeners[type].push(listener);}
|
||||
function removeEventListener(listeners,type,listener){const typeListeners=listeners[type];const pos=typeListeners.indexOf(listener);if(pos===-1){return;}
|
||||
typeListeners.splice(pos,1);}
|
||||
let Client=(function(){function Client(remote){_classCallCheck(this,Client);this.id=this;this.remote=remote;const listeners={};this.on=(...args)=>addEventListener(listeners,...args);this.emit=(...args)=>emit(listeners,...args);}
|
||||
_createClass(Client,[{key:'method',value:function method(name,...args){return this.remote[name](...args);}}]);return Client;})();function broadcast(type,data){Array.from(this.ctxs.keys()).forEach(client=>client.emit(type,data));}
|
||||
function L10nError(message,id,lang){this.name='L10nError';this.message=message;this.id=id;this.lang=lang;}
|
||||
L10nError.prototype=Object.create(Error.prototype);L10nError.prototype.constructor=L10nError;function load(type,url){return new Promise(function(resolve,reject){const xhr=new XMLHttpRequest();if(xhr.overrideMimeType){xhr.overrideMimeType(type);}
|
||||
xhr.open('GET',url,true);if(type==='application/json'){xhr.responseType='json';}
|
||||
xhr.addEventListener('load',function io_onload(e){if(e.target.status===200||e.target.status===0){resolve(e.target.response||e.target.responseText);}else{reject(new L10nError('Not found: '+url));}});xhr.addEventListener('error',reject);xhr.addEventListener('timeout',reject);try{xhr.send(null);}catch(e){if(e.name==='NS_ERROR_FILE_NOT_FOUND'){reject(new L10nError('Not found: '+url));}else{throw e;}}});}
|
||||
const io={extra:function(code,ver,path,type){return navigator.mozApps.getLocalizationResource(code,ver,path,type);},app:function(code,ver,path,type){switch(type){case'text':return load('text/plain',path);case'json':return load('application/json',path);default:throw new L10nError('Unknown file type: '+type);}}};function fetchResource(res,{code,src,ver}){const url=res.replace('{locale}',code);const type=res.endsWith('.json')?'json':'text';return io[src](code,ver,url,type);}
|
||||
const KNOWN_MACROS=['plural'];const MAX_PLACEABLE_LENGTH=2500;const FSI='';const PDI='';const resolutionChain=new WeakSet();function format(ctx,lang,args,entity){if(typeof entity==='string'){return[{},entity];}
|
||||
if(resolutionChain.has(entity)){throw new L10nError('Cyclic reference detected');}
|
||||
resolutionChain.add(entity);let rv;try{rv=resolveValue({},ctx,lang,args,entity.value,entity.index);}finally{resolutionChain.delete(entity);}
|
||||
return rv;}
|
||||
function resolveIdentifier(ctx,lang,args,id){if(KNOWN_MACROS.indexOf(id)>-1){return[{},ctx._getMacro(lang,id)];}
|
||||
if(args&&args.hasOwnProperty(id)){if(typeof args[id]==='string'||typeof args[id]==='number'&&!isNaN(args[id])){return[{},args[id]];}else{throw new L10nError('Arg must be a string or a number: '+id);}}
|
||||
if(id==='__proto__'){throw new L10nError('Illegal id: '+id);}
|
||||
const entity=ctx._getEntity(lang,id);if(entity){return format(ctx,lang,args,entity);}
|
||||
throw new L10nError('Unknown reference: '+id);}
|
||||
function subPlaceable(locals,ctx,lang,args,id){let newLocals,value;try{[newLocals,value]=resolveIdentifier(ctx,lang,args,id);}catch(err){return[{error:err},FSI+'{{ '+id+' }}'+PDI];}
|
||||
if(typeof value==='number'){const formatter=ctx._getNumberFormatter(lang);return[newLocals,formatter.format(value)];}
|
||||
if(typeof value==='string'){if(value.length>=MAX_PLACEABLE_LENGTH){throw new L10nError('Too many characters in placeable ('+value.length+', max allowed is '+MAX_PLACEABLE_LENGTH+')');}
|
||||
return[newLocals,FSI+value+PDI];}
|
||||
return[{},FSI+'{{ '+id+' }}'+PDI];}
|
||||
function interpolate(locals,ctx,lang,args,arr){return arr.reduce(function([localsSeq,valueSeq],cur){if(typeof cur==='string'){return[localsSeq,valueSeq+cur];}else{const[,value]=subPlaceable(locals,ctx,lang,args,cur.name);return[localsSeq,valueSeq+value];}},[locals,'']);}
|
||||
function resolveSelector(ctx,lang,args,expr,index){let selectorName;if(index[0].type==='call'&&index[0].expr.type==='prop'&&index[0].expr.expr.name==='cldr'){selectorName='plural';}else{selectorName=index[0].name;}
|
||||
const selector=resolveIdentifier(ctx,lang,args,selectorName)[1];if(typeof selector!=='function'){return selector;}
|
||||
const argValue=index[0].args?resolveIdentifier(ctx,lang,args,index[0].args[0].name)[1]:undefined;if(selectorName==='plural'){if(argValue===0&&'zero'in expr){return'zero';}
|
||||
if(argValue===1&&'one'in expr){return'one';}
|
||||
if(argValue===2&&'two'in expr){return'two';}}
|
||||
return selector(argValue);}
|
||||
function resolveValue(locals,ctx,lang,args,expr,index){if(!expr){return[locals,expr];}
|
||||
if(typeof expr==='string'||typeof expr==='boolean'||typeof expr==='number'){return[locals,expr];}
|
||||
if(Array.isArray(expr)){return interpolate(locals,ctx,lang,args,expr);}
|
||||
if(index){const selector=resolveSelector(ctx,lang,args,expr,index);if(selector in expr){return resolveValue(locals,ctx,lang,args,expr[selector]);}}
|
||||
const defaultKey=expr.__default||'other';if(defaultKey in expr){return resolveValue(locals,ctx,lang,args,expr[defaultKey]);}
|
||||
throw new L10nError('Unresolvable value');}
|
||||
const locales2rules={'af':3,'ak':4,'am':4,'ar':1,'asa':3,'az':0,'be':11,'bem':3,'bez':3,'bg':3,'bh':4,'bm':0,'bn':3,'bo':0,'br':20,'brx':3,'bs':11,'ca':3,'cgg':3,'chr':3,'cs':12,'cy':17,'da':3,'de':3,'dv':3,'dz':0,'ee':3,'el':3,'en':3,'eo':3,'es':3,'et':3,'eu':3,'fa':0,'ff':5,'fi':3,'fil':4,'fo':3,'fr':5,'fur':3,'fy':3,'ga':8,'gd':24,'gl':3,'gsw':3,'gu':3,'guw':4,'gv':23,'ha':3,'haw':3,'he':2,'hi':4,'hr':11,'hu':0,'id':0,'ig':0,'ii':0,'is':3,'it':3,'iu':7,'ja':0,'jmc':3,'jv':0,'ka':0,'kab':5,'kaj':3,'kcg':3,'kde':0,'kea':0,'kk':3,'kl':3,'km':0,'kn':0,'ko':0,'ksb':3,'ksh':21,'ku':3,'kw':7,'lag':18,'lb':3,'lg':3,'ln':4,'lo':0,'lt':10,'lv':6,'mas':3,'mg':4,'mk':16,'ml':3,'mn':3,'mo':9,'mr':3,'ms':0,'mt':15,'my':0,'nah':3,'naq':7,'nb':3,'nd':3,'ne':3,'nl':3,'nn':3,'no':3,'nr':3,'nso':4,'ny':3,'nyn':3,'om':3,'or':3,'pa':3,'pap':3,'pl':13,'ps':3,'pt':3,'rm':3,'ro':9,'rof':3,'ru':11,'rwk':3,'sah':0,'saq':3,'se':7,'seh':3,'ses':0,'sg':0,'sh':11,'shi':19,'sk':12,'sl':14,'sma':7,'smi':7,'smj':7,'smn':7,'sms':7,'sn':3,'so':3,'sq':3,'sr':11,'ss':3,'ssy':3,'st':3,'sv':3,'sw':3,'syr':3,'ta':3,'te':3,'teo':3,'th':0,'ti':4,'tig':3,'tk':3,'tl':4,'tn':3,'to':0,'tr':0,'ts':3,'tzm':22,'uk':11,'ur':3,'ve':3,'vi':0,'vun':3,'wa':4,'wae':3,'wo':0,'xh':3,'xog':3,'yo':0,'zh':0,'zu':3};function isIn(n,list){return list.indexOf(n)!==-1;}
|
||||
function isBetween(n,start,end){return typeof n===typeof start&&start<=n&&n<=end;}
|
||||
const pluralRules={'0':function(){return'other';},'1':function(n){if(isBetween(n%100,3,10)){return'few';}
|
||||
if(n===0){return'zero';}
|
||||
if(isBetween(n%100,11,99)){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'2':function(n){if(n!==0&&n%10===0){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'3':function(n){if(n===1){return'one';}
|
||||
return'other';},'4':function(n){if(isBetween(n,0,1)){return'one';}
|
||||
return'other';},'5':function(n){if(isBetween(n,0,2)&&n!==2){return'one';}
|
||||
return'other';},'6':function(n){if(n===0){return'zero';}
|
||||
if(n%10===1&&n%100!==11){return'one';}
|
||||
return'other';},'7':function(n){if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'8':function(n){if(isBetween(n,3,6)){return'few';}
|
||||
if(isBetween(n,7,10)){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'9':function(n){if(n===0||n!==1&&isBetween(n%100,1,19)){return'few';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'10':function(n){if(isBetween(n%10,2,9)&&!isBetween(n%100,11,19)){return'few';}
|
||||
if(n%10===1&&!isBetween(n%100,11,19)){return'one';}
|
||||
return'other';},'11':function(n){if(isBetween(n%10,2,4)&&!isBetween(n%100,12,14)){return'few';}
|
||||
if(n%10===0||isBetween(n%10,5,9)||isBetween(n%100,11,14)){return'many';}
|
||||
if(n%10===1&&n%100!==11){return'one';}
|
||||
return'other';},'12':function(n){if(isBetween(n,2,4)){return'few';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'13':function(n){if(isBetween(n%10,2,4)&&!isBetween(n%100,12,14)){return'few';}
|
||||
if(n!==1&&isBetween(n%10,0,1)||isBetween(n%10,5,9)||isBetween(n%100,12,14)){return'many';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'14':function(n){if(isBetween(n%100,3,4)){return'few';}
|
||||
if(n%100===2){return'two';}
|
||||
if(n%100===1){return'one';}
|
||||
return'other';},'15':function(n){if(n===0||isBetween(n%100,2,10)){return'few';}
|
||||
if(isBetween(n%100,11,19)){return'many';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'16':function(n){if(n%10===1&&n!==11){return'one';}
|
||||
return'other';},'17':function(n){if(n===3){return'few';}
|
||||
if(n===0){return'zero';}
|
||||
if(n===6){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'18':function(n){if(n===0){return'zero';}
|
||||
if(isBetween(n,0,2)&&n!==0&&n!==2){return'one';}
|
||||
return'other';},'19':function(n){if(isBetween(n,2,10)){return'few';}
|
||||
if(isBetween(n,0,1)){return'one';}
|
||||
return'other';},'20':function(n){if((isBetween(n%10,3,4)||n%10===9)&&!(isBetween(n%100,10,19)||isBetween(n%100,70,79)||isBetween(n%100,90,99))){return'few';}
|
||||
if(n%1000000===0&&n!==0){return'many';}
|
||||
if(n%10===2&&!isIn(n%100,[12,72,92])){return'two';}
|
||||
if(n%10===1&&!isIn(n%100,[11,71,91])){return'one';}
|
||||
return'other';},'21':function(n){if(n===0){return'zero';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'22':function(n){if(isBetween(n,0,1)||isBetween(n,11,99)){return'one';}
|
||||
return'other';},'23':function(n){if(isBetween(n%10,1,2)||n%20===0){return'one';}
|
||||
return'other';},'24':function(n){if(isBetween(n,3,10)||isBetween(n,13,19)){return'few';}
|
||||
if(isIn(n,[2,12])){return'two';}
|
||||
if(isIn(n,[1,11])){return'one';}
|
||||
return'other';}};function getPluralRule(code){const index=locales2rules[code.replace(/-.*$/,'')];if(!(index in pluralRules)){return function(){return'other';};}
|
||||
return pluralRules[index];}
|
||||
const L20nIntl=typeof Intl!=='undefined'?Intl:{NumberFormat:function(){return{format:function(v){return v;}};}};let Context=(function(){function Context(env,langs,resIds){_classCallCheck(this,Context);this.langs=langs;this.resIds=resIds;this.env=env;this.emit=(type,evt)=>env.emit(type,evt,this);}
|
||||
_createClass(Context,[{key:'_formatTuple',value:function _formatTuple(lang,args,entity,id,key){try{return format(this,lang,args,entity);}catch(err){err.id=key?id+'::'+key:id;err.lang=lang;this.emit('resolveerror',err);return[{error:err},err.id];}}},{key:'_formatEntity',value:function _formatEntity(lang,args,entity,id){const[,value]=this._formatTuple(lang,args,entity,id);const formatted={value,attrs:null};if(entity.attrs){formatted.attrs=Object.create(null);for(let key in entity.attrs){const[,attrValue]=this._formatTuple(lang,args,entity.attrs[key],id,key);formatted.attrs[key]=attrValue;}}
|
||||
return formatted;}},{key:'_formatValue',value:function _formatValue(lang,args,entity,id){return this._formatTuple(lang,args,entity,id)[1];}},{key:'fetch',value:function fetch(langs=this.langs){if(langs.length===0){return Promise.resolve(langs);}
|
||||
return Promise.all(this.resIds.map(resId=>this.env._getResource(langs[0],resId))).then(()=>langs);}},{key:'_resolve',value:function _resolve(langs,keys,formatter,prevResolved){const lang=langs[0];if(!lang){return reportMissing.call(this,keys,formatter,prevResolved);}
|
||||
let hasUnresolved=false;const resolved=keys.map((key,i)=>{if(prevResolved&&prevResolved[i]!==undefined){return prevResolved[i];}
|
||||
const[id,args]=Array.isArray(key)?key:[key,undefined];const entity=this._getEntity(lang,id);if(entity){return formatter.call(this,lang,args,entity,id);}
|
||||
this.emit('notfounderror',new L10nError('"'+id+'"'+' not found in '+lang.code,id,lang));hasUnresolved=true;});if(!hasUnresolved){return resolved;}
|
||||
return this.fetch(langs.slice(1)).then(nextLangs=>this._resolve(nextLangs,keys,formatter,resolved));}},{key:'formatEntities',value:function formatEntities(...keys){return this.fetch().then(langs=>this._resolve(langs,keys,this._formatEntity));}},{key:'formatValues',value:function formatValues(...keys){return this.fetch().then(langs=>this._resolve(langs,keys,this._formatValue));}},{key:'_getEntity',value:function _getEntity(lang,id){const cache=this.env.resCache;for(let i=0,resId;resId=this.resIds[i];i++){const resource=cache.get(resId+lang.code+lang.src);if(resource instanceof L10nError){continue;}
|
||||
if(id in resource){return resource[id];}}
|
||||
return undefined;}},{key:'_getNumberFormatter',value:function _getNumberFormatter(lang){if(!this.env.numberFormatters){this.env.numberFormatters=new Map();}
|
||||
if(!this.env.numberFormatters.has(lang)){const formatter=L20nIntl.NumberFormat(lang);this.env.numberFormatters.set(lang,formatter);return formatter;}
|
||||
return this.env.numberFormatters.get(lang);}},{key:'_getMacro',value:function _getMacro(lang,id){switch(id){case'plural':return getPluralRule(lang.code);default:return undefined;}}}]);return Context;})();function reportMissing(keys,formatter,resolved){const missingIds=new Set();keys.forEach((key,i)=>{if(resolved&&resolved[i]!==undefined){return;}
|
||||
const id=Array.isArray(key)?key[0]:key;missingIds.add(id);resolved[i]=formatter===this._formatValue?id:{value:id,attrs:null};});this.emit('notfounderror',new L10nError('"'+Array.from(missingIds).join(', ')+'"'+' not found in any language',missingIds));return resolved;}
|
||||
var MAX_PLACEABLES=100;var PropertiesParser={patterns:null,entryIds:null,emit:null,init:function(){this.patterns={comment:/^\s*#|^\s*$/,entity:/^([^=\s]+)\s*=\s*(.*)$/,multiline:/[^\\]\\$/,index:/\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,unicode:/\\u([0-9a-fA-F]{1,4})/g,entries:/[^\r\n]+/g,controlChars:/\\([\\\n\r\t\b\f\{\}\"\'])/g,placeables:/\{\{\s*([^\s]*?)\s*\}\}/};},parse:function(emit,source){if(!this.patterns){this.init();}
|
||||
this.emit=emit;var entries={};var lines=source.match(this.patterns.entries);if(!lines){return entries;}
|
||||
for(var i=0;i<lines.length;i++){var line=lines[i];if(this.patterns.comment.test(line)){continue;}
|
||||
while(this.patterns.multiline.test(line)&&i<lines.length){line=line.slice(0,-1)+lines[++i].trim();}
|
||||
var entityMatch=line.match(this.patterns.entity);if(entityMatch){try{this.parseEntity(entityMatch[1],entityMatch[2],entries);}catch(e){if(!this.emit){throw e;}}}}
|
||||
return entries;},parseEntity:function(id,value,entries){var name,key;var pos=id.indexOf('[');if(pos!==-1){name=id.substr(0,pos);key=id.substring(pos+1,id.length-1);}else{name=id;key=null;}
|
||||
var nameElements=name.split('.');if(nameElements.length>2){throw this.error('Error in ID: "'+name+'".'+' Nested attributes are not supported.');}
|
||||
var attr;if(nameElements.length>1){name=nameElements[0];attr=nameElements[1];if(attr[0]==='$'){throw this.error('Attribute can\'t start with "$"');}}else{attr=null;}
|
||||
this.setEntityValue(name,attr,key,this.unescapeString(value),entries);},setEntityValue:function(id,attr,key,rawValue,entries){var value=rawValue.indexOf('{{')>-1?this.parseString(rawValue):rawValue;var isSimpleValue=typeof value==='string';var root=entries;var isSimpleNode=typeof entries[id]==='string';if(!entries[id]&&(attr||key||!isSimpleValue)){entries[id]=Object.create(null);isSimpleNode=false;}
|
||||
if(attr){if(isSimpleNode){const val=entries[id];entries[id]=Object.create(null);entries[id].value=val;}
|
||||
if(!entries[id].attrs){entries[id].attrs=Object.create(null);}
|
||||
if(!entries[id].attrs&&!isSimpleValue){entries[id].attrs[attr]=Object.create(null);}
|
||||
root=entries[id].attrs;id=attr;}
|
||||
if(key){isSimpleNode=false;if(typeof root[id]==='string'){const val=root[id];root[id]=Object.create(null);root[id].index=this.parseIndex(val);root[id].value=Object.create(null);}
|
||||
root=root[id].value;id=key;isSimpleValue=true;}
|
||||
if(isSimpleValue){if(id in root){throw this.error('Duplicated id: '+id);}
|
||||
root[id]=value;}else{if(!root[id]){root[id]=Object.create(null);}
|
||||
root[id].value=value;}},parseString:function(str){var chunks=str.split(this.patterns.placeables);var complexStr=[];var len=chunks.length;var placeablesCount=(len-1)/2;if(placeablesCount>=MAX_PLACEABLES){throw this.error('Too many placeables ('+placeablesCount+', max allowed is '+MAX_PLACEABLES+')');}
|
||||
for(var i=0;i<chunks.length;i++){if(chunks[i].length===0){continue;}
|
||||
if(i%2===1){complexStr.push({type:'idOrVar',name:chunks[i]});}else{complexStr.push(chunks[i]);}}
|
||||
return complexStr;},unescapeString:function(str){if(str.lastIndexOf('\\')!==-1){str=str.replace(this.patterns.controlChars,'$1');}
|
||||
return str.replace(this.patterns.unicode,function(match,token){return String.fromCodePoint(parseInt(token,16));});},parseIndex:function(str){var match=str.match(this.patterns.index);if(!match){throw new L10nError('Malformed index');}
|
||||
if(match[2]){return[{type:'call',expr:{type:'prop',expr:{type:'glob',name:'cldr'},prop:'plural',cmpt:false},args:[{type:'idOrVar',name:match[2]}]}];}else{return[{type:'idOrVar',name:match[1]}];}},error:function(msg,type='parsererror'){const err=new L10nError(msg);if(this.emit){this.emit(type,err);}
|
||||
return err;}};const MAX_PLACEABLES$1=100;var L20nParser={parse:function(emit,string){this._source=string;this._index=0;this._length=string.length;this.entries=Object.create(null);this.emit=emit;return this.getResource();},getResource:function(){this.getWS();while(this._index<this._length){try{this.getEntry();}catch(e){if(e instanceof L10nError){this.getJunkEntry();if(!this.emit){throw e;}}else{throw e;}}
|
||||
if(this._index<this._length){this.getWS();}}
|
||||
return this.entries;},getEntry:function(){if(this._source[this._index]==='<'){++this._index;const id=this.getIdentifier();if(this._source[this._index]==='['){++this._index;return this.getEntity(id,this.getItemList(this.getExpression,']'));}
|
||||
return this.getEntity(id);}
|
||||
if(this._source.startsWith('/*',this._index)){return this.getComment();}
|
||||
throw this.error('Invalid entry');},getEntity:function(id,index){if(!this.getRequiredWS()){throw this.error('Expected white space');}
|
||||
const ch=this._source[this._index];const hasIndex=index!==undefined;const value=this.getValue(ch,hasIndex,hasIndex);let attrs;if(value===undefined){if(ch==='>'){throw this.error('Expected ">"');}
|
||||
attrs=this.getAttributes();}else{const ws1=this.getRequiredWS();if(this._source[this._index]!=='>'){if(!ws1){throw this.error('Expected ">"');}
|
||||
attrs=this.getAttributes();}}
|
||||
++this._index;if(id in this.entries){throw this.error('Duplicate entry ID "'+id,'duplicateerror');}
|
||||
if(!attrs&&!index&&typeof value==='string'){this.entries[id]=value;}else{this.entries[id]={value,attrs,index};}},getValue:function(ch=this._source[this._index],index=false,required=true){switch(ch){case'\'':case'"':return this.getString(ch,1);case'{':return this.getHash(index);}
|
||||
if(required){throw this.error('Unknown value type');}
|
||||
return;},getWS:function(){let cc=this._source.charCodeAt(this._index);while(cc===32||cc===10||cc===9||cc===13){cc=this._source.charCodeAt(++this._index);}},getRequiredWS:function(){const pos=this._index;let cc=this._source.charCodeAt(pos);while(cc===32||cc===10||cc===9||cc===13){cc=this._source.charCodeAt(++this._index);}
|
||||
return this._index!==pos;},getIdentifier:function(){const start=this._index;let cc=this._source.charCodeAt(this._index);if(cc>=97&&cc<=122||cc>=65&&cc<=90||cc===95){cc=this._source.charCodeAt(++this._index);}else{throw this.error('Identifier has to start with [a-zA-Z_]');}
|
||||
while(cc>=97&&cc<=122||cc>=65&&cc<=90||cc>=48&&cc<=57||cc===95){cc=this._source.charCodeAt(++this._index);}
|
||||
return this._source.slice(start,this._index);},getUnicodeChar:function(){for(let i=0;i<4;i++){let cc=this._source.charCodeAt(++this._index);if(cc>96&&cc<103||cc>64&&cc<71||cc>47&&cc<58){continue;}
|
||||
throw this.error('Illegal unicode escape sequence');}
|
||||
this._index++;return String.fromCharCode(parseInt(this._source.slice(this._index-4,this._index),16));},stringRe:/"|'|{{|\\/g,getString:function(opchar,opcharLen){const body=[];let placeables=0;this._index+=opcharLen;const start=this._index;let bufStart=start;let buf='';while(true){this.stringRe.lastIndex=this._index;const match=this.stringRe.exec(this._source);if(!match){throw this.error('Unclosed string literal');}
|
||||
if(match[0]==='"'||match[0]==='\''){if(match[0]!==opchar){this._index+=opcharLen;continue;}
|
||||
this._index=match.index+opcharLen;break;}
|
||||
if(match[0]==='{{'){if(placeables>MAX_PLACEABLES$1-1){throw this.error('Too many placeables, maximum allowed is '+MAX_PLACEABLES$1);}
|
||||
placeables++;if(match.index>bufStart||buf.length>0){body.push(buf+this._source.slice(bufStart,match.index));buf='';}
|
||||
this._index=match.index+2;this.getWS();body.push(this.getExpression());this.getWS();this._index+=2;bufStart=this._index;continue;}
|
||||
if(match[0]==='\\'){this._index=match.index+1;const ch2=this._source[this._index];if(ch2==='u'){buf+=this._source.slice(bufStart,match.index)+this.getUnicodeChar();}else if(ch2===opchar||ch2==='\\'){buf+=this._source.slice(bufStart,match.index)+ch2;this._index++;}else if(this._source.startsWith('{{',this._index)){buf+=this._source.slice(bufStart,match.index)+'{{';this._index+=2;}else{throw this.error('Illegal escape sequence');}
|
||||
bufStart=this._index;}}
|
||||
if(body.length===0){return buf+this._source.slice(bufStart,this._index-opcharLen);}
|
||||
if(this._index-opcharLen>bufStart||buf.length>0){body.push(buf+this._source.slice(bufStart,this._index-opcharLen));}
|
||||
return body;},getAttributes:function(){const attrs=Object.create(null);while(true){this.getAttribute(attrs);const ws1=this.getRequiredWS();const ch=this._source.charAt(this._index);if(ch==='>'){break;}else if(!ws1){throw this.error('Expected ">"');}}
|
||||
return attrs;},getAttribute:function(attrs){const key=this.getIdentifier();let index;if(this._source[this._index]==='['){++this._index;this.getWS();index=this.getItemList(this.getExpression,']');}
|
||||
this.getWS();if(this._source[this._index]!==':'){throw this.error('Expected ":"');}
|
||||
++this._index;this.getWS();const hasIndex=index!==undefined;const value=this.getValue(undefined,hasIndex);if(key in attrs){throw this.error('Duplicate attribute "'+key,'duplicateerror');}
|
||||
if(!index&&typeof value==='string'){attrs[key]=value;}else{attrs[key]={value,index};}},getHash:function(index){const items=Object.create(null);++this._index;this.getWS();let defKey;while(true){const[key,value,def]=this.getHashItem();items[key]=value;if(def){if(defKey){throw this.error('Default item redefinition forbidden');}
|
||||
defKey=key;}
|
||||
this.getWS();const comma=this._source[this._index]===',';if(comma){++this._index;this.getWS();}
|
||||
if(this._source[this._index]==='}'){++this._index;break;}
|
||||
if(!comma){throw this.error('Expected "}"');}}
|
||||
if(defKey){items.__default=defKey;}else if(!index){throw this.error('Unresolvable Hash Value');}
|
||||
return items;},getHashItem:function(){let defItem=false;if(this._source[this._index]==='*'){++this._index;defItem=true;}
|
||||
const key=this.getIdentifier();this.getWS();if(this._source[this._index]!==':'){throw this.error('Expected ":"');}
|
||||
++this._index;this.getWS();return[key,this.getValue(),defItem];},getComment:function(){this._index+=2;const start=this._index;const end=this._source.indexOf('*/',start);if(end===-1){throw this.error('Comment without a closing tag');}
|
||||
this._index=end+2;},getExpression:function(){let exp=this.getPrimaryExpression();while(true){let ch=this._source[this._index];if(ch==='.'||ch==='['){++this._index;exp=this.getPropertyExpression(exp,ch==='[');}else if(ch==='('){++this._index;exp=this.getCallExpression(exp);}else{break;}}
|
||||
return exp;},getPropertyExpression:function(idref,computed){let exp;if(computed){this.getWS();exp=this.getExpression();this.getWS();if(this._source[this._index]!==']'){throw this.error('Expected "]"');}
|
||||
++this._index;}else{exp=this.getIdentifier();}
|
||||
return{type:'prop',expr:idref,prop:exp,cmpt:computed};},getCallExpression:function(callee){this.getWS();return{type:'call',expr:callee,args:this.getItemList(this.getExpression,')')};},getPrimaryExpression:function(){const ch=this._source[this._index];switch(ch){case'$':++this._index;return{type:'var',name:this.getIdentifier()};case'@':++this._index;return{type:'glob',name:this.getIdentifier()};default:return{type:'id',name:this.getIdentifier()};}},getItemList:function(callback,closeChar){const items=[];let closed=false;this.getWS();if(this._source[this._index]===closeChar){++this._index;closed=true;}
|
||||
while(!closed){items.push(callback.call(this));this.getWS();let ch=this._source.charAt(this._index);switch(ch){case',':++this._index;this.getWS();break;case closeChar:++this._index;closed=true;break;default:throw this.error('Expected "," or "'+closeChar+'"');}}
|
||||
return items;},getJunkEntry:function(){const pos=this._index;let nextEntity=this._source.indexOf('<',pos);let nextComment=this._source.indexOf('/*',pos);if(nextEntity===-1){nextEntity=this._length;}
|
||||
if(nextComment===-1){nextComment=this._length;}
|
||||
let nextEntry=Math.min(nextEntity,nextComment);this._index=nextEntry;},error:function(message,type='parsererror'){const pos=this._index;let start=this._source.lastIndexOf('<',pos-1);const lastClose=this._source.lastIndexOf('>',pos-1);start=lastClose>start?lastClose+1:start;const context=this._source.slice(start,pos+10);const msg=message+' at pos '+pos+': `'+context+'`';const err=new L10nError(msg);if(this.emit){this.emit(type,err);}
|
||||
return err;}};function walkEntry(entry,fn){if(typeof entry==='string'){return fn(entry);}
|
||||
const newEntry=Object.create(null);if(entry.value){newEntry.value=walkValue(entry.value,fn);}
|
||||
if(entry.index){newEntry.index=entry.index;}
|
||||
if(entry.attrs){newEntry.attrs=Object.create(null);for(let key in entry.attrs){newEntry.attrs[key]=walkEntry(entry.attrs[key],fn);}}
|
||||
return newEntry;}
|
||||
function walkValue(value,fn){if(typeof value==='string'){return fn(value);}
|
||||
if(value.type){return value;}
|
||||
const newValue=Array.isArray(value)?[]:Object.create(null);const keys=Object.keys(value);for(let i=0,key;key=keys[i];i++){newValue[key]=walkValue(value[key],fn);}
|
||||
return newValue;}
|
||||
function createGetter(id,name){let _pseudo=null;return function getPseudo(){if(_pseudo){return _pseudo;}
|
||||
const reAlphas=/[a-zA-Z]/g;const reVowels=/[aeiouAEIOU]/g;const reWords=/[^\W0-9_]+/g;const reExcluded=/(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;const charMaps={'fr-x-psaccent':'ȦƁƇḒḖƑƓĦĪĴĶĿḾȠǾƤɊŘŞŦŬṼẆẊẎẐ[\\]^_`ȧƀƈḓḗƒɠħīĵķŀḿƞǿƥɋřşŧŭṽẇẋẏẑ','ar-x-psbidi':'∀ԐↃpƎɟפHIſӼ˥WNOԀÒᴚS⊥∩ɅMXʎZ[\\]ᵥ_,ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz'};const mods={'fr-x-psaccent':val=>val.replace(reVowels,match=>match+match.toLowerCase()),'ar-x-psbidi':val=>val.replace(reWords,match=>''+match+'')};const replaceChars=(map,val)=>val.replace(reAlphas,match=>map.charAt(match.charCodeAt(0)-65));const transform=val=>replaceChars(charMaps[id],mods[id](val));const apply=(fn,val)=>{if(!val){return val;}
|
||||
const parts=val.split(reExcluded);const modified=parts.map(function(part){if(reExcluded.test(part)){return part;}
|
||||
return fn(part);});return modified.join('');};return _pseudo={name:transform(name),process:str=>apply(transform,str)};};}
|
||||
const pseudo=Object.defineProperties(Object.create(null),{'fr-x-psaccent':{enumerable:true,get:createGetter('fr-x-psaccent','Runtime Accented')},'ar-x-psbidi':{enumerable:true,get:createGetter('ar-x-psbidi','Runtime Bidi')}});let Env=(function(){function Env(fetchResource){_classCallCheck(this,Env);this.fetchResource=fetchResource;this.resCache=new Map();this.resRefs=new Map();this.numberFormatters=null;this.parsers={properties:PropertiesParser,l20n:L20nParser};const listeners={};this.emit=emit.bind(this,listeners);this.addEventListener=addEventListener.bind(this,listeners);this.removeEventListener=removeEventListener.bind(this,listeners);}
|
||||
_createClass(Env,[{key:'createContext',value:function createContext(langs,resIds){const ctx=new Context(this,langs,resIds);resIds.forEach(resId=>{const usedBy=this.resRefs.get(resId)||0;this.resRefs.set(resId,usedBy+1);});return ctx;}},{key:'destroyContext',value:function destroyContext(ctx){ctx.resIds.forEach(resId=>{const usedBy=this.resRefs.get(resId)||0;if(usedBy>1){return this.resRefs.set(resId,usedBy-1);}
|
||||
this.resRefs.delete(resId);this.resCache.forEach((val,key)=>key.startsWith(resId)?this.resCache.delete(key):null);});}},{key:'_parse',value:function _parse(syntax,lang,data){const parser=this.parsers[syntax];if(!parser){return data;}
|
||||
const emit=(type,err)=>this.emit(type,amendError(lang,err));return parser.parse.call(parser,emit,data);}},{key:'_create',value:function _create(lang,entries){if(lang.src!=='pseudo'){return entries;}
|
||||
const pseudoentries=Object.create(null);for(let key in entries){pseudoentries[key]=walkEntry(entries[key],pseudo[lang.code].process);}
|
||||
return pseudoentries;}},{key:'_getResource',value:function _getResource(lang,res){const cache=this.resCache;const id=res+lang.code+lang.src;if(cache.has(id)){return cache.get(id);}
|
||||
const syntax=res.substr(res.lastIndexOf('.')+1);const saveEntries=data=>{const entries=this._parse(syntax,lang,data);cache.set(id,this._create(lang,entries));};const recover=err=>{err.lang=lang;this.emit('fetcherror',err);cache.set(id,err);};const langToFetch=lang.src==='pseudo'?{code:'en-US',src:'app',ver:lang.ver}:lang;const resource=this.fetchResource(res,langToFetch).then(saveEntries,recover);cache.set(id,resource);return resource;}}]);return Env;})();function amendError(lang,err){err.lang=lang;return err;}
|
||||
function prioritizeLocales(def,availableLangs,requested){let supportedLocale;for(let i=0;i<requested.length;i++){const locale=requested[i];if(availableLangs.indexOf(locale)!==-1){supportedLocale=locale;break;}}
|
||||
if(!supportedLocale||supportedLocale===def){return[def];}
|
||||
return[supportedLocale,def];}
|
||||
function negotiateLanguages({appVersion,defaultLang,availableLangs},additionalLangs,prevLangs,requestedLangs){const allAvailableLangs=Object.keys(availableLangs).concat(Object.keys(additionalLangs)).concat(Object.keys(pseudo));const newLangs=prioritizeLocales(defaultLang,allAvailableLangs,requestedLangs);const langs=newLangs.map(code=>({code:code,src:getLangSource(appVersion,availableLangs,additionalLangs,code),ver:appVersion}));return{langs,haveChanged:!arrEqual(prevLangs,newLangs)};}
|
||||
function arrEqual(arr1,arr2){return arr1.length===arr2.length&&arr1.every((elem,i)=>elem===arr2[i]);}
|
||||
function getMatchingLangpack(appVersion,langpacks){for(let i=0,langpack;langpack=langpacks[i];i++){if(langpack.target===appVersion){return langpack;}}
|
||||
return null;}
|
||||
function getLangSource(appVersion,availableLangs,additionalLangs,code){if(additionalLangs&&additionalLangs[code]){const lp=getMatchingLangpack(appVersion,additionalLangs[code]);if(lp&&(!(code in availableLangs)||parseInt(lp.revision)>availableLangs[code])){return'extra';}}
|
||||
if(code in pseudo&&!(code in availableLangs)){return'pseudo';}
|
||||
return'app';}
|
||||
let Remote=(function(){function Remote(fetchResource,broadcast){_classCallCheck(this,Remote);this.broadcast=broadcast;this.env=new Env(fetchResource);this.ctxs=new Map();}
|
||||
_createClass(Remote,[{key:'registerView',value:function registerView(view,resources,meta,additionalLangs,requestedLangs){const{langs}=negotiateLanguages(meta,additionalLangs,[],requestedLangs);this.ctxs.set(view,this.env.createContext(langs,resources));return langs;}},{key:'unregisterView',value:function unregisterView(view){this.ctxs.delete(view);return true;}},{key:'formatEntities',value:function formatEntities(view,keys){return this.ctxs.get(view).formatEntities(...keys);}},{key:'formatValues',value:function formatValues(view,keys){return this.ctxs.get(view).formatValues(...keys);}},{key:'changeLanguages',value:function changeLanguages(view,meta,additionalLangs,requestedLangs){const oldCtx=this.ctxs.get(view);const prevLangs=oldCtx.langs;const newLangs=negotiateLanguages(meta,additionalLangs,prevLangs,requestedLangs);this.ctxs.set(view,this.env.createContext(newLangs.langs,oldCtx.resIds));return newLangs;}},{key:'requestLanguages',value:function requestLanguages(requestedLangs){this.broadcast('languageschangerequest',requestedLangs);}},{key:'getName',value:function getName(code){return pseudo[code].name;}},{key:'processString',value:function processString(code,str){return pseudo[code].process(str);}}]);return Remote;})();const observerConfig={attributes:true,characterData:false,childList:true,subtree:true,attributeFilter:['data-l10n-id','data-l10n-args']};const observers=new WeakMap();function initMutationObserver(view){observers.set(view,{roots:new Set(),observer:new MutationObserver(mutations=>translateMutations(view,mutations))});}
|
||||
function translateRoots(view){return Promise.all([...observers.get(view).roots].map(root=>_translateFragment(view,root)));}
|
||||
function observe(view,root){const obs=observers.get(view);if(obs){obs.roots.add(root);obs.observer.observe(root,observerConfig);}}
|
||||
function disconnect(view,root,allRoots){const obs=observers.get(view);if(obs){obs.observer.disconnect();if(allRoots){return;}
|
||||
obs.roots.delete(root);obs.roots.forEach(other=>obs.observer.observe(other,observerConfig));}}
|
||||
function reconnect(view){const obs=observers.get(view);if(obs){obs.roots.forEach(root=>obs.observer.observe(root,observerConfig));}}
|
||||
const reOverlay=/<|&#?\w+;/;const allowed={elements:['a','em','strong','small','s','cite','q','dfn','abbr','data','time','code','var','samp','kbd','sub','sup','i','b','u','mark','ruby','rt','rp','bdi','bdo','span','br','wbr'],attributes:{global:['title','aria-label','aria-valuetext','aria-moz-hint'],a:['download'],area:['download','alt'],input:['alt','placeholder'],menuitem:['label'],menu:['label'],optgroup:['label'],option:['label'],track:['label'],img:['alt'],textarea:['placeholder'],th:['abbr']}};function overlayElement(element,translation){const value=translation.value;if(typeof value==='string'){if(!reOverlay.test(value)){element.textContent=value;}else{const tmpl=element.ownerDocument.createElement('template');tmpl.innerHTML=value;overlay(element,tmpl.content);}}
|
||||
for(let key in translation.attrs){const attrName=camelCaseToDashed(key);if(isAttrAllowed({name:attrName},element)){element.setAttribute(attrName,translation.attrs[key]);}}}
|
||||
function overlay(sourceElement,translationElement){const result=translationElement.ownerDocument.createDocumentFragment();let k,attr;let childElement;while(childElement=translationElement.childNodes[0]){translationElement.removeChild(childElement);if(childElement.nodeType===childElement.TEXT_NODE){result.appendChild(childElement);continue;}
|
||||
const index=getIndexOfType(childElement);const sourceChild=getNthElementOfType(sourceElement,childElement,index);if(sourceChild){overlay(sourceChild,childElement);result.appendChild(sourceChild);continue;}
|
||||
if(isElementAllowed(childElement)){const sanitizedChild=childElement.ownerDocument.createElement(childElement.nodeName);overlay(sanitizedChild,childElement);result.appendChild(sanitizedChild);continue;}
|
||||
result.appendChild(translationElement.ownerDocument.createTextNode(childElement.textContent));}
|
||||
sourceElement.textContent='';sourceElement.appendChild(result);if(translationElement.attributes){for(k=0,attr;attr=translationElement.attributes[k];k++){if(isAttrAllowed(attr,sourceElement)){sourceElement.setAttribute(attr.name,attr.value);}}}}
|
||||
function isElementAllowed(element){return allowed.elements.indexOf(element.tagName.toLowerCase())!==-1;}
|
||||
function isAttrAllowed(attr,element){const attrName=attr.name.toLowerCase();const tagName=element.tagName.toLowerCase();if(allowed.attributes.global.indexOf(attrName)!==-1){return true;}
|
||||
if(!allowed.attributes[tagName]){return false;}
|
||||
if(allowed.attributes[tagName].indexOf(attrName)!==-1){return true;}
|
||||
if(tagName==='input'&&attrName==='value'){const type=element.type.toLowerCase();if(type==='submit'||type==='button'||type==='reset'){return true;}}
|
||||
return false;}
|
||||
function getNthElementOfType(context,element,index){let nthOfType=0;for(let i=0,child;child=context.children[i];i++){if(child.nodeType===child.ELEMENT_NODE&&child.tagName===element.tagName){if(nthOfType===index){return child;}
|
||||
nthOfType++;}}
|
||||
return null;}
|
||||
function getIndexOfType(element){let index=0;let child;while(child=element.previousElementSibling){if(child.tagName===element.tagName){index++;}}
|
||||
return index;}
|
||||
function camelCaseToDashed(string){if(string==='ariaValueText'){return'aria-valuetext';}
|
||||
return string.replace(/[A-Z]/g,function(match){return'-'+match.toLowerCase();}).replace(/^-/,'');}
|
||||
const reHtml=/[&<>]/g;const htmlEntities={'&':'&','<':'<','>':'>'};function setAttributes(element,id,args){element.setAttribute('data-l10n-id',id);if(args){element.setAttribute('data-l10n-args',JSON.stringify(args));}}
|
||||
function getAttributes(element){return{id:element.getAttribute('data-l10n-id'),args:JSON.parse(element.getAttribute('data-l10n-args'))};}
|
||||
function getTranslatables(element){const nodes=Array.from(element.querySelectorAll('[data-l10n-id]'));if(typeof element.hasAttribute==='function'&&element.hasAttribute('data-l10n-id')){nodes.push(element);}
|
||||
return nodes;}
|
||||
function translateMutations(view,mutations){const targets=new Set();for(let mutation of mutations){switch(mutation.type){case'attributes':targets.add(mutation.target);break;case'childList':for(let addedNode of mutation.addedNodes){if(addedNode.nodeType===addedNode.ELEMENT_NODE){if(addedNode.childElementCount){getTranslatables(addedNode).forEach(targets.add.bind(targets));}else{if(addedNode.hasAttribute('data-l10n-id')){targets.add(addedNode);}}}}
|
||||
break;}}
|
||||
if(targets.size===0){return;}
|
||||
translateElements(view,Array.from(targets));}
|
||||
function _translateFragment(view,frag){return translateElements(view,getTranslatables(frag));}
|
||||
function getElementsTranslation(view,elems){const keys=elems.map(elem=>{const id=elem.getAttribute('data-l10n-id');const args=elem.getAttribute('data-l10n-args');return args?[id,JSON.parse(args.replace(reHtml,match=>htmlEntities[match]))]:id;});return view.formatEntities(...keys);}
|
||||
function translateElements(view,elements){return getElementsTranslation(view,elements).then(translations=>applyTranslations(view,elements,translations));}
|
||||
function applyTranslations(view,elems,translations){disconnect(view,null,true);for(let i=0;i<elems.length;i++){overlayElement(elems[i],translations[i]);}
|
||||
reconnect(view);}
|
||||
if(typeof NodeList==='function'&&!NodeList.prototype[Symbol.iterator]){NodeList.prototype[Symbol.iterator]=Array.prototype[Symbol.iterator];}
|
||||
function documentReady(){if(document.readyState!=='loading'){return Promise.resolve();}
|
||||
return new Promise(resolve=>{document.addEventListener('readystatechange',function onrsc(){document.removeEventListener('readystatechange',onrsc);resolve();});});}
|
||||
function getDirection(code){const tag=code.split('-')[0];return['ar','he','fa','ps','ur'].indexOf(tag)>=0?'rtl':'ltr';}
|
||||
if(navigator.languages===undefined){navigator.languages=[navigator.language];}
|
||||
function getResourceLinks(head){return Array.prototype.map.call(head.querySelectorAll('link[rel="localization"]'),el=>el.getAttribute('href'));}
|
||||
function getMeta(head){let availableLangs=Object.create(null);let defaultLang=null;let appVersion=null;const metas=Array.from(head.querySelectorAll('meta[name="availableLanguages"],'+'meta[name="defaultLanguage"],'+'meta[name="appVersion"]'));for(let meta of metas){const name=meta.getAttribute('name');const content=meta.getAttribute('content').trim();switch(name){case'availableLanguages':availableLangs=getLangRevisionMap(availableLangs,content);break;case'defaultLanguage':const[lang,rev]=getLangRevisionTuple(content);defaultLang=lang;if(!(lang in availableLangs)){availableLangs[lang]=rev;}
|
||||
break;case'appVersion':appVersion=content;}}
|
||||
return{defaultLang,availableLangs,appVersion};}
|
||||
function getLangRevisionMap(seq,str){return str.split(',').reduce((seq,cur)=>{const[lang,rev]=getLangRevisionTuple(cur);seq[lang]=rev;return seq;},seq);}
|
||||
function getLangRevisionTuple(str){const[lang,rev]=str.trim().split(':');return[lang,parseInt(rev)];}
|
||||
const viewProps=new WeakMap();let View=(function(){function View(client,doc){_classCallCheck(this,View);this.pseudo={'fr-x-psaccent':createPseudo(this,'fr-x-psaccent'),'ar-x-psbidi':createPseudo(this,'ar-x-psbidi')};const initialized=documentReady().then(()=>init(this,client));this._interactive=initialized.then(()=>client);this.ready=initialized.then(langs=>translateView(this,langs));initMutationObserver(this);viewProps.set(this,{doc:doc,ready:false});client.on('languageschangerequest',requestedLangs=>this.requestLanguages(requestedLangs));}
|
||||
_createClass(View,[{key:'requestLanguages',value:function requestLanguages(requestedLangs,isGlobal){const method=isGlobal?client=>client.method('requestLanguages',requestedLangs):client=>changeLanguages(this,client,requestedLangs);return this._interactive.then(method);}},{key:'handleEvent',value:function handleEvent(){return this.requestLanguages(navigator.languages);}},{key:'formatEntities',value:function formatEntities(...keys){return this._interactive.then(client=>client.method('formatEntities',client.id,keys));}},{key:'formatValue',value:function formatValue(id,args){return this._interactive.then(client=>client.method('formatValues',client.id,[[id,args]])).then(values=>values[0]);}},{key:'formatValues',value:function formatValues(...keys){return this._interactive.then(client=>client.method('formatValues',client.id,keys));}},{key:'translateFragment',value:function translateFragment(frag){return _translateFragment(this,frag);}},{key:'observeRoot',value:function observeRoot(root){observe(this,root);}},{key:'disconnectRoot',value:function disconnectRoot(root){disconnect(this,root);}}]);return View;})();View.prototype.setAttributes=setAttributes;View.prototype.getAttributes=getAttributes;function createPseudo(view,code){return{getName:()=>view._interactive.then(client=>client.method('getName',code)),processString:str=>view._interactive.then(client=>client.method('processString',code,str))};}
|
||||
function init(view,client){const doc=viewProps.get(view).doc;const resources=getResourceLinks(doc.head);const meta=getMeta(doc.head);view.observeRoot(doc.documentElement);return getAdditionalLanguages().then(additionalLangs=>client.method('registerView',client.id,resources,meta,additionalLangs,navigator.languages));}
|
||||
function changeLanguages(view,client,requestedLangs){const doc=viewProps.get(view).doc;const meta=getMeta(doc.head);return getAdditionalLanguages().then(additionalLangs=>client.method('changeLanguages',client.id,meta,additionalLangs,requestedLangs)).then(({langs,haveChanged})=>haveChanged?translateView(view,langs):undefined);}
|
||||
function getAdditionalLanguages(){if(navigator.mozApps&&navigator.mozApps.getAdditionalLanguages){return navigator.mozApps.getAdditionalLanguages().catch(()=>Object.create(null));}
|
||||
return Promise.resolve(Object.create(null));}
|
||||
function translateView(view,langs){const props=viewProps.get(view);const html=props.doc.documentElement;if(props.ready){return translateRoots(view).then(()=>setAllAndEmit(html,langs));}
|
||||
const translated=langs[0].code===html.getAttribute('lang')?Promise.resolve():translateRoots(view).then(()=>setLangDir(html,langs));return translated.then(()=>{setLangs(html,langs);props.ready=true;});}
|
||||
function setLangs(html,langs){const codes=langs.map(lang=>lang.code);html.setAttribute('langs',codes.join(' '));}
|
||||
function setLangDir(html,langs){const code=langs[0].code;html.setAttribute('lang',code);html.setAttribute('dir',getDirection(code));}
|
||||
function setAllAndEmit(html,langs){setLangDir(html,langs);setLangs(html,langs);html.parentNode.dispatchEvent(new CustomEvent('DOMRetranslated',{bubbles:false,cancelable:false}));}
|
||||
const remote=new Remote(fetchResource,broadcast);const client=new Client(remote);document.l10n=new View(client,document);window.addEventListener('languagechange',document.l10n);document.addEventListener('additionallanguageschange',document.l10n);navigator.mozL10n={setAttributes:document.l10n.setAttributes,getAttributes:document.l10n.getAttributes,formatValue:(...args)=>document.l10n.formatValue(...args),translateFragment:(...args)=>document.l10n.translateFragment(...args),once:cb=>document.l10n.ready.then(cb),ready:cb=>document.l10n.ready.then(()=>{document.addEventListener('DOMRetranslated',cb);cb();})};})();
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
;'use strict';(function(exports){var BASE_TYPES=new Set(['text','url','email','password','number','option']);var SETTINGS_KEYS={ENABLED:'keyboard.enabled-layouts',DEFAULT:'keyboard.default-layouts'};var DEPRECATE_KEYBOARD_SETTINGS={en:'keyboard.layouts.english','en-Dvorak':'keyboard.layouts.dvorak',cs:'keyboard.layouts.czech',fr:'keyboard.layouts.french',de:'keyboard.layouts.german',hu:'keyboard.layouts.hungarian',nb:'keyboard.layouts.norwegian',my:'keyboard.layouts.myanmar',sl:'keyboard.layouts.slovak',tr:'keyboard.layouts.turkish',ro:'keyboard.layouts.romanian',ru:'keyboard.layouts.russian',ar:'keyboard.layouts.arabic',he:'keyboard.layouts.hebrew','zh-Hant-Zhuyin':'keyboard.layouts.zhuyin','zh-Hans-Pinyin':'keyboard.layouts.pinyin',el:'keyboard.layouts.greek','jp-kanji':'keyboard.layouts.japanese',pl:'keyboard.layouts.polish','pt-BR':'keyboard.layouts.portuguese',sr:'keyboard.layouts.serbian',es:'keyboard.layouts.spanish',ca:'keyboard.layouts.catalan'};var MULTI_LAYOUT_MAP={sr:['sr-Cyrl','sr-Latn']};var defaultKeyboardManifestURL='app://keyboard.gaiamobile.org/manifest.webapp';var currentSettings={defaultLayouts:{}};currentSettings.defaultLayouts[defaultKeyboardManifestURL]={en:true,number:true};currentSettings.enabledLayouts=map2dClone(currentSettings.defaultLayouts);function map2dIs(manifestURL,layoutId){return!!(this[manifestURL]&&this[manifestURL][layoutId]);}
|
||||
function map2dSet(manifestURL,layoutId){if(!this[manifestURL]){this[manifestURL]={};}
|
||||
this[manifestURL][layoutId]=true;}
|
||||
function map2dUnset(manifestURL,layoutId){if(!this[manifestURL]){return;}
|
||||
delete this[manifestURL][layoutId];if(!Object.keys(this[manifestURL]).length){delete this[manifestURL];}}
|
||||
function map2dClone(obj){var result={};for(var key1 in obj){result[key1]={};for(var key2 in obj[key1]){result[key1][key2]=obj[key1][key2];}}
|
||||
return result;}
|
||||
var watchQueries=[];var defaultLayoutConfig;function kh_updateWatchers(reason){if(watchQueries.length){KeyboardHelper.getApps(function withApps(){watchQueries.forEach(function eachWatchQuery(watch){KeyboardHelper.getLayouts(watch.query,function withLayouts(layouts){watch.callback(layouts,reason);});});});}}
|
||||
var waitingForSettings=[];var loadedSettings=new Set();function kh_loadedSetting(setting){loadedSettings.add(setting);if(loadedSettings.size>=Object.keys(SETTINGS_KEYS).length){waitingForSettings.forEach(function(callback){callback();});waitingForSettings=[];kh_updateWatchers({settings:true});}}
|
||||
function kh_withSettings(callback){if(loadedSettings.size>=Object.keys(SETTINGS_KEYS).length){callback();}else{waitingForSettings.push(callback);}}
|
||||
function kh_getSettings(){loadedSettings=new Set();var lock=window.navigator.mozSettings.createLock();lock.get(SETTINGS_KEYS.DEFAULT).onsuccess=kh_parseDefault;lock.get(SETTINGS_KEYS.ENABLED).onsuccess=kh_parseEnabled;}
|
||||
function kh_parseDefault(){var value=this.result[SETTINGS_KEYS.DEFAULT];if(value){currentSettings.defaultLayouts=value;}
|
||||
kh_loadedSetting(SETTINGS_KEYS.DEFAULT);}
|
||||
function kh_parseEnabled(){var value=this.result[SETTINGS_KEYS.ENABLED];var needsSave=false;if(!value){currentSettings.enabledLayouts=map2dClone(currentSettings.defaultLayouts);needsSave=true;}else{var type=typeof value;if(type==='string'){try{currentSettings.enabledLayouts={};var oldSettings=JSON.parse(value);oldSettings.forEach(function(layout){if(layout.enabled){var manifestURL=layout.manifestURL;if(!manifestURL){manifestURL=layout.appOrigin+'/manifest.webapp';}
|
||||
map2dSet.call(currentSettings.enabledLayouts,manifestURL,layout.layoutId);}});}catch(e){currentSettings.enabledLayouts=map2dClone(currentSettings.defaultLayouts);needsSave=true;}}else{currentSettings.enabledLayouts=value;}}
|
||||
kh_loadedSetting(SETTINGS_KEYS.ENABLED);if(needsSave){KeyboardHelper.saveToSettings();}}
|
||||
function kh_migrateDeprecatedSettings(deprecatedSettings){var settingEntry=DEPRECATE_KEYBOARD_SETTINGS.en;if(deprecatedSettings[settingEntry]===undefined){return;}
|
||||
currentSettings.enabledLayouts[defaultKeyboardManifestURL]={number:true};var hasEnabledLayout=false;for(var key in DEPRECATE_KEYBOARD_SETTINGS){settingEntry=DEPRECATE_KEYBOARD_SETTINGS[key];if(deprecatedSettings[settingEntry]){hasEnabledLayout=true;if(key in MULTI_LAYOUT_MAP){MULTI_LAYOUT_MAP[key].forEach(function enableLayout(layoutId){map2dSet.call(currentSettings.enabledLayouts,defaultKeyboardManifestURL,layoutId);});}else{map2dSet.call(currentSettings.enabledLayouts,defaultKeyboardManifestURL,key);}}}
|
||||
if(!hasEnabledLayout){map2dSet.call(currentSettings.enabledLayouts,defaultKeyboardManifestURL,'en');}
|
||||
var deprecatedSettingsQuery={};for(key in DEPRECATE_KEYBOARD_SETTINGS){settingEntry=DEPRECATE_KEYBOARD_SETTINGS[key];deprecatedSettingsQuery[settingEntry]=null;}
|
||||
window.navigator.mozSettings.createLock().set(deprecatedSettingsQuery);KeyboardHelper.saveToSettings();}
|
||||
function kh_loadJSON(href,callback){if(!callback){return;}
|
||||
var xhr=new XMLHttpRequest();xhr.onerror=function(){console.error('Failed to fetch file: '+href,xhr.statusText);};xhr.onload=function(){callback(xhr.response);};xhr.open('GET',href,true);xhr.responseType='json';xhr.send();}
|
||||
function kh_getMultiSettings(settings,callback){var results={};try{var lock=navigator.mozSettings.createLock();}
|
||||
catch(e){console.warn('Exception in mozSettings.createLock():',e,'\nUsing default values');for(var p in settings){results[p]=settings[p];}
|
||||
callback(results);}
|
||||
var settingNames=Object.keys(settings);var numSettings=settingNames.length;var numResults=0;for(var i=0;i<numSettings;i++){requestSetting(settingNames[i]);}
|
||||
function requestSetting(name){var request;try{request=lock.get(name);}
|
||||
catch(e){console.warn('Exception querying setting',name,':',e,'\nUsing default value');recordResult(name,settings[name]);return;}
|
||||
request.onsuccess=function(){var value=request.result[name];if(value===undefined){value=settings[name];}
|
||||
recordResult(name,value);};request.onerror=function(evt){console.warn('Error querying setting',name,':',evt.error);recordResult(name,settings[name]);};}
|
||||
function recordResult(name,value){results[name]=value;numResults++;if(numResults===numSettings){callback(results);}}}
|
||||
function KeyboardLayout(options){for(var key in options){this[key]=options[key];}}
|
||||
Object.defineProperties(KeyboardLayout.prototype,{'default':{get:function kh_getLayoutIsDefault(){return map2dIs.call(currentSettings.defaultLayouts,this.app.manifestURL,this.layoutId);}},enabled:{get:function kh_getLayoutIsEnabled(){return map2dIs.call(currentSettings.enabledLayouts,this.app.manifestURL,this.layoutId);},set:function kh_setLayoutIsDefault(value){var method=value?map2dSet:map2dUnset;method.call(currentSettings.enabledLayouts,this.app.manifestURL,this.layoutId);}}});var kh_SettingsHelper={};Object.defineProperties(kh_SettingsHelper,{'default':{get:function(){return map2dClone(currentSettings.defaultLayouts);},set:function(value){currentSettings.defaultLayouts=map2dClone(value);},enumerable:true},'enabled':{get:function(){return map2dClone(currentSettings.enabledLayouts);},set:function(value){currentSettings.enabledLayouts=map2dClone(value);},enumerable:true}});var KeyboardHelper=exports.KeyboardHelper={settings:kh_SettingsHelper,fallbackLayoutNames:{password:'en'},fallbackLayouts:{},inputAppList:null,init:function kh_init(){watchQueries=[];this.inputAppList=new InputAppList();this.inputAppList.onupdate=kh_updateWatchers.bind(undefined,{apps:true});this.inputAppList.start();var settings=window.navigator.mozSettings;if(settings){kh_getSettings();settings.addObserver(SETTINGS_KEYS.ENABLED,kh_getSettings);settings.addObserver(SETTINGS_KEYS.DEFAULT,kh_getSettings);var deprecatedSettingsQuery={};for(var key in DEPRECATE_KEYBOARD_SETTINGS){var settingEntry=DEPRECATE_KEYBOARD_SETTINGS[key];deprecatedSettingsQuery[settingEntry]=undefined;}
|
||||
kh_getMultiSettings(deprecatedSettingsQuery,kh_migrateDeprecatedSettings);}
|
||||
window.addEventListener('applicationinstallsuccess',this);},handleEvent:function(event){kh_updateWatchers({apps:true});},isKeyboardType:function kh_isKeyboardType(type){return BASE_TYPES.has(type);},setLayoutEnabled:function kh_setLayoutEnabled(manifestURL,layoutId,enabled){var method=enabled?map2dSet:map2dUnset;method.call(currentSettings.enabledLayouts,manifestURL,layoutId);},getLayoutEnabled:function kh_getLayoutEnabled(manifestURL,layoutId){return map2dIs.call(currentSettings.enabledLayouts,manifestURL,layoutId);},setLayoutIsDefault:function kh_setLayoutIsDefault(manifestURL,layoutId,enabled){var method=enabled?map2dSet:map2dUnset;method.call(currentSettings.defaultLayouts,manifestURL,layoutId);},getLayoutIsDefault:function kh_getLayoutIsDefault(manifestURL,layoutId){return map2dIs.call(currentSettings.defaultLayouts,manifestURL,layoutId);},checkDefaults:function kh_checkDefaults(callback){var layoutsEnabled=[];var missingTypes=[];['text','url','number'].forEach(function eachType(type){var enabled;this.getLayouts({type:type,enabled:true},function(layouts){enabled=layouts.length;});if(!enabled){missingTypes.push(type);this.getLayouts({type:type,'default':true},function(layouts){if(layouts[0]){layouts[0].enabled=true;layoutsEnabled.push(layouts[0]);}});}},this);if(layoutsEnabled.length){if(callback){callback(layoutsEnabled,missingTypes);}}},saveToSettings:function ke_saveToSettings(){var toSet={};toSet[SETTINGS_KEYS.ENABLED]=currentSettings.enabledLayouts;toSet[SETTINGS_KEYS.DEFAULT]=currentSettings.defaultLayouts;window.navigator.mozSettings.createLock().set(toSet);},getApps:function kh_getApps(callback){var cleanupSettings=function(inputApps){Object.keys(currentSettings.enabledLayouts).concat(Object.keys(currentSettings.defaultLayouts)).forEach(function(manifestURL){if(!inputApps.some(function(inputApp){return(inputApp.domApp.manifestURL===manifestURL);})){delete currentSettings.enabledLayouts[manifestURL];delete currentSettings.defaultLayouts[manifestURL];}});};if(this.inputAppList.ready){var inputApps=this.inputAppList.getListSync();cleanupSettings(inputApps);callback(inputApps);return;}
|
||||
this.inputAppList.getList().then(function(inputApps){cleanupSettings(inputApps);callback(inputApps);})['catch'](function(e){console.error(e);});},getLayouts:function kh_getLayouts(options,callback){if(typeof options==='function'){callback=options;options={};}
|
||||
function withApps(inputApps){var fallbackLayoutNames={};for(var group in this.fallbackLayoutNames){fallbackLayoutNames[group]=this.fallbackLayoutNames[group];}
|
||||
this.fallbackLayouts={};var layouts=inputApps.reduce(function eachApp(result,inputApp){var domApp=inputApp.domApp;var manifest=new ManifestHelper(domApp.manifest);var inputs=inputApp.getInputs();for(var layoutId in inputs){var inputManifest=inputs[layoutId];if(!inputManifest.types){console.warn(domApp.manifestURL,layoutId,'did not declare type.');continue;}
|
||||
var layout=new KeyboardLayout({app:domApp,inputApp:inputApp,manifest:manifest,inputManifest:inputManifest,layoutId:layoutId});if(domApp.manifestURL===defaultKeyboardManifestURL){for(var group in fallbackLayoutNames){if(layoutId===fallbackLayoutNames[group]){this.fallbackLayouts[group]=layout;delete fallbackLayoutNames[group];}}}
|
||||
if(options['default']&&!layout['default']){continue;}
|
||||
if(options.enabled&&!layout.enabled){continue;}
|
||||
if(options.type){options.type=[].concat(options.type);if(!options.type.some(function(type){return inputManifest.types.indexOf(type)!==-1;})){continue;}}
|
||||
result.push(layout);}
|
||||
if(options['default']){result.sort(function(a,b){return a.inputManifest.types.length-b.inputManifest.types.length;});}
|
||||
return result;}.bind(this),[]);kh_withSettings(callback.bind(null,layouts));}
|
||||
this.getApps(withApps.bind(this));},stopWatching:function(){watchQueries=[];},watchLayouts:function(query,callback){if(typeof query==='function'){callback=query;query={};}
|
||||
var watch={query:query,callback:callback};watchQueries.push(watch);this.getLayouts(query,function initialCall(layouts){watch.layouts=layouts;callback(layouts,{apps:true,settings:true});});},getDefaultLayoutConfig:function kh_getDefaultLayoutConfig(callback){if(!callback){return;}
|
||||
if(defaultLayoutConfig){callback(defaultLayoutConfig);}else{var KEYBOARDS='/shared/resources/keyboard_layouts.json';kh_loadJSON(KEYBOARDS,function loadKeyboardLayouts(data){if(data){defaultLayoutConfig=data;callback(defaultLayoutConfig);}});}},changeDefaultLayouts:function kh_changeDefaultLayouts(language,reset){this.getDefaultLayoutConfig(function gotDefaultLayouts(keyboards){var newKbLayouts=keyboards.layout[language];var kbManifestURL=defaultKeyboardManifestURL;currentSettings.defaultLayouts={};var langIndependentLayouts=keyboards.langIndependentLayouts;for(var i=langIndependentLayouts.length-1;i>=0;i--){this.setLayoutIsDefault(kbManifestURL,langIndependentLayouts[i].layoutId,true);}
|
||||
if(reset){currentSettings.enabledLayouts=map2dClone(currentSettings.defaultLayouts);}
|
||||
for(i=newKbLayouts.length-1;i>=0;i--){this.setLayoutIsDefault(kbManifestURL,newKbLayouts[i].layoutId,true);this.setLayoutEnabled(kbManifestURL,newKbLayouts[i].layoutId,true);}
|
||||
this.saveToSettings();}.bind(this));}};KeyboardHelper.init();}(window));
|
||||
245
assets/webapps/ftu.gaiamobile.org/shared/js/l10n.js
Normal file
245
assets/webapps/ftu.gaiamobile.org/shared/js/l10n.js
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
;;;(function(window,undefined){'use strict';function L10nError(message,id,loc){this.name='L10nError';this.message=message;this.id=id;this.loc=loc;}
|
||||
L10nError.prototype=Object.create(Error.prototype);L10nError.prototype.constructor=L10nError;var io={_load:function(type,url,callback,sync){var xhr=new XMLHttpRequest();var needParse;if(xhr.overrideMimeType){xhr.overrideMimeType(type);}
|
||||
xhr.open('GET',url,!sync);if(type==='application/json'){if(sync){needParse=true;}else{xhr.responseType='json';}}
|
||||
xhr.addEventListener('load',function io_onload(e){if(e.target.status===200||e.target.status===0){var res=e.target.response||e.target.responseText;callback(null,needParse?JSON.parse(res):res);}else{callback(new L10nError('Not found: '+url));}});xhr.addEventListener('error',callback);xhr.addEventListener('timeout',callback);try{xhr.send(null);}catch(e){if(e.name==='NS_ERROR_FILE_NOT_FOUND'){callback(new L10nError('Not found: '+url));}else{throw e;}}},load:function(url,callback,sync){return io._load('text/plain',url,callback,sync);},loadJSON:function(url,callback,sync){return io._load('application/json',url,callback,sync);}};function EventEmitter(){}
|
||||
EventEmitter.prototype.emit=function ee_emit(){if(!this._listeners){return;}
|
||||
var args=Array.prototype.slice.call(arguments);var type=args.shift();if(!this._listeners[type]){return;}
|
||||
var typeListeners=this._listeners[type].slice();for(var i=0;i<typeListeners.length;i++){typeListeners[i].apply(this,args);}};EventEmitter.prototype.addEventListener=function ee_add(type,listener){if(!this._listeners){this._listeners={};}
|
||||
if(!(type in this._listeners)){this._listeners[type]=[];}
|
||||
this._listeners[type].push(listener);};EventEmitter.prototype.removeEventListener=function ee_rm(type,listener){if(!this._listeners){return;}
|
||||
var typeListeners=this._listeners[type];var pos=typeListeners.indexOf(listener);if(pos===-1){return;}
|
||||
typeListeners.splice(pos,1);};function getPluralRule(lang){var locales2rules={'af':3,'ak':4,'am':4,'ar':1,'asa':3,'az':0,'be':11,'bem':3,'bez':3,'bg':3,'bh':4,'bm':0,'bn':3,'bo':0,'br':20,'brx':3,'bs':11,'ca':3,'cgg':3,'chr':3,'cs':12,'cy':17,'da':3,'de':3,'dv':3,'dz':0,'ee':3,'el':3,'en':3,'eo':3,'es':3,'et':3,'eu':3,'fa':0,'ff':5,'fi':3,'fil':4,'fo':3,'fr':5,'fur':3,'fy':3,'ga':8,'gd':24,'gl':3,'gsw':3,'gu':3,'guw':4,'gv':23,'ha':3,'haw':3,'he':2,'hi':4,'hr':11,'hu':0,'id':0,'ig':0,'ii':0,'is':3,'it':3,'iu':7,'ja':0,'jmc':3,'jv':0,'ka':0,'kab':5,'kaj':3,'kcg':3,'kde':0,'kea':0,'kk':3,'kl':3,'km':0,'kn':0,'ko':0,'ksb':3,'ksh':21,'ku':3,'kw':7,'lag':18,'lb':3,'lg':3,'ln':4,'lo':0,'lt':10,'lv':6,'mas':3,'mg':4,'mk':16,'ml':3,'mn':3,'mo':9,'mr':3,'ms':0,'mt':15,'my':0,'nah':3,'naq':7,'nb':3,'nd':3,'ne':3,'nl':3,'nn':3,'no':3,'nr':3,'nso':4,'ny':3,'nyn':3,'om':3,'or':3,'pa':3,'pap':3,'pl':13,'ps':3,'pt':3,'rm':3,'ro':9,'rof':3,'ru':11,'rwk':3,'sah':0,'saq':3,'se':7,'seh':3,'ses':0,'sg':0,'sh':11,'shi':19,'sk':12,'sl':14,'sma':7,'smi':7,'smj':7,'smn':7,'sms':7,'sn':3,'so':3,'sq':3,'sr':11,'ss':3,'ssy':3,'st':3,'sv':3,'sw':3,'syr':3,'ta':3,'te':3,'teo':3,'th':0,'ti':4,'tig':3,'tk':3,'tl':4,'tn':3,'to':0,'tr':0,'ts':3,'tzm':22,'uk':11,'ur':3,'ve':3,'vi':0,'vun':3,'wa':4,'wae':3,'wo':0,'xh':3,'xog':3,'yo':0,'zh':0,'zu':3};function isIn(n,list){return list.indexOf(n)!==-1;}
|
||||
function isBetween(n,start,end){return typeof n===typeof start&&start<=n&&n<=end;}
|
||||
var pluralRules={'0':function(){return'other';},'1':function(n){if((isBetween((n%100),3,10))){return'few';}
|
||||
if(n===0){return'zero';}
|
||||
if((isBetween((n%100),11,99))){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'2':function(n){if(n!==0&&(n%10)===0){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'3':function(n){if(n===1){return'one';}
|
||||
return'other';},'4':function(n){if((isBetween(n,0,1))){return'one';}
|
||||
return'other';},'5':function(n){if((isBetween(n,0,2))&&n!==2){return'one';}
|
||||
return'other';},'6':function(n){if(n===0){return'zero';}
|
||||
if((n%10)===1&&(n%100)!==11){return'one';}
|
||||
return'other';},'7':function(n){if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'8':function(n){if((isBetween(n,3,6))){return'few';}
|
||||
if((isBetween(n,7,10))){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'9':function(n){if(n===0||n!==1&&(isBetween((n%100),1,19))){return'few';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'10':function(n){if((isBetween((n%10),2,9))&&!(isBetween((n%100),11,19))){return'few';}
|
||||
if((n%10)===1&&!(isBetween((n%100),11,19))){return'one';}
|
||||
return'other';},'11':function(n){if((isBetween((n%10),2,4))&&!(isBetween((n%100),12,14))){return'few';}
|
||||
if((n%10)===0||(isBetween((n%10),5,9))||(isBetween((n%100),11,14))){return'many';}
|
||||
if((n%10)===1&&(n%100)!==11){return'one';}
|
||||
return'other';},'12':function(n){if((isBetween(n,2,4))){return'few';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'13':function(n){if((isBetween((n%10),2,4))&&!(isBetween((n%100),12,14))){return'few';}
|
||||
if(n!==1&&(isBetween((n%10),0,1))||(isBetween((n%10),5,9))||(isBetween((n%100),12,14))){return'many';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'14':function(n){if((isBetween((n%100),3,4))){return'few';}
|
||||
if((n%100)===2){return'two';}
|
||||
if((n%100)===1){return'one';}
|
||||
return'other';},'15':function(n){if(n===0||(isBetween((n%100),2,10))){return'few';}
|
||||
if((isBetween((n%100),11,19))){return'many';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'16':function(n){if((n%10)===1&&n!==11){return'one';}
|
||||
return'other';},'17':function(n){if(n===3){return'few';}
|
||||
if(n===0){return'zero';}
|
||||
if(n===6){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'18':function(n){if(n===0){return'zero';}
|
||||
if((isBetween(n,0,2))&&n!==0&&n!==2){return'one';}
|
||||
return'other';},'19':function(n){if((isBetween(n,2,10))){return'few';}
|
||||
if((isBetween(n,0,1))){return'one';}
|
||||
return'other';},'20':function(n){if((isBetween((n%10),3,4)||((n%10)===9))&&!(isBetween((n%100),10,19)||isBetween((n%100),70,79)||isBetween((n%100),90,99))){return'few';}
|
||||
if((n%1000000)===0&&n!==0){return'many';}
|
||||
if((n%10)===2&&!isIn((n%100),[12,72,92])){return'two';}
|
||||
if((n%10)===1&&!isIn((n%100),[11,71,91])){return'one';}
|
||||
return'other';},'21':function(n){if(n===0){return'zero';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'22':function(n){if((isBetween(n,0,1))||(isBetween(n,11,99))){return'one';}
|
||||
return'other';},'23':function(n){if((isBetween((n%10),1,2))||(n%20)===0){return'one';}
|
||||
return'other';},'24':function(n){if((isBetween(n,3,10)||isBetween(n,13,19))){return'few';}
|
||||
if(isIn(n,[2,12])){return'two';}
|
||||
if(isIn(n,[1,11])){return'one';}
|
||||
return'other';}};var index=locales2rules[lang.replace(/-.*$/,'')];if(!(index in pluralRules)){return function(){return'other';};}
|
||||
return pluralRules[index];}
|
||||
var MAX_PLACEABLES=100;var PropertiesParser={patterns:null,entryIds:null,init:function(){this.patterns={comment:/^\s*#|^\s*$/,entity:/^([^=\s]+)\s*=\s*(.*)$/,multiline:/[^\\]\\$/,index:/\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,unicode:/\\u([0-9a-fA-F]{1,4})/g,entries:/[^\r\n]+/g,controlChars:/\\([\\\n\r\t\b\f\{\}\"\'])/g,placeables:/\{\{\s*([^\s]*?)\s*\}\}/,};},parse:function(ctx,source){if(!this.patterns){this.init();}
|
||||
var ast=[];this.entryIds=Object.create(null);var entries=source.match(this.patterns.entries);if(!entries){return ast;}
|
||||
for(var i=0;i<entries.length;i++){var line=entries[i];if(this.patterns.comment.test(line)){continue;}
|
||||
while(this.patterns.multiline.test(line)&&i<entries.length){line=line.slice(0,-1)+entries[++i].trim();}
|
||||
var entityMatch=line.match(this.patterns.entity);if(entityMatch){try{this.parseEntity(entityMatch[1],entityMatch[2],ast);}catch(e){if(ctx){ctx._emitter.emit('parseerror',e);}else{throw e;}}}}
|
||||
return ast;},parseEntity:function(id,value,ast){var name,key;var pos=id.indexOf('[');if(pos!==-1){name=id.substr(0,pos);key=id.substring(pos+1,id.length-1);}else{name=id;key=null;}
|
||||
var nameElements=name.split('.');if(nameElements.length>2){throw new L10nError('Error in ID: "'+name+'".'+' Nested attributes are not supported.');}
|
||||
var attr;if(nameElements.length>1){name=nameElements[0];attr=nameElements[1];if(attr[0]==='$'){throw new L10nError('Attribute can\'t start with "$"',id);}}else{attr=null;}
|
||||
this.setEntityValue(name,attr,key,this.unescapeString(value),ast);},setEntityValue:function(id,attr,key,rawValue,ast){var pos,v;var value=rawValue.indexOf('{{')>-1?this.parseString(rawValue):rawValue;if(attr){pos=this.entryIds[id];if(pos===undefined){v={$i:id};if(key){v[attr]={};v[attr][key]=value;}else{v[attr]=value;}
|
||||
ast.push(v);this.entryIds[id]=ast.length-1;return;}
|
||||
if(key){if(typeof(ast[pos][attr])==='string'){ast[pos][attr]={$x:this.parseIndex(ast[pos][attr]),$v:{}};}
|
||||
ast[pos][attr].$v[key]=value;return;}
|
||||
ast[pos][attr]=value;return;}
|
||||
if(key){pos=this.entryIds[id];if(pos===undefined){v={};v[key]=value;ast.push({$i:id,$v:v});this.entryIds[id]=ast.length-1;return;}
|
||||
if(typeof(ast[pos].$v)==='string'){ast[pos].$x=this.parseIndex(ast[pos].$v);ast[pos].$v={};}
|
||||
ast[pos].$v[key]=value;return;}
|
||||
ast.push({$i:id,$v:value});this.entryIds[id]=ast.length-1;},parseString:function(str){var chunks=str.split(this.patterns.placeables);var complexStr=[];var len=chunks.length;var placeablesCount=(len-1)/2;if(placeablesCount>=MAX_PLACEABLES){throw new L10nError('Too many placeables ('+placeablesCount+', max allowed is '+MAX_PLACEABLES+')');}
|
||||
for(var i=0;i<chunks.length;i++){if(chunks[i].length===0){continue;}
|
||||
if(i%2===1){complexStr.push({t:'idOrVar',v:chunks[i]});}else{complexStr.push(chunks[i]);}}
|
||||
return complexStr;},unescapeString:function(str){if(str.lastIndexOf('\\')!==-1){str=str.replace(this.patterns.controlChars,'$1');}
|
||||
return str.replace(this.patterns.unicode,function(match,token){return unescape('%u'+'0000'.slice(token.length)+token);});},parseIndex:function(str){var match=str.match(this.patterns.index);if(!match){throw new L10nError('Malformed index');}
|
||||
if(match[2]){return[{t:'idOrVar',v:match[1]},match[2]];}else{return[{t:'idOrVar',v:match[1]}];}}};var KNOWN_MACROS=['plural'];var MAX_PLACEABLE_LENGTH=2500;var rePlaceables=/\{\{\s*(.+?)\s*\}\}/g;var nonLatin1=/[^\x01-\xFF]/;var FSI='\u2068';var PDI='\u2069';function createEntry(node,env){var keys=Object.keys(node);if(typeof node.$v==='string'&&keys.length===2){return node.$v;}
|
||||
var attrs;for(var i=0,key;key=keys[i];i++){if(key[0]==='$'){continue;}
|
||||
if(!attrs){attrs=Object.create(null);}
|
||||
attrs[key]=createAttribute(node[key],env,node.$i+'.'+key);}
|
||||
return{id:node.$i,value:node.$v!==undefined?node.$v:null,index:node.$x||null,attrs:attrs||null,env:env,dirty:false};}
|
||||
function createAttribute(node,env,id){if(typeof node==='string'){return node;}
|
||||
return{id:id,value:node.$v||(node!==undefined?node:null),index:node.$x||null,env:env,dirty:false};}
|
||||
function format(args,entity){if(typeof entity==='string'){return[{},entity];}
|
||||
if(entity.dirty){throw new L10nError('Cyclic reference detected: '+entity.id);}
|
||||
entity.dirty=true;var rv;try{rv=resolveValue({},args,entity.env,entity.value,entity.index);}finally{entity.dirty=false;}
|
||||
return rv;}
|
||||
function resolveIdentifier(args,env,id){if(KNOWN_MACROS.indexOf(id)>-1){return[{},env['__'+id]];}
|
||||
if(args&&args.hasOwnProperty(id)){if(typeof args[id]==='string'||(typeof args[id]==='number'&&!isNaN(args[id]))){return[{},args[id]];}else{throw new L10nError('Arg must be a string or a number: '+id);}}
|
||||
if(id in env&&id!=='__proto__'){return format(args,env[id]);}
|
||||
throw new L10nError('Unknown reference: '+id);}
|
||||
function subPlaceable(locals,args,env,id){var res;try{res=resolveIdentifier(args,env,id);}catch(err){return[{error:err},'{{ '+id+' }}'];}
|
||||
var value=res[1];if(typeof value==='number'){return res;}
|
||||
if(typeof value==='string'){if(value.length>=MAX_PLACEABLE_LENGTH){throw new L10nError('Too many characters in placeable ('+
|
||||
value.length+', max allowed is '+
|
||||
MAX_PLACEABLE_LENGTH+')');}
|
||||
if(locals.contextIsNonLatin1||value.match(nonLatin1)){res[1]=FSI+value+PDI;}
|
||||
return res;}
|
||||
return[{},'{{ '+id+' }}'];}
|
||||
function interpolate(locals,args,env,arr){return arr.reduce(function(prev,cur){if(typeof cur==='string'){return[prev[0],prev[1]+cur];}else if(cur.t==='idOrVar'){var placeable=subPlaceable(locals,args,env,cur.v);return[prev[0],prev[1]+placeable[1]];}},[locals,'']);}
|
||||
function resolveSelector(args,env,expr,index){var selectorName=index[0].v;var selector=resolveIdentifier(args,env,selectorName)[1];if(typeof selector!=='function'){return selector;}
|
||||
var argValue=index[1]?resolveIdentifier(args,env,index[1])[1]:undefined;if(selector===env.__plural){if(argValue===0&&'zero'in expr){return'zero';}
|
||||
if(argValue===1&&'one'in expr){return'one';}
|
||||
if(argValue===2&&'two'in expr){return'two';}}
|
||||
return selector(argValue);}
|
||||
function resolveValue(locals,args,env,expr,index){if(!expr){return[locals,expr];}
|
||||
if(typeof expr==='string'||typeof expr==='boolean'||typeof expr==='number'){return[locals,expr];}
|
||||
if(Array.isArray(expr)){locals.contextIsNonLatin1=expr.some(function($_){return typeof($_)==='string'&&$_.match(nonLatin1);});return interpolate(locals,args,env,expr);}
|
||||
if(index){var selector=resolveSelector(args,env,expr,index);if(expr.hasOwnProperty(selector)){return resolveValue(locals,args,env,expr[selector]);}}
|
||||
if('other'in expr){return resolveValue(locals,args,env,expr.other);}
|
||||
throw new L10nError('Unresolvable value');}
|
||||
var Resolver={createEntry:createEntry,format:format,rePlaceables:rePlaceables};function walkContent(node,fn){if(typeof node==='string'){return fn(node);}
|
||||
if(node.t==='idOrVar'){return node;}
|
||||
var rv=Array.isArray(node)?[]:{};var keys=Object.keys(node);for(var i=0,key;(key=keys[i]);i++){if(key==='$i'||key==='$x'){rv[key]=node[key];}else{rv[key]=walkContent(node[key],fn);}}
|
||||
return rv;}
|
||||
var reAlphas=/[a-zA-Z]/g;var reVowels=/[aeiouAEIOU]/g;var ACCENTED_MAP='\u0226\u0181\u0187\u1E12\u1E16\u0191\u0193\u0126\u012A'+'\u0134\u0136\u013F\u1E3E\u0220\u01FE\u01A4\u024A\u0158'+'\u015E\u0166\u016C\u1E7C\u1E86\u1E8A\u1E8E\u1E90'+'[\\]^_`'+'\u0227\u0180\u0188\u1E13\u1E17\u0192\u0260\u0127\u012B'+'\u0135\u0137\u0140\u1E3F\u019E\u01FF\u01A5\u024B\u0159'+'\u015F\u0167\u016D\u1E7D\u1E87\u1E8B\u1E8F\u1E91';var FLIPPED_MAP='\u2200\u0510\u2183p\u018E\u025F\u05E4HI\u017F'+'\u04FC\u02E5WNO\u0500\xD2\u1D1AS\u22A5\u2229\u0245'+'\uFF2DX\u028EZ'+'[\\]\u1D65_,'+'\u0250q\u0254p\u01DD\u025F\u0183\u0265\u0131\u027E'+'\u029E\u0285\u026Fuodb\u0279s\u0287n\u028C\u028Dx\u028Ez';function makeLonger(val){return val.replace(reVowels,function(match){return match+match.toLowerCase();});}
|
||||
function replaceChars(map,val){return val.replace(reAlphas,function(match){return map.charAt(match.charCodeAt(0)-65);});}
|
||||
var reWords=/[^\W0-9_]+/g;function makeRTL(val){return val.replace(reWords,function(match){return'\u202e'+match+'\u202c';});}
|
||||
var reExcluded=/(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;function mapContent(fn,val){if(!val){return val;}
|
||||
var parts=val.split(reExcluded);var modified=parts.map(function(part){if(reExcluded.test(part)){return part;}
|
||||
return fn(part);});return modified.join('');}
|
||||
function Pseudo(id,name,charMap,modFn){this.id=id;this.translate=mapContent.bind(null,function(val){return replaceChars(charMap,modFn(val));});this.name=this.translate(name);}
|
||||
var PSEUDO={'fr-x-psaccent':new Pseudo('fr-x-psaccent','Runtime Accented',ACCENTED_MAP,makeLonger),'ar-x-psbidi':new Pseudo('ar-x-psbidi','Runtime Bidi',FLIPPED_MAP,makeRTL)};function Locale(id,ctx){this.id=id;this.ctx=ctx;this.isReady=false;this.entries=Object.create(null);this.entries.__plural=getPluralRule(this.isPseudo()?this.ctx.defaultLocale:id);}
|
||||
Locale.prototype.isPseudo=function(){return this.ctx.qps.indexOf(this.id)!==-1;};var bindingsIO={extra:function(id,ver,path,type,callback,errback){if(type==='properties'){type='text';}
|
||||
navigator.mozApps.getLocalizationResource(id,ver,path,type).then(callback.bind(null,null),errback);},app:function(id,ver,path,type,callback,errback,sync){switch(type){case'properties':io.load(path,callback,sync);break;case'json':io.loadJSON(path,callback,sync);break;}},};Locale.prototype.build=function L_build(callback){var sync=!callback;var ctx=this.ctx;var self=this;var l10nLoads=ctx.resLinks.length;function onL10nLoaded(err){if(err){ctx._emitter.emit('fetcherror',err);}
|
||||
if(--l10nLoads<=0){self.isReady=true;if(callback){callback();}}}
|
||||
if(l10nLoads===0){onL10nLoaded();return;}
|
||||
function onJSONLoaded(err,json){if(!err&&json){self.addAST(json);}
|
||||
onL10nLoaded(err);}
|
||||
function onPropLoaded(err,source){if(!err&&source){var ast=PropertiesParser.parse(ctx,source);self.addAST(ast);}
|
||||
onL10nLoaded(err);}
|
||||
var idToFetch=this.isPseudo()?ctx.defaultLocale:this.id;var appVersion=null;var source='app';if(typeof(navigator)!=='undefined'){source=navigator.mozL10n._config.localeSources[this.id]||'app';appVersion=navigator.mozL10n._config.appVersion;}
|
||||
for(var i=0;i<ctx.resLinks.length;i++){var resLink=decodeURI(ctx.resLinks[i]);var path=resLink.replace('{locale}',idToFetch);var type=path.substr(path.lastIndexOf('.')+1);var cb;switch(type){case'json':cb=onJSONLoaded;break;case'properties':cb=onPropLoaded;break;}
|
||||
bindingsIO[source](this.id,appVersion,path,type,cb,onL10nLoaded,sync);}};function createPseudoEntry(node,entries){return Resolver.createEntry(walkContent(node,PSEUDO[this.id].translate),entries);}
|
||||
Locale.prototype.addAST=function(ast){var createEntry=this.isPseudo()?createPseudoEntry.bind(this):Resolver.createEntry;for(var i=0;i<ast.length;i++){this.entries[ast[i].$i]=createEntry(ast[i],this.entries);}};function Context(id){this.id=id;this.isReady=false;this.isLoading=false;this.defaultLocale='en-US';this.availableLocales=[];this.supportedLocales=[];this.qps=[];this.resLinks=[];this.locales={};this._emitter=new EventEmitter();this._ready=new Promise(this.once.bind(this));}
|
||||
function reportMissing(id,err){this._emitter.emit('notfounderror',err);return id;}
|
||||
function getWithFallback(id){var cur=0;var loc;var locale;while(loc=this.supportedLocales[cur]){locale=this.getLocale(loc);if(!locale.isReady){locale.build(null);}
|
||||
var entry=locale.entries[id];if(entry===undefined){cur++;reportMissing.call(this,id,new L10nError('"'+id+'"'+' not found in '+loc+' in '+this.id,id,loc));continue;}
|
||||
return entry;}
|
||||
throw new L10nError('"'+id+'"'+' missing from all supported locales in '+this.id,id);}
|
||||
function formatTuple(args,entity){try{return Resolver.format(args,entity);}catch(err){this._emitter.emit('resolveerror',err);var locals={error:err};return[locals,entity.id];}}
|
||||
function formatValue(args,entity){if(typeof entity==='string'){return entity;}
|
||||
return formatTuple.call(this,args,entity)[1];}
|
||||
function formatEntity(args,entity){var entityTuple=formatTuple.call(this,args,entity);var value=entityTuple[1];var formatted={value:value,attrs:null,};if(entity.attrs){formatted.attrs=Object.create(null);}
|
||||
for(var key in entity.attrs){var attrTuple=formatTuple.call(this,args,entity.attrs[key]);formatted.attrs[key]=attrTuple[1];}
|
||||
return formatted;}
|
||||
function formatAsync(fn,id,args){return this._ready.then(getWithFallback.bind(this,id)).then(fn.bind(this,args),reportMissing.bind(this,id));}
|
||||
Context.prototype.formatValue=function(id,args){return formatAsync.call(this,formatValue,id,args);};Context.prototype.formatEntity=function(id,args){return formatAsync.call(this,formatEntity,id,args);};function legacyGet(fn,id,args){if(!this.isReady){throw new L10nError('Context not ready');}
|
||||
var entry;try{entry=getWithFallback.call(this,id);}catch(err){if(err.loc){throw err;}
|
||||
reportMissing.call(this,id,err);return'';}
|
||||
return fn.call(this,args,entry);}
|
||||
Context.prototype.get=function(id,args){return legacyGet.call(this,formatValue,id,args);};Context.prototype.getEntity=function(id,args){return legacyGet.call(this,formatEntity,id,args);};Context.prototype.getLocale=function getLocale(code){var locales=this.locales;if(locales[code]){return locales[code];}
|
||||
return locales[code]=new Locale(code,this);};function negotiate(available,requested,defaultLocale){var supportedLocale;for(var i=0;i<requested.length;i++){var locale=requested[i];if(available.indexOf(locale)!==-1){supportedLocale=locale;break;}}
|
||||
if(!supportedLocale||supportedLocale===defaultLocale){return[defaultLocale];}
|
||||
return[supportedLocale,defaultLocale];}
|
||||
function freeze(supported){var locale=this.getLocale(supported[0]);if(locale.isReady){setReady.call(this,supported);}else{locale.build(setReady.bind(this,supported));}}
|
||||
function setReady(supported){this.supportedLocales=supported;this.isReady=true;this._emitter.emit('ready');}
|
||||
Context.prototype.registerLocales=function(defLocale,available){if(defLocale){this.defaultLocale=defLocale;}
|
||||
this.availableLocales=[this.defaultLocale];this.qps=Object.keys(PSEUDO);if(available){for(var i=0,loc;loc=available[i];i++){if(this.availableLocales.indexOf(loc)===-1){this.availableLocales.push(loc);var pos=this.qps.indexOf(loc);if(pos!==-1){this.qps.splice(pos,1);}}}}};Context.prototype.requestLocales=function requestLocales(){if(this.isLoading&&!this.isReady){throw new L10nError('Context not ready');}
|
||||
this.isLoading=true;var requested=Array.prototype.slice.call(arguments);if(requested.length===0){throw new L10nError('No locales requested');}
|
||||
var supported=negotiate(this.availableLocales.concat(this.qps),requested,this.defaultLocale);if(this.supportedLocales[0]!==supported[0]){freeze.call(this,supported);}};Context.prototype.addEventListener=function(type,listener){this._emitter.addEventListener(type,listener);};Context.prototype.removeEventListener=function(type,listener){this._emitter.removeEventListener(type,listener);};Context.prototype.ready=function(callback){if(this.isReady){setTimeout(callback);}
|
||||
this.addEventListener('ready',callback);};Context.prototype.once=function(callback){if(this.isReady){setTimeout(callback);return;}
|
||||
var callAndRemove=(function(){this.removeEventListener('ready',callAndRemove);callback();}).bind(this);this.addEventListener('ready',callAndRemove);};var allowed={elements:['a','em','strong','small','s','cite','q','dfn','abbr','data','time','code','var','samp','kbd','sub','sup','i','b','u','mark','ruby','rt','rp','bdi','bdo','span','br','wbr'],attributes:{global:['title','aria-label','aria-valuetext','aria-moz-hint'],a:['download'],area:['download','alt'],input:['alt','placeholder'],menuitem:['label'],menu:['label'],optgroup:['label'],option:['label'],track:['label'],img:['alt'],textarea:['placeholder'],th:['abbr']}};var rtlList=['ar','he','fa','ps','ar-x-psbidi','ur'];var nodeObserver=null;var pendingElements=null;var moConfig={attributes:true,characterData:false,childList:true,subtree:true,attributeFilter:['data-l10n-id','data-l10n-args']};navigator.mozL10n={ctx:null,get:function get(id,ctxdata){return navigator.mozL10n.ctx.get(id,ctxdata);},formatValue:function(id,ctxdata){return navigator.mozL10n.ctx.formatValue(id,ctxdata);},formatEntity:function(id,ctxdata){return navigator.mozL10n.ctx.formatEntity(id,ctxdata);},translateFragment:function(fragment){return translateFragment.call(navigator.mozL10n,fragment);},setAttributes:setL10nAttributes,getAttributes:getL10nAttributes,ready:function ready(callback){return navigator.mozL10n.ctx.ready(callback);},once:function once(callback){return navigator.mozL10n.ctx.once(callback);},get readyState(){return navigator.mozL10n.ctx.isReady?'complete':'loading';},language:{set code(lang){navigator.mozL10n.ctx.requestLocales(lang);},get code(){return navigator.mozL10n.ctx.supportedLocales[0];},get direction(){return getDirection(navigator.mozL10n.ctx.supportedLocales[0]);}},qps:PSEUDO,_config:{appVersion:null,localeSources:Object.create(null),isPretranslated:false,},_getInternalAPI:function(){return{Error:L10nError,Context:Context,Locale:Locale,Resolver:Resolver,getPluralRule:getPluralRule,rePlaceables:rePlaceables,translateDocument:translateDocument,onMetaInjected:onMetaInjected,PropertiesParser:PropertiesParser,walkContent:walkContent,buildLocaleList:buildLocaleList};}};function getDirection(lang){return(rtlList.indexOf(lang)>=0)?'rtl':'ltr';}
|
||||
var readyStates={loading:0,interactive:1,complete:2};function whenInteractive(callback){if(readyStates[document.readyState]>=readyStates.interactive){callback();return;}
|
||||
document.addEventListener('readystatechange',function l10n_onrsc(){if(readyStates[document.readyState]>=readyStates.interactive){document.removeEventListener('readystatechange',l10n_onrsc);callback();}});}
|
||||
function initObserver(){nodeObserver=new MutationObserver(onMutations.bind(navigator.mozL10n));nodeObserver.observe(document,moConfig);}
|
||||
function init(pretranslate){if(!pretranslate){initObserver();}
|
||||
initResources.call(navigator.mozL10n);}
|
||||
function initResources(){var meta={};var nodes=document.head.querySelectorAll('link[rel="localization"],'+'meta[name="availableLanguages"],'+'meta[name="defaultLanguage"],'+'meta[name="appVersion"],'+'script[type="application/l10n"]');for(var i=0,node;node=nodes[i];i++){var type=node.getAttribute('rel')||node.nodeName.toLowerCase();switch(type){case'localization':this.ctx.resLinks.push(node.getAttribute('href'));break;case'meta':onMetaInjected.call(this,node,meta);break;case'script':onScriptInjected.call(this,node);break;}}
|
||||
var additionalLanguagesPromise;if(navigator.mozApps&&navigator.mozApps.getAdditionalLanguages){additionalLanguagesPromise=navigator.mozApps.getAdditionalLanguages().catch(function(e){console.error('Error while loading getAdditionalLanguages',e);});document.addEventListener('additionallanguageschange',function(evt){registerLocales.call(this,meta,evt.detail);this.ctx.requestLocales.apply(this.ctx,navigator.languages||[navigator.language]);}.bind(this));}else{additionalLanguagesPromise=Promise.resolve();}
|
||||
additionalLanguagesPromise.then(function(extraLangs){registerLocales.call(this,meta,extraLangs);initLocale.call(this);}.bind(this));}
|
||||
function registerLocales(meta,extraLangs){var locales=buildLocaleList.call(this,meta,extraLangs);navigator.mozL10n._config.localeSources=locales[1];this.ctx.registerLocales(locales[0],Object.keys(locales[1]));}
|
||||
function getMatchingLangpack(appVersion,langpacks){for(var i=0,langpack;(langpack=langpacks[i]);i++){if(langpack.target===appVersion){return langpack;}}
|
||||
return null;}
|
||||
function buildLocaleList(meta,extraLangs){var loc,lp;var localeSources=Object.create(null);var defaultLocale=meta.defaultLanguage||this.ctx.defaultLocale;if(meta.availableLanguages){for(loc in meta.availableLanguages){localeSources[loc]='app';}}
|
||||
if(extraLangs){for(loc in extraLangs){lp=getMatchingLangpack(this._config.appVersion,extraLangs[loc]);if(!lp){continue;}
|
||||
if(!(loc in localeSources)||!meta.availableLanguages[loc]||parseInt(lp.revision)>meta.availableLanguages[loc]){localeSources[loc]='extra';}}}
|
||||
if(!(defaultLocale in localeSources)){localeSources[defaultLocale]='app';}
|
||||
return[defaultLocale,localeSources];}
|
||||
function splitAvailableLanguagesString(str){var langs={};str.split(',').forEach(function(lang){lang=lang.trim().split(':');langs[lang[0]]=parseInt(lang[1]);});return langs;}
|
||||
function onMetaInjected(node,meta){switch(node.getAttribute('name')){case'availableLanguages':meta.availableLanguages=splitAvailableLanguagesString(node.getAttribute('content'));break;case'defaultLanguage':meta.defaultLanguage=node.getAttribute('content');break;case'appVersion':navigator.mozL10n._config.appVersion=node.getAttribute('content');break;}}
|
||||
function onScriptInjected(node){var lang=node.getAttribute('lang');var locale=this.ctx.getLocale(lang);locale.addAST(JSON.parse(node.textContent));}
|
||||
function initLocale(){this.ctx.requestLocales.apply(this.ctx,navigator.languages||[navigator.language]);window.addEventListener('languagechange',function l10n_langchange(){this.ctx.requestLocales.apply(this.ctx,navigator.languages||[navigator.language]);}.bind(this));}
|
||||
function localizeMutations(mutations){var mutation;var targets=new Set();for(var i=0;i<mutations.length;i++){mutation=mutations[i];if(mutation.type==='childList'){var addedNode;for(var j=0;j<mutation.addedNodes.length;j++){addedNode=mutation.addedNodes[j];if(addedNode.nodeType!==Node.ELEMENT_NODE){continue;}
|
||||
targets.add(addedNode);}}
|
||||
if(mutation.type==='attributes'){targets.add(mutation.target);}}
|
||||
targets.forEach(function(target){if(target.childElementCount){translateFragment.call(this,target);}else if(target.hasAttribute('data-l10n-id')){translateElement.call(this,target);}},this);}
|
||||
function onMutations(mutations,self){self.disconnect();localizeMutations.call(this,mutations);self.observe(document,moConfig);}
|
||||
function onReady(){if(!navigator.mozL10n._config.isPretranslated){translateDocument.call(this);}
|
||||
navigator.mozL10n._config.isPretranslated=false;if(pendingElements){for(var i=0,element;element=pendingElements[i];i++){translateElement.call(this,element);}
|
||||
pendingElements=null;}
|
||||
if(!nodeObserver){initObserver();}
|
||||
fireLocalizedEvent.call(this);}
|
||||
function fireLocalizedEvent(){var event=new CustomEvent('localized',{'bubbles':false,'cancelable':false,'detail':{'language':this.ctx.supportedLocales[0]}});window.dispatchEvent(event);}
|
||||
var reOverlay=/<|&#?\w+;/;var reHtml=/[&<>]/g;var htmlEntities={'&':'&','<':'<','>':'>',};function translateDocument(){document.documentElement.lang=this.language.code;document.documentElement.dir=this.language.direction;translateFragment.call(this,document.documentElement);}
|
||||
function translateFragment(element){if(typeof element.hasAttribute==='function'&&element.hasAttribute('data-l10n-id')){translateElement.call(this,element);}
|
||||
var nodes=getTranslatableChildren(element);for(var i=0;i<nodes.length;i++){translateElement.call(this,nodes[i]);}}
|
||||
function setL10nAttributes(element,id,args){element.setAttribute('data-l10n-id',id);if(args){element.setAttribute('data-l10n-args',JSON.stringify(args));}}
|
||||
function getL10nAttributes(element){return{id:element.getAttribute('data-l10n-id'),args:JSON.parse(element.getAttribute('data-l10n-args'))};}
|
||||
function getTranslatableChildren(element){return element?element.querySelectorAll('*[data-l10n-id]'):[];}
|
||||
function camelCaseToDashed(string){if(string==='ariaValueText'){return'aria-valuetext';}
|
||||
return string.replace(/[A-Z]/g,function(match){return'-'+match.toLowerCase();}).replace(/^-/,'');}
|
||||
function escapeL10nArgs(match){return htmlEntities[match];}
|
||||
function translateElement(element){if(!this.ctx.isReady){if(!pendingElements){pendingElements=[];}
|
||||
pendingElements.push(element);return;}
|
||||
var l10nId=element.getAttribute('data-l10n-id');if(!l10nId){return false;}
|
||||
var l10nArgs=element.getAttribute('data-l10n-args');var entity=this.ctx.getEntity(l10nId,l10nArgs?JSON.parse(l10nArgs.replace(reHtml,escapeL10nArgs)):undefined);var value=entity.value;if(typeof value==='string'){if(!reOverlay.test(value)){element.textContent=value;}else{var translation=element.ownerDocument.createElement('template');translation.innerHTML=value;overlayElement(element,translation.content);}}
|
||||
for(var key in entity.attrs){var attrName=camelCaseToDashed(key);if(isAttrAllowed({name:attrName},element)){element.setAttribute(attrName,entity.attrs[key]);}}}
|
||||
function overlayElement(sourceElement,translationElement){var result=translationElement.ownerDocument.createDocumentFragment();var k,attr;var childElement;while((childElement=translationElement.childNodes[0])){translationElement.removeChild(childElement);if(childElement.nodeType===Node.TEXT_NODE){result.appendChild(childElement);continue;}
|
||||
var index=getIndexOfType(childElement);var sourceChild=getNthElementOfType(sourceElement,childElement,index);if(sourceChild){overlayElement(sourceChild,childElement);result.appendChild(sourceChild);continue;}
|
||||
if(isElementAllowed(childElement)){var sanitizedChild=childElement.ownerDocument.createElement(childElement.nodeName);overlayElement(sanitizedChild,childElement);result.appendChild(sanitizedChild);continue;}
|
||||
result.appendChild(document.createTextNode(childElement.textContent));}
|
||||
sourceElement.textContent='';sourceElement.appendChild(result);if(translationElement.attributes){for(k=0,attr;(attr=translationElement.attributes[k]);k++){if(isAttrAllowed(attr,sourceElement)){sourceElement.setAttribute(attr.name,attr.value);}}}}
|
||||
function isElementAllowed(element){return allowed.elements.indexOf(element.tagName.toLowerCase())!==-1;}
|
||||
function isAttrAllowed(attr,element){var attrName=attr.name.toLowerCase();var tagName=element.tagName.toLowerCase();if(allowed.attributes.global.indexOf(attrName)!==-1){return true;}
|
||||
if(!allowed.attributes[tagName]){return false;}
|
||||
if(allowed.attributes[tagName].indexOf(attrName)!==-1){return true;}
|
||||
if(tagName==='input'&&attrName==='value'){var type=element.type.toLowerCase();if(type==='submit'||type==='button'||type==='reset'){return true;}}
|
||||
return false;}
|
||||
function getNthElementOfType(context,element,index){var nthOfType=0;for(var i=0,child;child=context.children[i];i++){if(child.nodeType===Node.ELEMENT_NODE&&child.tagName===element.tagName){if(nthOfType===index){return child;}
|
||||
nthOfType++;}}
|
||||
return null;}
|
||||
function getIndexOfType(element){var index=0;var child;while((child=element.previousElementSibling)){if(child.tagName===element.tagName){index++;}}
|
||||
return index;}
|
||||
var DEBUG=false;navigator.mozL10n.ctx=new Context(window.document?document.URL:null);navigator.mozL10n.ctx.ready(onReady.bind(navigator.mozL10n));navigator.mozL10n.ctx.addEventListener('notfounderror',function reportMissingEntity(e){if(DEBUG||e.loc==='en-US'){console.warn(e.toString());}});if(DEBUG){navigator.mozL10n.ctx.addEventListener('fetcherror',console.error.bind(console));navigator.mozL10n.ctx.addEventListener('parseerror',console.error.bind(console));navigator.mozL10n.ctx.addEventListener('resolveerror',console.error.bind(console));}
|
||||
if(window.document){navigator.mozL10n._config.isPretranslated=document.documentElement.lang===navigator.language;var forcePretranslate=!navigator.mozL10n._config.isPretranslated;whenInteractive(init.bind(navigator.mozL10n,forcePretranslate));}
|
||||
document.l10n={setAttributes:navigator.mozL10n.setAttributes,getAttributes:navigator.mozL10n.getAttributes,formatValue:function(id,args){return navigator.mozL10n.formatValue(id,args);},translateFragment:function(frag){return Promise.resolve(navigator.mozL10n.translateFragment(frag));},ready:new Promise(function(resolve){navigator.mozL10n.once(resolve);}),formatValues:function(){var keys=arguments;var resp=keys.map(function(key){if(Array.isArray(key)){return navigator.mozL10n.formatValue(key[0],key[1]);}
|
||||
return navigator.mozL10n.formatValue(key);});return Promise.all(resp);},requestLanguages:function(langs){navigator.mozL10n.ctx.requestLocales.apply(navigator.mozL10n.ctx,langs);},pseudo:{'fr-x-psaccent':{getName:function(){return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].name);},processString:function(s){return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].translate(s));}},'ar-x-psbidi':{getName:function(){return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].name);},processString:function(s){return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].translate(s));}}},};navigator.mozL10n.ready(function(){document.documentElement.setAttribute('langs',navigator.mozL10n.ctx.supportedLocales.join(' '));});navigator.mozL10n.once(function(){window.addEventListener('localized',function(){document.dispatchEvent(new CustomEvent('DOMRetranslated',{bubbles:false,cancelable:false}));});});})(this);
|
||||
10
assets/webapps/ftu.gaiamobile.org/shared/js/language_list.js
Normal file
10
assets/webapps/ftu.gaiamobile.org/shared/js/language_list.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
;'use strict';(function(exports){var LOCALES_FILE='/shared/resources/languages.json';function readFile(file,callback){return new Promise(function(resolve,reject){var xhr=new XMLHttpRequest();xhr.onreadystatechange=function loadFile(){if(xhr.readyState===4){if(xhr.status===0||xhr.status===200){resolve(xhr.response);}else{reject(xhr.statusText);}}};xhr.open('GET',file,true);xhr.responseType='json';xhr.send();});}
|
||||
function onError(name){console.error('Error checking setting '+name);}
|
||||
function readSetting(name,callback){var settings=window.navigator.mozSettings;if(!settings||!settings.createLock){return callback(null);}
|
||||
return settings.createLock().get(name).then(function(res){return res[name];},onError.bind(null,name));}
|
||||
exports.LanguageList={_languages:null,_readFile:readFile,_readSetting:readSetting,_extendPseudo:function(languages,currentLang,pseudoEnabled){if(!pseudoEnabled){for(var code in languages){var isCurrent=(code===currentLang);if(code.indexOf('-x-ps')>-1&&!isCurrent){delete languages[code];}}}
|
||||
if(!document.l10n){return Promise.resolve(languages);}
|
||||
var runtimePseudoCodes=Object.keys(document.l10n.pseudo).filter(code=>!(code in languages)&&(code===currentLang||pseudoEnabled));function obj(arr1,arr2){const zipped=[];arr1.forEach(x=>{arr2.forEach(y=>{zipped.push([x,y]);});});return zipped.reduce((obj,[x,y])=>Object.assign(obj,{[x]:y}),{});}
|
||||
return Promise.all(runtimePseudoCodes.map(code=>document.l10n.pseudo[code].getName())).then(names=>Object.assign(languages,obj(runtimePseudoCodes,names)));},_extendAdditional:function(languages,ver,additional){for(var lang in additional){for(var i=0,locale;locale=additional[lang][i];i++){if(locale.target===ver){languages[lang]=locale.name;break;}}}},_removeWithNoSpeech:function(languages,srEnabled){if(srEnabled){var speechLangs=new Set(window.speechSynthesis.getVoices().map(v=>v.lang.split('-')[0]));for(var langName in languages){if(!speechLangs.has(langName.split('-')[0])){delete languages[langName];}}}},_copyObj:function(obj){var copy=Object.create(null);for(var prop in obj){copy[prop]=obj[prop];}
|
||||
return copy;},_parseVersion:function(ver){return ver.split('.').slice(0,2).join('.');},_build:function(){return Promise.all([this._languages||(this._languages=this._readFile(LOCALES_FILE)),this._readSetting('language.current'),this._readSetting('devtools.pseudolocalization.enabled')]).then(([langsFromFile,current,pseudoEnabled])=>{var langs=this._copyObj(langsFromFile);return Promise.all([this._extendPseudo(langs,current,pseudoEnabled),this._readSetting('langpack.channel'),current,this._readSetting('accessibility.screenreader'),navigator.mozApps.getAdditionalLanguages()]);}).then(([langs,ver,current,srEnabled,addl])=>{this._extendAdditional(langs,this._parseVersion(ver),addl);this._removeWithNoSpeech(langs,srEnabled);return[langs,current];});},get:function(callback){if(!callback){return;}
|
||||
this._build().then(Function.prototype.apply.bind(callback,null));},wrapBidi:function(langCode,langName){var rtlList=['ar','he','fa','ps','ar-x-psbidi','ur'];var lEmbedBegin=(rtlList.indexOf(langCode)>=0)?'\u202b':'\u202a';var lEmbedEnd='\u202c';return lEmbedBegin+langName+lEmbedEnd;}};}(window));
|
||||
11
assets/webapps/ftu.gaiamobile.org/shared/js/lazy_loader.js
Normal file
11
assets/webapps/ftu.gaiamobile.org/shared/js/lazy_loader.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
;;;'use strict';var LazyLoader=(function(){function LazyLoader(){this._loaded={};this._isLoading={};}
|
||||
LazyLoader.prototype={_js:function(file,callback){var script=document.createElement('script');script.src=file;script.async=false;script.addEventListener('load',callback);document.head.appendChild(script);this._isLoading[file]=script;},_css:function(file,callback){var style=document.createElement('link');style.type='text/css';style.rel='stylesheet';style.href=file;document.head.appendChild(style);callback();},_html:function(domNode,callback){if(domNode.getAttribute('is')){this.load(['/shared/js/html_imports.js'],function(){HtmlImports.populate(callback);}.bind(this));return;}
|
||||
for(var i=0;i<domNode.childNodes.length;i++){if(domNode.childNodes[i].nodeType==document.COMMENT_NODE){domNode.innerHTML=domNode.childNodes[i].nodeValue;break;}}
|
||||
window.dispatchEvent(new CustomEvent('lazyload',{detail:domNode}));callback();},getJSON:function(file,mozSystem){return new Promise(function(resolve,reject){var xhr;if(mozSystem){xhr=new XMLHttpRequest({mozSystem:true});}else{xhr=new XMLHttpRequest();}
|
||||
xhr.open('GET',file,true);xhr.responseType='json';xhr.onerror=function(error){reject(error);};xhr.onload=function(){if(xhr.response!==null){resolve(xhr.response);}else{reject(new Error('No valid JSON object was found ('+
|
||||
xhr.status+' '+xhr.statusText+')'));}};xhr.send();});},load:function(files,callback){var deferred={};deferred.promise=new Promise(resolve=>{deferred.resolve=resolve;});if(!Array.isArray(files)){files=[files];}
|
||||
var loadsRemaining=files.length,self=this;function perFileCallback(file){if(self._isLoading[file]){delete self._isLoading[file];}
|
||||
self._loaded[file]=true;if(--loadsRemaining===0){deferred.resolve();if(callback){callback();}}}
|
||||
for(var i=0;i<files.length;i++){var file=files[i];if(this._loaded[file.id||file]){perFileCallback(file);}else if(this._isLoading[file]){this._isLoading[file].addEventListener('load',perFileCallback.bind(null,file));}else{var method,idx;if(typeof file==='string'){method=file.match(/\.([^.]+)$/)[1];idx=file;}else{method='html';idx=file.id;}
|
||||
this['_'+method](file,perFileCallback.bind(null,idx));}}
|
||||
return deferred.promise;}};return new LazyLoader();}());
|
||||
135
assets/webapps/ftu.gaiamobile.org/shared/js/mime_mapper.js
Normal file
135
assets/webapps/ftu.gaiamobile.org/shared/js/mime_mapper.js
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/* exported MimeMapper */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* MimeMapper helps gaia apps to decide the mapping of mimetype and extension.
|
||||
* The use cases often happen when apps need to know about the exact
|
||||
* mimetypes or extensions, such as to delegate the open web activity, we must
|
||||
* have suitable mimetypes or extensions to request the right activity
|
||||
*
|
||||
* The mapping is basically created according to:
|
||||
* http://en.wikipedia.org/wiki/Internet_media_type
|
||||
*
|
||||
* The supported formats are considered base on the deviceStorage properties:
|
||||
* http://dxr.mozilla.org/mozilla-central/toolkit/content/
|
||||
* devicestorage.properties
|
||||
*
|
||||
*/
|
||||
|
||||
var MimeMapper = {
|
||||
// This list only contains the extensions we currently supported
|
||||
// We should make it more complete for further usages
|
||||
_typeToExtensionMap: {
|
||||
// Image
|
||||
'image/jpeg': 'jpg',
|
||||
'image/png': 'png',
|
||||
'image/gif': 'gif',
|
||||
'image/bmp': 'bmp',
|
||||
// Audio
|
||||
'audio/mpeg': 'mp3',
|
||||
'audio/mp4': 'm4a',
|
||||
'audio/ogg': 'ogg',
|
||||
'audio/webm': 'webm',
|
||||
'audio/3gpp': '3gp',
|
||||
'audio/amr': 'amr',
|
||||
// Video
|
||||
'video/mp4': 'mp4',
|
||||
'video/mpeg': 'mpg',
|
||||
'video/ogg': 'ogg',
|
||||
'video/webm': 'webm',
|
||||
'video/3gpp': '3gp',
|
||||
'video/3gpp2': '3g2',
|
||||
// Application
|
||||
// If we want to support some types, like pdf, just add
|
||||
'application/pdf': 'pdf',
|
||||
'application/vcard': 'vcf',
|
||||
// Text
|
||||
'text/vcard': 'vcf',
|
||||
'text/x-vcard': 'vcf'
|
||||
},
|
||||
|
||||
// This list only contains the mimetypes we currently supported
|
||||
// We should make it more complete for further usages
|
||||
_extensionToTypeMap: {
|
||||
// Image
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'jpe': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'bmp': 'image/bmp',
|
||||
// Audio
|
||||
'mp3': 'audio/mpeg',
|
||||
'm4a': 'audio/mp4',
|
||||
'm4b': 'audio/mp4',
|
||||
'm4p': 'audio/mp4',
|
||||
'm4r': 'audio/mp4',
|
||||
'aac': 'audio/aac',
|
||||
'opus': 'audio/ogg',
|
||||
'amr': 'audio/amr',
|
||||
// Video
|
||||
'mp4': 'video/mp4',
|
||||
'mpeg': 'video/mpeg',
|
||||
'mpg': 'video/mpeg',
|
||||
'ogv': 'video/ogg',
|
||||
'ogx': 'video/ogg',
|
||||
'webm': 'video/webm',
|
||||
'3gp': 'video/3gpp',
|
||||
'3g2': 'video/3gpp2',
|
||||
'ogg': 'video/ogg',
|
||||
// Application
|
||||
// If we want to support some extensions, like pdf, just add
|
||||
'pdf': 'application/pdf',
|
||||
// Text
|
||||
'vcf': 'text/vcard'
|
||||
},
|
||||
_parseExtension: function(filename) {
|
||||
var array = filename.split('.');
|
||||
return array.length > 1 ? array.pop() : '';
|
||||
},
|
||||
|
||||
isSupportedType: function(mimetype) {
|
||||
return (mimetype in this._typeToExtensionMap);
|
||||
},
|
||||
|
||||
isSupportedExtension: function(extension) {
|
||||
return (extension in this._extensionToTypeMap);
|
||||
},
|
||||
|
||||
isFilenameMatchesType: function(filename, mimetype) {
|
||||
var extension = this._parseExtension(filename);
|
||||
var guessedType = this.guessTypeFromExtension(extension);
|
||||
return (guessedType == mimetype);
|
||||
},
|
||||
|
||||
guessExtensionFromType: function(mimetype) {
|
||||
return this._typeToExtensionMap[mimetype];
|
||||
},
|
||||
|
||||
guessTypeFromExtension: function(extension) {
|
||||
return this._extensionToTypeMap[extension];
|
||||
},
|
||||
|
||||
// If mimetype is not in the supported list, we will try to
|
||||
// predict the possible valid mimetype based on extension.
|
||||
guessTypeFromFileProperties: function(filename, mimetype) {
|
||||
var extension = this._parseExtension(filename);
|
||||
var type = this.isSupportedType(mimetype) ?
|
||||
mimetype : this.guessTypeFromExtension(extension);
|
||||
return type || '';
|
||||
},
|
||||
|
||||
// if mimetype is not supported, preserve the original extension
|
||||
// and add the predict result as new extension.
|
||||
// If both filename and mimetype are not supported, return the original
|
||||
// filename.
|
||||
ensureFilenameMatchesType: function(filename, mimetype) {
|
||||
if (!this.isFilenameMatchesType(filename, mimetype)) {
|
||||
var guessedExt = this.guessExtensionFromType(mimetype);
|
||||
if (guessedExt) {
|
||||
filename += '.' + guessedExt;
|
||||
}
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
;'use strict';var MobileInfo={brazil:{carriers:{'00':'NEXTEL','02':'TIM','03':'TIM','04':'TIM','05':'CLARO','06':'VIVO','07':'CTBC','08':'TIM','10':'VIVO','11':'VIVO','15':'SERCOMTEL','16':'OI','23':'VIVO','24':'OI','31':'OI','32':'CTBC','33':'CTBC','34':'CTBC','37':'AEIOU'},regions:{'11':'SP','12':'SP','13':'SP','14':'SP','15':'SP','16':'SP','17':'SP','18':'SP','19':'SP','21':'RJ','22':'RJ','24':'RJ','27':'ES','28':'ES','31':'MG','32':'MG','33':'MG','34':'MG','35':'MG','37':'MG','38':'MG','41':'PR','42':'PR','43':'PR','44':'PR','45':'PR','46':'PR','47':'SC','48':'SC','49':'SC','51':'RS','53':'RS','54':'RS','55':'RS','61':'DF','62':'GO','63':'TO','64':'GO','65':'MT','66':'MT','67':'MS','68':'AC','69':'RO','71':'BA','73':'BA','74':'BA','75':'BA','77':'BA','79':'SE','81':'PE','82':'AL','83':'PB','84':'RN','85':'CE','86':'PI','87':'PE','88':'CE','89':'PI','91':'PA','92':'AM','93':'PA','94':'PA','95':'RR','96':'AP','97':'AM','98':'MA','99':'MA'}}};var MobileOperator={BRAZIL_MCC:'724',BRAZIL_CELLBROADCAST_CHANNEL:50,userFacingInfo:function mo_userFacingInfo(mobileConnection){var network=mobileConnection.voice.network;var iccid=mobileConnection.iccId;var iccObj=navigator.mozIccManager.getIccById(iccid);var iccInfo=iccObj?iccObj.iccInfo:null;var operator=network?(network.shortName||network.longName):null;if(operator&&iccInfo&&iccInfo.isDisplaySpnRequired&&iccInfo.spn){if(iccInfo.isDisplayNetworkNameRequired&&operator!==iccInfo.spn){operator=operator+' '+iccInfo.spn;}else{operator=iccInfo.spn;}}
|
||||
var carrier,region;if(this.isBrazil(mobileConnection)){var lac=mobileConnection.voice.cell.gsmLocationAreaCode%100;var carriers=MobileInfo.brazil.carriers;var regions=MobileInfo.brazil.regions;carrier=carriers[network.mnc]||(this.BRAZIL_MCC+network.mnc);region=(regions[lac]?regions[lac]+' '+lac:'');}
|
||||
return{'operator':operator,'carrier':carrier,'region':region};},isBrazil:function mo_isBrazil(mobileConnection){var cell=mobileConnection.voice.cell;var net=mobileConnection.voice.network;return net?(net.mcc===this.BRAZIL_MCC&&cell&&cell.gsmLocationAreaCode):null;}};
|
||||
2
assets/webapps/ftu.gaiamobile.org/shared/js/sanitizer.js
Normal file
2
assets/webapps/ftu.gaiamobile.org/shared/js/sanitizer.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
;(function(root,factory){'use strict';if(typeof define==='function'&&define.amd){define(factory);}else if(typeof exports==='object'){module.exports=factory();}else{root.Sanitizer=factory();}}(this,function(){'use strict';var Sanitizer={_entity:/[&<>"'/]/g,_entities:{'&':'&','<':'<','>':'>','"':'"','\'':''','/':'/'},getEntity:function(s){return Sanitizer._entities[s];},escapeHTML:function(strings,...values){var result='';for(var i=0;i<strings.length;i++){result+=strings[i];if(i<values.length){result+=String(values[i]).replace(Sanitizer._entity,Sanitizer.getEntity);}}
|
||||
return result;},createSafeHTML:function(strings,...values){var escaped=Sanitizer.escapeHTML(strings,...values);return{__html:escaped,toString:function(){return'[object WrappedHTMLObject]';},info:'This is a wrapped HTML object. See https://developer.mozilla.or'+'g/en-US/Firefox_OS/Security/Security_Automation for more.'};},unwrapSafeHTML:function(...htmlObjects){var markupList=htmlObjects.map(function(obj){return obj.__html;});return markupList.join('');}};return Sanitizer;}));
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
;'use strict';var ScreenLayout={defaultQueries:{tiny:'(max-width: 767px)',small:'(min-width: 768px) and (max-width: 991px)',medium:'(min-width: 992px) and (max-width: 1200px)',large:'(min-width: 1201px)',hardwareHomeButton:window.VulpesCompat?'(min-width: 0px)':'(-moz-physical-home-button)'},init:function sl_init(){this.queries=(function(qs){var result={};for(var key in qs){result[key]=window.matchMedia(qs[key]);}
|
||||
return result;})(this.defaultQueries);},_isOnRealDevice:undefined,isOnRealDevice:function sl_isOnRealDevice(){if(typeof(this._isOnRealDevice)!=='undefined'){return this._isOnRealDevice;}
|
||||
if(window.innerWidth===screen.availWidth){this._isOnRealDevice=true;}else{this._isOnRealDevice=false;}
|
||||
return this._isOnRealDevice;},getCurrentLayout:function sl_getCurrentLayout(type){if(type===undefined){for(var name in this.defaultQueries){if(this.queries[name]&&this.queries[name].matches){return name;}}}
|
||||
if(typeof this.queries[type]!=='undefined'){return this.queries[type].matches;}
|
||||
return false;},watch:function sl_watch(name,media){var mediaString=media||this.queries[name].media;if(!mediaString){return;}
|
||||
this.unwatch(name);this.queries[name]=window.matchMedia(mediaString);this.boundHandleChange=this.handleChange.bind(this);this.queries[name].addListener(this.boundHandleChange);},unwatch:function sl_unwatch(name){if(this.queries[name]){this.queries[name].removeListener(this.boundHandleChange);}},handleChange:function sl_handleChange(evt){for(var key in this.queries){if(this.queries[key].media!==evt.media){continue;}
|
||||
window.dispatchEvent(new CustomEvent('screenlayoutchange',{detail:{name:key,status:evt.matches}}));}}};ScreenLayout.init();
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
;'use strict';var SettingsListener={_lock:null,_observers:[],getSettingsLock:function sl_getSettingsLock(){if(this._lock&&!this._lock.closed){return this._lock;}
|
||||
var settings=window.navigator.mozSettings;return(this._lock=settings.createLock());},observe:function sl_observe(name,defaultValue,callback){var settings=window.navigator.mozSettings;if(!settings){window.setTimeout(function(){callback(defaultValue);});return;}
|
||||
var req;try{req=this.getSettingsLock().get(name);}catch(e){console.warn('Stale lock in settings_listener.js.','See https://bugzilla.mozilla.org/show_bug.cgi?id=793239');this._lock=null;req=this.getSettingsLock().get(name);}
|
||||
req.addEventListener('success',(function onsuccess(){callback(typeof(req.result[name])!='undefined'?req.result[name]:defaultValue);}));var settingChanged=function settingChanged(evt){callback(evt.settingValue);};settings.addObserver(name,settingChanged);this._observers.push({name:name,callback:callback,observer:settingChanged});},unobserve:function sl_unobserve(name,callback){var settings=window.navigator.mozSettings;var that=this;this._observers.forEach(function(value,index){if(value.name===name&&value.callback===callback){settings.removeObserver(name,value.observer);that._observers.splice(index,1);}});}};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,7 @@
|
|||
;;'use strict';var Normalizer={initAsciiNormalizer:function normalizer_init(){var equivalentChars={'a':'áăǎâäȧạȁàảȃāąåḁⱥãǽǣæ','A':'ÁĂǍÂÄȦẠȀÀẢȂĀĄÅḀȺÃǼǢÆ','b':'ḃḅɓḇƀƃ','B':'ḂḄƁḆɃƂ','c':'ćčçĉċƈȼ','C':'ĆČÇĈĊƇȻ','d':'ďḑḓḋḍɗḏđƌð','D':'ĎḐḒḊḌƊḎĐƋ','e':'éĕěȩêḙëėẹȅèẻȇēę','E':'ÉĔĚȨÊḘËĖẸȄÈẺȆĒĘ','f':'ḟƒ','F':'ḞƑ','g':'ǵğǧģĝġɠḡǥ','G':'ǴĞǦĢĜĠƓḠǤ','h':'ḫȟḩĥⱨḧḣḥħ','H':'ḪȞḨĤⱧḦḢḤĦ','i':'íĭǐîïịȉìỉȋīįɨĩḭı','I':'ÍĬǏÎÏỊȈÌỈȊĪĮƗĨḬ','j':'ĵɉ','J':'ĴɈ','k':'ḱǩķⱪꝃḳƙḵꝁ','K':'ḰǨĶⱩꝂḲƘḴꝀ','l':'ĺƚľļḽḷⱡꝉḻŀɫł','L':'ĹȽĽĻḼḶⱠꝈḺĿⱢŁ','m':'ḿṁṃɱ','M':'ḾṀṂⱮ','n':'ńňņṋṅṇǹɲṉƞñ','N':'ŃŇŅṊṄṆǸƝṈȠÑ','o':'óŏǒôöȯọőȍòỏơȏꝋꝍōǫøõœ','O':'ÓŎǑÔÖȮỌŐȌÒỎƠȎꝊꝌŌǪØÕŒ','p':'ṕṗꝓƥᵽꝑ','P':'ṔṖꝒƤⱣꝐ','q':'ꝗ','Q':'Ꝗ','r':'ŕřŗṙṛȑȓṟɍɽ','R':'ŔŘŖṘṚȐȒṞɌⱤ','s':'śšşŝșṡṣß','S':'ŚŠŞŜȘṠṢ','t':'ťţṱțⱦṫṭƭṯʈŧ','T':'ŤŢṰȚȾṪṬƬṮƮŦ','u':'úŭǔûṷüṳụűȕùủưȗūųůũṵ','U':'ÚŬǓÛṶÜṲỤŰȔÙỦƯȖŪŲŮŨṴ','v':'ṿʋṽ','V':'ṾƲṼ','w':'ẃŵẅẇẉẁⱳ','W':'ẂŴẄẆẈẀⱲ','x':'ẍẋ','X':'ẌẊ','y':'ýŷÿẏỵỳƴỷỿȳɏỹ','Y':'ÝŶŸẎỴỲƳỶỾȲɎỸ','z':'źžẑⱬżẓȥẕƶ','Z':'ŹŽẐⱫŻẒȤẔƵ'};this._toAsciiForm={};for(var letter in equivalentChars){var accentedForms=equivalentChars[letter];for(var i=accentedForms.length-1;i>=0;i--){this._toAsciiForm[accentedForms[i]]=letter;}}},toAscii:function normalizer_toAscii(str){if(!str||typeof str!='string'){return'';}
|
||||
if(!this._toAsciiForm){Normalizer.initAsciiNormalizer();}
|
||||
var result='';for(var i=0,len=str.length;i<len;i++){result+=this._toAsciiForm[str.charAt(i)]||str.charAt(i);}
|
||||
return result;},escapeHTML:function normalizer_escapeHTML(str,escapeQuotes){if(Array.isArray(str)){return Normalizer.escapeHTML(str.join(' '),escapeQuotes);}
|
||||
if(!str||typeof str!='string'){return'';}
|
||||
var escaped=str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');if(escapeQuotes){return escaped.replace(/"/g,'"').replace(/'/g,''');}
|
||||
return escaped;},unescapeHTML:function normalizer_unescapeHTML(text){return text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'\'');},escapeRegExp:function normalizer_escapeRegExp(str){return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,'\\$&');}};
|
||||
25
assets/webapps/ftu.gaiamobile.org/shared/js/tz_select.js
Normal file
25
assets/webapps/ftu.gaiamobile.org/shared/js/tz_select.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
;'use strict';function tzSelect(regionSelector,citySelector,onchange,onload){const TIMEZONE_FILE='/shared/resources/tz.json';const APN_TZ_FILE='/shared/resources/apn_tz.json';const TIMEZONE_NONE='NONE';const TIMEZONE_NETWORK='NETWORK';const TIMEZONE_SIM='SIM';const TIMEZONE_USER='USER';const TIMEZONE_PREVIOUS_SETTING='PREVIOUS_SETTING';function loadJSON(href,callback){var xhr=new XMLHttpRequest();xhr.open('GET',href,true);xhr.responseType='json';xhr.onerror=callback;xhr.onload=function(){callback(xhr.response);};xhr.onerror=function(){console.error('Error getting file');callback(xhr.response);};xhr.send();}
|
||||
function getDefaultTimezoneID(callback){if(!callback){return;}
|
||||
var tzDefault='America/New_York';var source=TIMEZONE_NONE;var mcc,mnc;var connections=window.navigator.mozMobileConnections||[];for(var i=0;i<connections.length;++i){var conn=connections[i];if(conn&&conn.voice&&conn.voice.network&&conn.voice.connected){mcc=conn.voice.network.mcc;mnc=conn.voice.network.mnc;source=TIMEZONE_NETWORK;break;}}
|
||||
if(!mcc&&IccHelper&&IccHelper.iccInfo){mcc=IccHelper.iccInfo.mcc;mnc=IccHelper.iccInfo.mnc;source=TIMEZONE_SIM;if(IccHelper.cardState!=='ready'){IccHelper.addEventListener('iccinfochange',function simReady(){if(IccHelper.iccInfo.mcc){IccHelper.removeEventListener('iccinfochange',simReady);}
|
||||
getDefaultTimezoneID(callback);});}}
|
||||
if(!mcc){callback(tzDefault,TIMEZONE_NONE);return;}
|
||||
loadJSON(APN_TZ_FILE,function(response){if(response){var tz=response[mcc];if(typeof(tz)==='string'){callback(tz,source);return;}else if(tz&&(mnc in tz)){callback(tz[mnc],source);return;}}
|
||||
callback(tzDefault,TIMEZONE_NONE);});}
|
||||
function newTZSelector(onchangeTZ,currentID,source){var gRegion=currentID.replace(/\/.*/,'');var gCity=currentID.replace(/.*?\//,'');var gTZ=null;function fillSelectElement(selector,options){selector.innerHTML='';options.sort(function(a,b){return(a.text>b.text);});for(var i=0;i<options.length;i++){var option=document.createElement('option');option.textContent=options[i].text;option.selected=options[i].selected;option.value=options[i].value;selector.appendChild(option);}}
|
||||
function fillSelectL10nElement(selector,options){selector.innerHTML='';const collator=Intl.Collator(navigator.languages,{usage:'sort',});const frag=document.createDocumentFragment();const items=options.map(option=>{const item=document.createElement('option');item.setAttribute('data-l10n-id',option.l10nId);item.selected=option.selected;item.value=option.value;frag.appendChild(item);return item;});return document.l10n.translateFragment(frag).then(()=>{items.sort((a,b)=>collator.compare(a.textContent,b.textContent));items.forEach(item=>frag.appendChild(item));selector.appendChild(frag);});}
|
||||
function getSelectedText(selector){var options=selector.querySelectorAll('option');return options[selector.selectedIndex].textContent;}
|
||||
function fillRegions(){var options=[];for(var c in gTZ){options.push({l10nId:'tzRegion-'+c,value:c,selected:(c===gRegion)});}
|
||||
fillSelectL10nElement(regionSelector,options).then(fillCities);}
|
||||
function fillCities(){gRegion=regionSelector.value;var list=gTZ[gRegion];var options=[];for(var i=0;i<list.length;i++){options.push({text:list[i].name||list[i].city.replace(/_/g,' '),value:i,selected:(list[i].city===gCity)});}
|
||||
fillSelectElement(citySelector,options);setTimezone();}
|
||||
function setTimezone(){if(source===TIMEZONE_PREVIOUS_SETTING){onload&&onload(getTZInfo(),true);return;}
|
||||
onchangeTZ(getTZInfo(),{'changedByUser':source===TIMEZONE_USER,'needsConfirmation':source!==TIMEZONE_USER&&source!==TIMEZONE_NETWORK});}
|
||||
function getTZInfo(){var res=gTZ[gRegion][citySelector.value];gCity=res.city;var offset=res.offset.split(',');return{id:res.id||gRegion+'/'+res.city,region:getSelectedText(regionSelector),city:getSelectedText(citySelector),cc:res.cc,utcOffset:offset[0],dstOffset:offset[1]};}
|
||||
regionSelector.onchange=function(){source=TIMEZONE_USER;fillCities();};citySelector.onchange=function(){source=TIMEZONE_USER;setTimezone();};loadJSON(TIMEZONE_FILE,function loadTZ(response){gTZ=response;fillRegions();});}
|
||||
function newTZObserver(){var settings=window.navigator.mozSettings;if(!settings){return;}
|
||||
settings.addObserver('time.timezone',function(event){setTimezoneDescription(event.settingValue);});var systemTimeChanged=function(){onchange(systemTimeChanged.tz,systemTimeChanged.needsConfirmation);};function updateTZ(tz,options){systemTimeChanged.tz=tz;systemTimeChanged.needsConfirmation=options.needsConfirmation;var req=settings.createLock().set({'time.timezone':tz.id});req.onsuccess=function updateTZ_callback(){if(options.changedByUser){settings.createLock().set({'time.timezone.user-selected':tz.id});}else{onload&&onload(tz,options.needsConfirmation);}};}
|
||||
function initSelector(initialValue,source){onchange&&window.addEventListener('moztimechange',systemTimeChanged);onchange&&window.addEventListener('unload',function(){window.removeEventListener('moztimechange',systemTimeChanged);});newTZSelector(updateTZ,initialValue,source);}
|
||||
var reqTimezone=settings.createLock().get('time.timezone');reqTimezone.onsuccess=function dt_getStatusSuccess(){var reqUserTZ=settings.createLock().get('time.timezone.user-selected');reqUserTZ.onsuccess=function dt_getUserTimezoneSuccess(){var userSelTimezone=reqUserTZ.result['time.timezone.user-selected'];if(userSelTimezone){initSelector(userSelTimezone,TIMEZONE_PREVIOUS_SETTING);}else{getDefaultTimezoneID(initSelector);}};};function setTimezoneDescription(timezoneID){var oldSelectedIndex=regionSelector.selectedIndex;regionSelector.value=timezoneID.replace(/\/.*/,'');if(regionSelector.selectedIndex==-1){regionSelector.selectedIndex=oldSelectedIndex;}
|
||||
oldSelectedIndex=citySelector.selectedIndex;citySelector.value=timezoneID.replace(/.*?\//,'');if(citySelector.selectedIndex==-1){citySelector.selectedIndex=oldSelectedIndex;}}}
|
||||
newTZObserver();}
|
||||
7
assets/webapps/ftu.gaiamobile.org/shared/js/utilities.js
Normal file
7
assets/webapps/ftu.gaiamobile.org/shared/js/utilities.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
;'use strict';var DateHelper={todayStarted:function dh_todayStarted(){var now=(new Date()).valueOf();return this.getMidnight(now);},yesterdayStarted:function dh_yesterdayStarted(){var now=(new Date()).valueOf();var dayAgo=now-86400000;return this.getMidnight(dayAgo);},thisWeekStarted:function dh_thisWeekStarted(){var now=new Date();var dayOfTheWeek=now.getDay();var firstDay=new Date(now.getFullYear(),now.getMonth(),now.getDate()-dayOfTheWeek);return this.getMidnight(firstDay);},thisMonthStarted:function dh_thisMonthStarted(){var now=new Date();var firstDay=(new Date(now.getFullYear(),now.getMonth(),1).valueOf());return firstDay;},lastSixMonthsStarted:function dh_lastSixMonthsStarted(){var now=new Date().valueOf();var sixMonthsAgo=now-2629743830*6;return sixMonthsAgo;},thisYearStarted:function dh_thisYearStarted(){var now=new Date();var firstDay=(new Date(now.getFullYear(),0).valueOf());return firstDay;},getMidnight:function dh_getMidnight(timestamp){var day=new Date(timestamp);var midnight=(new Date(day.getFullYear(),day.getMonth(),day.getDate(),0).valueOf());return midnight;}};var NumberHelper={zfill:function nh_zfill(string,len){var s=string;while(s.length<len){s='0'+s;}
|
||||
return s;}};var StringHelper={fromUTF8:function ut_fromUTF8(str){var buf=new Uint8Array(str.length);for(var i=0;i<str.length;i++){buf[i]=str.charCodeAt(i);}
|
||||
return buf;},camelCase:function ut_camelCase(str){var rdashes=/-(.)/g;return str.replace(rdashes,function replacer(str,p1){return p1.toUpperCase();});}};var HtmlHelper={createHighlightHTML:function ut_createHighlightHTML(text,terms){var toHighlight=[];var normalizedText=Normalizer.toAscii(text).toLowerCase();terms.forEach(function(term){term=Normalizer.toAscii(term).toLowerCase();var index=normalizedText.indexOf(term);while(index>=0){for(var i=0,length=term.length;i<length;i++){toHighlight[index+i]=true;}
|
||||
index=normalizedText.indexOf(term,index+term.length);}});var fragment=document.createDocumentFragment();for(var i=0;i<text.length;i++){if(!toHighlight[i]){fragment.appendChild(document.createTextNode(text[i]));}else{var term='';while(toHighlight[i]){term+=text[i];i++;}
|
||||
i--;var mark=document.createElement('mark');mark.textContent=term;fragment.appendChild(mark);}}
|
||||
return fragment;},escapeHTML:function ut_escapeHTML(str,escapeQuotes){var span=document.createElement('span');span.textContent=str;span.innerHTML=span.innerHTML.replace(/\s/g,' ');if(escapeQuotes){return span.innerHTML.replace(/"/g,'"').replace(/'/g,''');}
|
||||
return span.innerHTML;},importElements:function importElements(context){var ids=[].slice.call(arguments,1);ids.forEach(function(id){context[StringHelper.camelCase(id)]=document.getElementById(id);});}};
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
;'use strict';(function(exports){function Version(str){str=str||'0.0';var parts=str.split('.');this.major=parts[0];this.minor=parts[1];this.toString=function(){return''+str;};}
|
||||
function InfoResult(current,previous){this.current=current;this.previous=previous;}
|
||||
InfoResult.prototype.isUpgrade=function(){var prev=this.previous,curr=this.current,isUpgrade=false;if(prev&&curr){isUpgrade=curr.major>prev.major||curr.minor>prev.minor;}
|
||||
return isUpgrade;};InfoResult.prototype.delta=function(){var prev=this.previous,curr=this.current;var delta='';if(prev){delta+=prev.major+'.'+prev.minor;}
|
||||
delta+='..';if(curr){delta+=curr.major+'.'+curr.minor;}
|
||||
return delta;};function getSetting(setting,cb,errback){var req=navigator.mozSettings.createLock().get(setting);req.onsuccess=function(){var value=req.result[setting];if(typeof cb==='function'){cb(value);}};req.onerror=function(){if(typeof errback==='function'){errback(req.error);}};}
|
||||
function getCurrent(){return new Promise(function(resolve,reject){getSetting('deviceinfo.os',function(value){resolve(value?new Version(value):null);},function(err){reject(err);});});}
|
||||
function getPrevious(){return new Promise(function(resolve,reject){getSetting('deviceinfo.previous_os',function(value){resolve(value?new Version(value):null);},function(err){resolve(null);});});}
|
||||
function updatePrevious(str){var currentPromise=str?Promise.resolve(new Version(str)):getCurrent();var setPromise=new Promise(function(resolve,reject){currentPromise.then(function(version){if(version){var req=navigator.mozSettings.createLock().set({'deviceinfo.previous_os':version.toString()});req.onsuccess=function(){resolve(version);};req.onerror=function(){var reason=req.error;reject(reason);};}else{resolve(null);}},function(err){reject('Error getting previous version: ',err);});});return setPromise;}
|
||||
function getVersionInfo(){var info=new InfoResult();var infoPromise=new Promise(function(resolve,reject){Promise.all([getCurrent().then(function(version){info.current=version;}),getPrevious().then(function(version){info.previous=version;})]).then(function(){resolve(info);},reject);});return infoPromise;}
|
||||
exports.VersionHelper={updatePrevious:updatePrevious,getVersionInfo:getVersionInfo};})(window);
|
||||
20
assets/webapps/ftu.gaiamobile.org/shared/js/wifi_helper.js
Normal file
20
assets/webapps/ftu.gaiamobile.org/shared/js/wifi_helper.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
;'use strict';var WifiHelper={getWifiManager:function(){return this.wifiManager();},wifiManager:function(){return navigator.mozWifiManager;},setPassword:function(network,password,identity,eap,phase2,certificate){var encType=this.getKeyManagement(network);switch(encType){case'WPA-PSK':network.psk=password;break;case'WPA-EAP':network.eap=eap;switch(eap){case'SIM':break;case'PEAP':case'TLS':case'TTLS':if(password&&password.length){network.password=password;}
|
||||
if(identity&&identity.length){network.identity=identity;}
|
||||
if(phase2!='No'){network.phase2=phase2;}
|
||||
if(certificate!='none'){network.serverCertificate=certificate;}
|
||||
break;default:break;}
|
||||
break;case'WEP':network.wep=password;break;default:return;}
|
||||
network.keyManagement=encType;},setSecurity:function(network,encryptions){network.security=encryptions;},getSecurity:function(network){return network.security;},getCapabilities:function(network){return network.capabilities===undefined||network.capabilities===null?[]:network.capabilities;},getKeyManagement:function(network){var key=this.getSecurity(network)[0];if(/WEP$/.test(key)){return'WEP';}
|
||||
if(/PSK$/.test(key)){return'WPA-PSK';}
|
||||
if(/EAP$/.test(key)){return'WPA-EAP';}
|
||||
return'';},isConnected:function(network){return'connected'===this.getNetworkStatus(network);},getNetworkStatus:function(network){var currentConnection=this.getWifiManager().connection;var currentNetwork=currentConnection.network;if(!currentNetwork||!network){return false;}
|
||||
var key=network.ssid+'+'+this.getSecurity(network).join('+');var curkey=currentNetwork.ssid+'+'+
|
||||
this.getSecurity(currentNetwork).join('+');return(key===curkey?currentConnection.status:'disconnected');},isValidInput:function(key,password,identity,eap){function isValidWepKey(password){switch(password.length){case 5:case 13:case 16:case 29:return true;case 10:case 26:case 32:case 58:return!/[^a-fA-F0-9]/.test(password);default:return false;}}
|
||||
switch(key){case'WPA-PSK':if(!password||password.length<8){return false;}
|
||||
break;case'WPA-EAP':switch(eap){case'SIM':break;case'PEAP':case'TLS':case'TTLS':default:if(!password||password.length<1||!identity||identity.length<1){return false;}
|
||||
break;}
|
||||
break;case'WEP':if(!password||!isValidWepKey(password)){return false;}
|
||||
break;}
|
||||
return true;},isSSIDValid:function(ssid){return!!ssid&&ssid.length>0;},isWpsAvailable:function(network){var capabilities=this.getCapabilities(network);for(var i=0;i<capabilities.length;i++){if(/WPS/.test(capabilities[i])){return true;}}
|
||||
return false;},isOpen:function(network){return this.getKeyManagement(network)==='';},isEap:function(network){return this.getKeyManagement(network).indexOf('EAP')!==-1;},_unionOfNetworks:function(available,known){var allNetworks=available||{};var result=[];Object.keys(known).forEach(function(key){if(!allNetworks[key]){allNetworks[key]=known[key];}});Object.keys(allNetworks).forEach(function(key){result.push(allNetworks[key]);});return result;},_networksArrayToObject:function(allNetworks){var self=this;var networksObject={};[].forEach.call(allNetworks,function(network){var key=network.ssid+'+'+
|
||||
self.getSecurity(network).join('+');if(!networksObject[key]||network.relSignalStrength>networksObject[key].relSignalStrength){networksObject[key]=network;}});return networksObject;},_onReqProxySuccess:function(reqProxy,availableNetworks,knownNetworks){reqProxy.result=this._unionOfNetworks(availableNetworks,knownNetworks);reqProxy.onsuccess();},getAvailableAndKnownNetworks:function(){var self=this;var reqProxy={onsuccess:function(){},onerror:function(){}};var knownNetworks={};var availableNetworks={};var knownNetworksReq=null;var availableNetworksReq=this.getWifiManager().getNetworks();availableNetworksReq.onsuccess=function anrOnSuccess(){availableNetworks=self._networksArrayToObject(availableNetworksReq.result);knownNetworksReq=self.getWifiManager().getKnownNetworks();knownNetworksReq.onsuccess=function knrOnSuccess(){knownNetworks=self._networksArrayToObject(knownNetworksReq.result);self._onReqProxySuccess(reqProxy,availableNetworks,knownNetworks);};knownNetworksReq.onerror=function knrOnError(){self._onReqProxySuccess(reqProxy,availableNetworks,knownNetworks);};};availableNetworksReq.onerror=function anrOnError(){reqProxy.error=availableNetworksReq.error;reqProxy.onerror();};return reqProxy;},getSignalLevel:function(network){return Math.min(Math.floor(network.relSignalStrength/20),4);}};
|
||||
Loading…
Reference in a new issue