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
123
gaia/apps/communications/contacts/js/utilities/cookie.js
Executable file
123
gaia/apps/communications/contacts/js/utilities/cookie.js
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
'use strict';
|
||||
|
||||
//
|
||||
// Utility API for manipulating config stored in local cookie.
|
||||
//
|
||||
(function() {
|
||||
var utils = window.utils = window.utils || {};
|
||||
utils.cookie = {};
|
||||
|
||||
// Bump this number if the format of the cookie changes
|
||||
var COOKIE_VERSION = 6;
|
||||
|
||||
// Default values for items stored in the configuration
|
||||
var COOKIE_DEFAULTS = {
|
||||
order: false,
|
||||
viewHeight: -1,
|
||||
rowsPerPage: -1,
|
||||
fbMigrated: false,
|
||||
fbScheduleDone: false,
|
||||
defaultImage: true,
|
||||
accessTokenMigrated: false,
|
||||
fbCleaningInProgress: 0
|
||||
};
|
||||
|
||||
// Only allow these properties to be stored in the config
|
||||
var COOKIE_PROPS = Object.keys(COOKIE_DEFAULTS);
|
||||
|
||||
// Set as a constant in the far future to avoid calling new Date
|
||||
var EXPIRATION_DATE = 'Fri, 31 Dec 9999 23:59:59 GMT';
|
||||
var COOKIE_NAME = 'preferences';
|
||||
var COOKIE_NAME_EQ = COOKIE_NAME + '=';
|
||||
var COOKIE_NAME_LENGHT = COOKIE_NAME_EQ.length;
|
||||
|
||||
// Parses the cookie returning the corresponding object
|
||||
// Returns null if the cookie string does not conform with expected format
|
||||
function _parseCookie(cookie) {
|
||||
// Expecting "preferences=<JSON value>"
|
||||
var index = cookie.indexOf(COOKIE_NAME_EQ);
|
||||
if (index === -1) {
|
||||
return null;
|
||||
}
|
||||
var cookieVal = cookie.substring(index + COOKIE_NAME_LENGHT);
|
||||
if (!cookieVal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup default value for cookie with version that will trigger update
|
||||
var result = {
|
||||
version: -1
|
||||
};
|
||||
try {
|
||||
result = JSON.parse(decodeURIComponent(cookieVal));
|
||||
} catch (error) {
|
||||
console.warn('Could not parse current cookie, rebuilding it');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Load and return the cookie config if present. Returns null if the
|
||||
// cookie is missing, has an incorrect format or versions mismatch.
|
||||
utils.cookie.load = function() {
|
||||
if (!document.cookie) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var cookie = _parseCookie(document.cookie);
|
||||
|
||||
// If the cookie is out-of-date, then we need to update it
|
||||
// and re-parse since the format might have changed.
|
||||
if (cookie && cookie.version !== COOKIE_VERSION) {
|
||||
_updateCookie(cookie);
|
||||
cookie = _parseCookie(document.cookie);
|
||||
}
|
||||
|
||||
return cookie;
|
||||
};
|
||||
|
||||
// Update the current cookie config with the given set of configuration
|
||||
// properties. The passed in configuration properties can be a subset of
|
||||
// the values stored in the cookie. Unspecified values will be unchanged.
|
||||
utils.cookie.update = function(cfg) {
|
||||
_updateCookie(utils.cookie.load(), cfg);
|
||||
};
|
||||
|
||||
// Internal helper function that writes out a new cookie based on a set
|
||||
// of existing value, a set of new values, and defaults.
|
||||
function _updateCookie(oldCookie, cfg) {
|
||||
oldCookie = oldCookie || {};
|
||||
cfg = cfg || {};
|
||||
|
||||
var newCookie = {version: COOKIE_VERSION};
|
||||
|
||||
// For each allowed property in the config, pick the right value given
|
||||
// our input. This intentionally removes old properties in the cookie
|
||||
// that may no longer be in our allowed COOKIE_PROPS.
|
||||
for (var i = 0, n = COOKIE_PROPS.length; i < n; ++i) {
|
||||
var prop = COOKIE_PROPS[i];
|
||||
// Prefer configuration overrides provided
|
||||
if (prop in cfg) {
|
||||
newCookie[prop] = cfg[prop];
|
||||
// Otherwise use previously set values
|
||||
} else if (prop in oldCookie) {
|
||||
newCookie[prop] = oldCookie[prop];
|
||||
// Finally, fall back to defaults
|
||||
} else {
|
||||
newCookie[prop] = COOKIE_DEFAULTS[prop];
|
||||
}
|
||||
}
|
||||
|
||||
document.cookie = COOKIE_NAME + '=' +
|
||||
encodeURIComponent(JSON.stringify(newCookie)) +
|
||||
';expires=' + EXPIRATION_DATE;
|
||||
|
||||
}
|
||||
|
||||
utils.cookie.getDefault = function(prop) {
|
||||
return COOKIE_DEFAULTS[prop];
|
||||
};
|
||||
|
||||
utils.cookie.COOKIE_DEFAULTS = COOKIE_DEFAULTS;
|
||||
utils.cookie.COOKIE_VERSION = COOKIE_VERSION;
|
||||
})();
|
||||
18
gaia/apps/communications/contacts/js/utilities/extract_params.js
Executable file
18
gaia/apps/communications/contacts/js/utilities/extract_params.js
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
'use strict';
|
||||
|
||||
var utils = window.utils || {};
|
||||
|
||||
if (!utils.extractParams) {
|
||||
utils.extractParams = function extractParams(url) {
|
||||
if (!url) {
|
||||
return -1;
|
||||
}
|
||||
var ret = {};
|
||||
var params = url.split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var currentParam = params[i].split('=');
|
||||
ret[currentParam[0]] = currentParam[1];
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
95
gaia/apps/communications/contacts/js/utilities/icc_handler.js
Executable file
95
gaia/apps/communications/contacts/js/utilities/icc_handler.js
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
/* exported IccHandler */
|
||||
//
|
||||
// IccManager Handler, abstract the new IccManager Multi SIM
|
||||
// API.
|
||||
// Generates code and handles listener for multiple SIMs
|
||||
//
|
||||
'use strict';
|
||||
|
||||
var IccHandler = function IccHandler() {
|
||||
|
||||
var iccManager = null;
|
||||
var isSingleSIMApi = false;
|
||||
var iccs = {};
|
||||
var sims = [];
|
||||
var mobileConnections = null;
|
||||
|
||||
var init = function init(domGenerator, cb) {
|
||||
iccManager = navigator.mozIccManager;
|
||||
// Avoid any icc operation in devices that don't
|
||||
// have icc (desktop, tablets, etc.)
|
||||
if (!iccManager) {
|
||||
if (typeof cb === 'function') {
|
||||
cb();
|
||||
}
|
||||
return;
|
||||
}
|
||||
mobileConnections = navigator.mozMobileConnections || [];
|
||||
isSingleSIMApi = typeof iccManager.getIccById !== 'function';
|
||||
|
||||
if (isSingleSIMApi) {
|
||||
iccs['default'] = iccManager;
|
||||
} else {
|
||||
iccManager.iccIds.forEach(function(iccId) {
|
||||
iccs[iccId] = iccManager.getIccById(iccId);
|
||||
});
|
||||
for (var i = 0; i < mobileConnections.length; i++) {
|
||||
sims[i] = iccs[mobileConnections[i].iccId] || null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
domGenerator.setIccList(sims);
|
||||
domGenerator.generateDOM();
|
||||
|
||||
subscribeToChanges(cb);
|
||||
};
|
||||
|
||||
// Make all the SIMS to listen to the same change observer
|
||||
var subscribeToChanges = function subscribeToChanges(cb) {
|
||||
if (!navigator.mozIccManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(iccs).forEach(function onIccId(iccId) {
|
||||
var icc = iccs[iccId];
|
||||
if (icc !== null) {
|
||||
icc.oncardstatechange = cb;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Get the status of all the SIMs, not just the status, also
|
||||
// the proper icc object
|
||||
var getStatus = function getStatus() {
|
||||
var status = [];
|
||||
Object.keys(iccs).forEach(function onIccId(iccId) {
|
||||
var icc = iccs[iccId];
|
||||
status.push({
|
||||
'iccId': iccId,
|
||||
'icc': icc,
|
||||
'cardState': icc.cardState
|
||||
});
|
||||
});
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
// Returns the specific icc depending on id.
|
||||
// If id is null will try to get the one named 'default'
|
||||
var getIccById = function getIccById(id) {
|
||||
if (!id) {
|
||||
id = 'default';
|
||||
}
|
||||
|
||||
return iccs[id];
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
'getStatus': getStatus,
|
||||
'getIccById': getIccById,
|
||||
'init': init
|
||||
};
|
||||
|
||||
}();
|
||||
272
gaia/apps/communications/contacts/js/utilities/ice_data.js
Executable file
272
gaia/apps/communications/contacts/js/utilities/ice_data.js
Executable file
|
|
@ -0,0 +1,272 @@
|
|||
'use strict';
|
||||
|
||||
/* global Cache */
|
||||
/* global ICEStore */
|
||||
|
||||
/**
|
||||
* Module to handle ICE data across different parts of the application
|
||||
* This module just know about the internal representation and how
|
||||
* to deal with share ICE information (which has different format).
|
||||
*/
|
||||
|
||||
(function ICEData(exports) {
|
||||
|
||||
if (exports.ICEData) {
|
||||
return;
|
||||
}
|
||||
|
||||
var localIceContacts = [];
|
||||
var ICE_CONTACTS_KEY = 'ice-contacts';
|
||||
var onChangeCallbacks = [];
|
||||
var onChangeAttached = false;
|
||||
var pendingChanges = [];
|
||||
var performingChange = false;
|
||||
|
||||
/**
|
||||
* Loads the local formated data into an internal
|
||||
* representation.
|
||||
* @return (Promise) fullfill when the load finish.
|
||||
*/
|
||||
function load() {
|
||||
var iceContactsIds = [
|
||||
{
|
||||
id: undefined,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
id: undefined,
|
||||
active: false
|
||||
}
|
||||
];
|
||||
|
||||
return new Promise(function(resolve) {
|
||||
window.asyncStorage.getItem(ICE_CONTACTS_KEY, function(data) {
|
||||
if (data) {
|
||||
if (data[0]) {
|
||||
iceContactsIds[0] = data[0];
|
||||
}
|
||||
if (data[1]) {
|
||||
iceContactsIds[1] = data[1];
|
||||
}
|
||||
}
|
||||
localIceContacts = iceContactsIds;
|
||||
resolve(localIceContacts);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an ICE contact, both in local and in the
|
||||
* datastore
|
||||
* @param id (string) contact id
|
||||
* @param pos (int) current position (0,1)
|
||||
* @param active (boolean) ice contact is active or not
|
||||
*/
|
||||
function setICEContact(id, pos, active) {
|
||||
active = active || false;
|
||||
|
||||
Cache.evict();
|
||||
|
||||
// Save locally
|
||||
localIceContacts[pos] = {
|
||||
id: id,
|
||||
active: active
|
||||
};
|
||||
return setIceContactsItem(localIceContacts).then(() => {
|
||||
// Save in the datastore
|
||||
return modifyICEInDS();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the values for ICE contacts, both in local and in the
|
||||
* datastore
|
||||
* @param iceContactList the list with ids of ICE Contacts to be set
|
||||
*/
|
||||
function setICEContacts(iceContactList) {
|
||||
if (!Array.isArray(iceContactList)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Save locally
|
||||
localIceContacts = [
|
||||
{
|
||||
id: iceContactList[0],
|
||||
active: !!iceContactList[0]
|
||||
},
|
||||
{
|
||||
id: iceContactList[1],
|
||||
active: !!iceContactList
|
||||
}
|
||||
];
|
||||
|
||||
return setIceContactsItem(localIceContacts).then(() => {
|
||||
// Save in the datastore
|
||||
return modifyICEInDS();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the current local ice contacts into the datastore,
|
||||
* considering the active flag.
|
||||
* @return (Promise) fullfill when data saved in DS
|
||||
*/
|
||||
function modifyICEInDS() {
|
||||
var contacts = [];
|
||||
localIceContacts.forEach(function(iceContact) {
|
||||
if (iceContact.id && iceContact.active) {
|
||||
contacts.push(iceContact.id);
|
||||
}
|
||||
});
|
||||
|
||||
return ICEStore.setContacts(contacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a specific contact id removes it from the
|
||||
* ice list, if present or disabled
|
||||
* @param id (String) contact id
|
||||
* @return (Promise) fullfill when data modified in DS
|
||||
*/
|
||||
function removeICEContact(id) {
|
||||
var contact = localIceContacts.find(function(x) {
|
||||
return x.id === id;
|
||||
});
|
||||
|
||||
if (!contact) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var index = localIceContacts.indexOf(contact);
|
||||
localIceContacts[index].id = null;
|
||||
localIceContacts[index].active = false;
|
||||
|
||||
setIceContactsItem(localIceContacts).then(function() {
|
||||
return modifyICEInDS().then(() => {
|
||||
notifyCallbacks(localIceContacts);
|
||||
});
|
||||
}).then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function setIceContactsItem(iceContactsList) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
window.asyncStorage.setItem(ICE_CONTACTS_KEY, iceContactsList,
|
||||
resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an observer to be called when an ice contact
|
||||
* changed. We can add multiple listeners, since we will
|
||||
* need to react in several parts of the app to this changes.
|
||||
* Also the listener removes a contact id in case we are performing
|
||||
* a delete. In case of modification we don't do anythying as we
|
||||
* store just ids.
|
||||
* Listener will be attached just once, but we can keep adding
|
||||
* callbacks.
|
||||
* @param fn (Function) callback to call when ice contact change
|
||||
*/
|
||||
function listenForChanges(fn) {
|
||||
return new Promise(function(resolve) {
|
||||
if (!onChangeAttached) {
|
||||
load().then(function() {
|
||||
document.addEventListener('contactChanged', onChangeEvent);
|
||||
onChangeAttached = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof fn === 'function') {
|
||||
onChangeCallbacks.push(fn);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function notifyCallbacks(data) {
|
||||
onChangeCallbacks.forEach(function(fn) {
|
||||
fn(data);
|
||||
});
|
||||
}
|
||||
|
||||
function handleChangeEvent(evt) {
|
||||
// Figure out if we got a change in a ICE contact
|
||||
var contact = localIceContacts.find(function(x) {
|
||||
return x.id === evt.detail.contactID;
|
||||
});
|
||||
|
||||
if (!contact) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// If it's a delete update all the storages
|
||||
if (evt.detail.reason === 'remove') {
|
||||
return removeICEContact(contact.id);
|
||||
} else {
|
||||
notifyCallbacks(localIceContacts);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeEvent(evt) {
|
||||
// We should refresh the iceContacts before determining if it is necessary
|
||||
// to notify or not
|
||||
pendingChanges.push(evt);
|
||||
if (!performingChange) {
|
||||
performChange();
|
||||
}
|
||||
}
|
||||
|
||||
// We perform any change sequentially to avoid any race condition while
|
||||
// saving to disc the ICE state.
|
||||
function performChange() {
|
||||
performingChange = true;
|
||||
var evt = pendingChanges.pop();
|
||||
|
||||
if (!evt) {
|
||||
performingChange = false;
|
||||
return;
|
||||
}
|
||||
|
||||
function callback() {
|
||||
handleChangeEvent(evt).then(performChange);
|
||||
}
|
||||
|
||||
load().then(callback, callback);
|
||||
}
|
||||
|
||||
function getActiveIceContacts() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
load().then(function() {
|
||||
var result = [];
|
||||
localIceContacts.forEach(function(aContact) {
|
||||
if (aContact.active && aContact.id) {
|
||||
result.push(aContact.id);
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function stopListenForChanges() {
|
||||
document.removeEventListener('contactChanged', onChangeEvent);
|
||||
onChangeCallbacks = [];
|
||||
onChangeAttached = false;
|
||||
}
|
||||
|
||||
exports.ICEData = {
|
||||
load: load,
|
||||
setICEContact: setICEContact,
|
||||
setICEContacts: setICEContacts,
|
||||
removeICEContact: removeICEContact,
|
||||
get iceContacts() { return localIceContacts; },
|
||||
getActiveIceContacts: getActiveIceContacts,
|
||||
listenForChanges: listenForChanges,
|
||||
stopListenForChanges: stopListenForChanges
|
||||
};
|
||||
|
||||
|
||||
})(window);
|
||||
149
gaia/apps/communications/contacts/js/utilities/merge_helper.js
Executable file
149
gaia/apps/communications/contacts/js/utilities/merge_helper.js
Executable file
|
|
@ -0,0 +1,149 @@
|
|||
'use strict';
|
||||
|
||||
/* exported MergeHelper */
|
||||
/* globals LazyLoader, ICEData, contacts, ContactsService */
|
||||
|
||||
/**
|
||||
*
|
||||
* Merge helper file. Allows to perform a merge while respecting
|
||||
* all the ICE Settings
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
var MergeHelper = (function() {
|
||||
var DEPENDENCIES = [
|
||||
'/shared/js/contacts/contacts_merger.js',
|
||||
'/shared/js/contacts/utilities/image_thumbnail.js',
|
||||
'/shared/js/contacts/utilities/ice_store.js',
|
||||
'/contacts/js/utilities/ice_data.js',
|
||||
'/shared/js/async_storage.js'
|
||||
];
|
||||
|
||||
var eventHandler;
|
||||
|
||||
// Set the new ICE Contacts being contactId the id of the contact which
|
||||
// is now the merged contact
|
||||
function setNewIceContacts(merged, original, contactId) {
|
||||
if (!merged) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// If we still don't know the contact id
|
||||
// (this happens when merge is done from 'add new contact')
|
||||
if (!contactId || contactId === 'undefined') {
|
||||
eventHandler = onContactAdded.bind(null, merged, original);
|
||||
ContactsService.addListener('contactchange', eventHandler);
|
||||
}
|
||||
|
||||
return ICEData.setICEContacts(merged);
|
||||
}
|
||||
|
||||
function onContactAdded(mergedIceContacts, originalIceContacts, event) {
|
||||
if (event.reason !== 'create') {
|
||||
return;
|
||||
}
|
||||
|
||||
ContactsService.removeListener('contactchange', eventHandler);
|
||||
|
||||
var id = event.contactID;
|
||||
|
||||
// Let's determine what ICE Contact is changing
|
||||
var mergedICEContact1 = false;
|
||||
if (originalIceContacts[0] && mergedIceContacts[0] !==
|
||||
originalIceContacts[0]) {
|
||||
mergedIceContacts[0] = id;
|
||||
mergedICEContact1 = true;
|
||||
}
|
||||
|
||||
// This condition avoids to set ICE Contact 2 to the same contact as
|
||||
// ICE Contact 1. This may occur if ICE Contact 1 and ICE Contact 2 both
|
||||
// match an incoming (manually added contact)
|
||||
if (originalIceContacts[1] &&
|
||||
mergedIceContacts[1] !== originalIceContacts[1] && !mergedICEContact1) {
|
||||
mergedIceContacts[1] = id;
|
||||
}
|
||||
|
||||
ICEData.setICEContacts(mergedIceContacts);
|
||||
}
|
||||
|
||||
function getMergedIceContacts(mergeList, contactId) {
|
||||
return new Promise(function(resolve) {
|
||||
ICEData.load().then(function() {
|
||||
var iceContacts = ICEData.iceContacts.map((contact) => {
|
||||
return contact.active ? contact.id : null;
|
||||
});
|
||||
|
||||
var mergeListWithIds = mergeList.map((matching) => {
|
||||
return matching.matchingContact.id;
|
||||
});
|
||||
|
||||
var mergedIceContacts;
|
||||
|
||||
var isThereMerge = false;
|
||||
mergedIceContacts = iceContacts.map((iceContact) => {
|
||||
if(iceContact && mergeListWithIds.indexOf(iceContact) !== -1) {
|
||||
isThereMerge = true;
|
||||
return contactId;
|
||||
}
|
||||
return iceContact;
|
||||
});
|
||||
|
||||
// If there is no merge null value is returned
|
||||
if (!isThereMerge) {
|
||||
resolve([null, iceContacts]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Here we filter out duplicates
|
||||
mergedIceContacts = mergedIceContacts.map((id, index) => {
|
||||
if(id && mergedIceContacts.indexOf(id) === index) {
|
||||
return id;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
resolve([mergedIceContacts, iceContacts]);
|
||||
|
||||
}, function(error) {
|
||||
console.error('Error while obtaining ICE Contacts: ', error);
|
||||
resolve([]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function merge(contact, mergeList) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
LazyLoader.load(DEPENDENCIES, function loaded() {
|
||||
getMergedIceContacts(mergeList, contact.id).then(
|
||||
function(result) {
|
||||
var mergedIceContacts = result[0], originalIceContacts = result[1];
|
||||
var callbacks = {
|
||||
success: resolve,
|
||||
error: function(e) {
|
||||
console.error('Failed merging duplicate contacts: ', e.name);
|
||||
// If there is an error in the merge then we just
|
||||
// keep the old ICE Contacts
|
||||
ICEData.setICEContacts(originalIceContacts);
|
||||
ContactsService.removeListener('contactchange', eventHandler);
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
var mergerFunction = contacts.Merger.merge.bind(null,
|
||||
contact, mergeList, callbacks);
|
||||
// We set the new ICEContact as the merged one
|
||||
// As a result, the contactChange events associated to the
|
||||
// Contacts merged (which are deleted) will be ignored
|
||||
// This is safer than relying on events as
|
||||
// there could race conditions
|
||||
setNewIceContacts(mergedIceContacts, originalIceContacts,
|
||||
contact.id).then(mergerFunction, mergerFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
'merge': merge
|
||||
};
|
||||
}());
|
||||
36
gaia/apps/communications/contacts/js/utilities/mozContact.js
Executable file
36
gaia/apps/communications/contacts/js/utilities/mozContact.js
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
exports = exports || {};
|
||||
|
||||
var mozContactUtils = {
|
||||
// Checks if an object fields are empty, by empty means
|
||||
// field is null and if it's an array it's length is 0
|
||||
haveEmptyFields: function (obj, fields) {
|
||||
if (obj === null ||
|
||||
typeof(obj) != 'object' ||
|
||||
!fields || !fields.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var attr;
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
attr = fields[i];
|
||||
if (obj[attr]) {
|
||||
if (Array.isArray(obj[attr])) {
|
||||
if (obj[attr].length > 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
exports.mozContact = mozContactUtils;
|
||||
|
||||
})(window.utils);
|
||||
68
gaia/apps/communications/contacts/js/utilities/normalizer.js
Executable file
68
gaia/apps/communications/contacts/js/utilities/normalizer.js
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
|
||||
var utils = window.utils || {};
|
||||
|
||||
if (!utils.time) {
|
||||
(function() {
|
||||
utils.time = {};
|
||||
|
||||
var _ = navigator.mozL10n.get;
|
||||
|
||||
utils.time.pretty = function(time, now) {
|
||||
// 'now' attribute for test purposes.
|
||||
var prettyDate = '';
|
||||
|
||||
if (!time) {
|
||||
return prettyDate;
|
||||
}
|
||||
|
||||
function roundToDay(date) {
|
||||
var rounded = new Date(date);
|
||||
rounded.setHours(0);
|
||||
rounded.setMinutes(0);
|
||||
rounded.setSeconds(0);
|
||||
rounded.setMilliseconds(0);
|
||||
return rounded;
|
||||
}
|
||||
|
||||
now = now || Date.now();
|
||||
var day_diff = (roundToDay(now) - roundToDay(time)) / 86400000;
|
||||
if (!isNaN(day_diff)) {
|
||||
// Woohh we are on the future here
|
||||
if (day_diff < 0) {
|
||||
prettyDate = (new Date(time)).toLocaleString(navigator.languages, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
} else {
|
||||
if (day_diff === 0) {
|
||||
prettyDate = _('today');
|
||||
} else if (day_diff === 1) {
|
||||
prettyDate = _('yesterday');
|
||||
} else if (day_diff < 6) {
|
||||
prettyDate = (new Date(time)).toLocaleString(navigator.languages, {
|
||||
weekday: 'long'
|
||||
});
|
||||
} else {
|
||||
prettyDate = (new Date(time)).toLocaleString(navigator.languages, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var timeString = (new Date(time)).toLocaleString(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
});
|
||||
|
||||
//XXX: This order should not be hardcoded. Change once we have
|
||||
// relative date/time in Intl
|
||||
return prettyDate + ' ' + timeString;
|
||||
};
|
||||
})();
|
||||
}
|
||||
37
gaia/apps/communications/contacts/js/utilities/performance_helper.js
Executable file
37
gaia/apps/communications/contacts/js/utilities/performance_helper.js
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Utility class to fire events needed to measure the performance
|
||||
* of the application.
|
||||
* For an explanation of what means each event go here:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=996038
|
||||
* For an explanation of how are adapted to the specific needs
|
||||
* in the contacts app please go here:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1015388#c8
|
||||
*/
|
||||
(function(){
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
var PerformanceHelper = {
|
||||
domLoaded: function() {
|
||||
window.performance.mark('navigationLoaded');
|
||||
},
|
||||
chromeInteractive: function() {
|
||||
window.performance.mark('navigationInteractive');
|
||||
},
|
||||
visuallyComplete: function() {
|
||||
window.performance.mark('visuallyLoaded');
|
||||
},
|
||||
contentInteractive: function() {
|
||||
window.performance.mark('contentInteractive');
|
||||
},
|
||||
loadEnd: function() {
|
||||
window.performance.mark('fullyLoaded');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
window.utils.PerformanceHelper = PerformanceHelper;
|
||||
|
||||
})();
|
||||
150
gaia/apps/communications/contacts/js/utilities/sim_dom_generator.js
Executable file
150
gaia/apps/communications/contacts/js/utilities/sim_dom_generator.js
Executable file
|
|
@ -0,0 +1,150 @@
|
|||
/* global _ */
|
||||
'use strict';
|
||||
|
||||
// Generates the DOM for each button, depending
|
||||
// on different variables
|
||||
function generateButton(singleSim, sim, index) {
|
||||
var button = document.createElement('button');
|
||||
button.classList.add('icon', 'icon-sim');
|
||||
|
||||
var span = document.createElement('span');
|
||||
if (singleSim) {
|
||||
span.setAttribute('data-l10n-id', 'simCard');
|
||||
} else {
|
||||
var msisdn = sim.iccInfo && sim.iccInfo.msisdn;
|
||||
var l10nId = msisdn ? 'simNumberWithMSISDN' : 'simNumberNoMSISDN';
|
||||
var l10nArgs = {
|
||||
number: index,
|
||||
msisdn: msisdn
|
||||
};
|
||||
navigator.mozL10n.setAttributes(span, l10nId, l10nArgs);
|
||||
}
|
||||
button.appendChild(span);
|
||||
return button;
|
||||
}
|
||||
|
||||
var SimDomGenerator = function SimDomGenerator() {
|
||||
};
|
||||
|
||||
SimDomGenerator.prototype.setIccList = function(list) {
|
||||
this.sims = list;
|
||||
this.singleSim = (list.length === 1);
|
||||
};
|
||||
|
||||
SimDomGenerator.prototype.generateDOM = function() {
|
||||
this.cleanDOM();
|
||||
this.generateExportDOM();
|
||||
this.generateImportDOM();
|
||||
};
|
||||
|
||||
// Specific DOM generation for export options, generating the
|
||||
// following code:
|
||||
//<li id="export-sim-option-<id>" data-source="sim">
|
||||
// <button class="icon icon-sim">
|
||||
// <span data-l10n-id="importSim2">SIM <number> (optional : msisdn)</span>
|
||||
// <p><span></span><time></time></p>
|
||||
// </button>
|
||||
// <p class="error-message" data-l10n-id="noSimMsgExport"></p>
|
||||
//</li>
|
||||
SimDomGenerator.prototype.generateExportDOM = function() {
|
||||
var exportList = document.getElementById('export-options');
|
||||
if (exportList === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var firstOption = exportList.firstChild;
|
||||
|
||||
var self = this;
|
||||
this.sims.forEach(function onSim(sim, index) {
|
||||
if (sim === null) {
|
||||
return;
|
||||
}
|
||||
var iccId = sim.iccInfo.iccid;
|
||||
var li = document.createElement('li');
|
||||
li.id = 'export-sim-option-' + iccId;
|
||||
li.dataset.source = 'sim';
|
||||
li.dataset.iccid = iccId;
|
||||
li.setAttribute('role', 'presentation');
|
||||
|
||||
var button = generateButton(self.singleSim, sim, index + 1);
|
||||
button.setAttribute('role', 'option');
|
||||
|
||||
var p = document.createElement('p');
|
||||
p.classList.add('error-message');
|
||||
p.textContent = _('noSimMsgExport');
|
||||
|
||||
li.appendChild(button);
|
||||
li.appendChild(p);
|
||||
|
||||
exportList.insertBefore(li, firstOption);
|
||||
});
|
||||
};
|
||||
|
||||
// Specific DOM generation for import options, generating the
|
||||
// following code:
|
||||
//<li id="import-sim-option-<id>" data-source="sim-<iccId>">
|
||||
// <button class="icon icon-sim" data-l10n-id="importSim2">
|
||||
// SIM <number> (optional : msisdn)
|
||||
// <p><span></span><time></time></p>
|
||||
// </button>
|
||||
// <p class="error-message" data-l10n-id="noSimMsg"></p>
|
||||
//</li>
|
||||
SimDomGenerator.prototype.generateImportDOM = function() {
|
||||
var importList = document.getElementById('import-options');
|
||||
if (importList === null) {
|
||||
return;
|
||||
}
|
||||
var firstOption = importList.firstChild;
|
||||
|
||||
var self = this;
|
||||
this.sims.forEach(function onSim(sim, index) {
|
||||
if (sim === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var iccId = sim.iccInfo.iccid;
|
||||
var li = document.createElement('li');
|
||||
li.dataset.source = 'sim-' + iccId;
|
||||
li.dataset.iccid = iccId;
|
||||
li.id = 'import-sim-option-' + iccId;
|
||||
li.setAttribute('role', 'presentation');
|
||||
|
||||
var button = generateButton(self.singleSim, sim, index + 1);
|
||||
button.setAttribute('role', 'option');
|
||||
|
||||
var pTime = document.createElement('p');
|
||||
pTime.appendChild(document.createElement('span'));
|
||||
pTime.appendChild(document.createElement('time'));
|
||||
|
||||
button.appendChild(pTime);
|
||||
|
||||
var p = document.createElement('p');
|
||||
p.classList.add('error-message');
|
||||
p.textContent = _('noSimMsg');
|
||||
|
||||
li.appendChild(button);
|
||||
li.appendChild(p);
|
||||
|
||||
importList.insertBefore(li, firstOption);
|
||||
});
|
||||
};
|
||||
|
||||
// Clean any dom elements generated with this class
|
||||
SimDomGenerator.prototype.cleanDOM = function() {
|
||||
// Clean the generated DOM
|
||||
function clean(selector) {
|
||||
var items = Array.prototype.slice.call(
|
||||
document.querySelectorAll(selector));
|
||||
if (!items) {
|
||||
return;
|
||||
}
|
||||
items.forEach(function(node) {
|
||||
node.parentNode.removeChild(node);
|
||||
});
|
||||
}
|
||||
|
||||
// Import
|
||||
clean('#import-options [data-iccid]');
|
||||
// Export
|
||||
clean('#export-options [data-iccid]');
|
||||
};
|
||||
108
gaia/apps/communications/contacts/js/utilities/tagSelector.js
Executable file
108
gaia/apps/communications/contacts/js/utilities/tagSelector.js
Executable file
|
|
@ -0,0 +1,108 @@
|
|||
(function(exports) {
|
||||
|
||||
/* global TAG_OPTIONS, ContactsTag, MainNavigation, LazyLoader */
|
||||
/* exported TagSelector*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var customTag, customTagReset, tagDone, tagHeader, lazyLoadedTagsDom = false;
|
||||
var contactTag;
|
||||
|
||||
function handleCustomTagReset(ev) {
|
||||
ev.preventDefault();
|
||||
if (customTag) {
|
||||
customTag.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function handleCustomTag(ev) {
|
||||
if (ev.keyCode === 13) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
ContactsTag.touchCustomTag();
|
||||
}
|
||||
|
||||
function handleBack(cb) {
|
||||
MainNavigation.back(cb);
|
||||
}
|
||||
|
||||
function handleSelectTagDone() {
|
||||
var prevValue = contactTag.textContent;
|
||||
ContactsTag.clickDone(function() {
|
||||
var valueModifiedEvent = new CustomEvent('ValueModified', {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
prevValue: prevValue,
|
||||
newValue: contactTag.textContent
|
||||
}
|
||||
});
|
||||
contactTag.dispatchEvent(valueModifiedEvent);
|
||||
handleBack();
|
||||
});
|
||||
}
|
||||
|
||||
function showSelectTag() {
|
||||
var tagsList = document.getElementById('tags-list');
|
||||
var selectedTagType = contactTag.dataset.taglist;
|
||||
var options = TAG_OPTIONS[selectedTagType];
|
||||
|
||||
var type = selectedTagType.split('-')[0];
|
||||
var isCustomTagVisible = (document.querySelector(
|
||||
'[data-template]' + '.' + type + '-' +
|
||||
'template').dataset.custom != 'false');
|
||||
|
||||
options = ContactsTag.filterTags(type, contactTag, options);
|
||||
|
||||
if (!customTag) {
|
||||
customTag = document.querySelector('#custom-tag');
|
||||
customTag.addEventListener('keydown', handleCustomTag);
|
||||
customTag.addEventListener('touchend', handleCustomTag);
|
||||
}
|
||||
if (!customTagReset) {
|
||||
customTagReset = document.getElementById('custom-tag-reset');
|
||||
customTagReset.addEventListener('touchstart', handleCustomTagReset);
|
||||
}
|
||||
if (!tagDone) {
|
||||
tagDone = document.querySelector('#settings-done');
|
||||
tagDone.addEventListener('click', handleSelectTagDone);
|
||||
}
|
||||
if (!tagHeader) {
|
||||
tagHeader = document.querySelector('#settings-header');
|
||||
tagHeader.addEventListener('action', handleBack);
|
||||
}
|
||||
|
||||
ContactsTag.setCustomTag(customTag);
|
||||
// Set whether the custom tag is visible or not
|
||||
// This is needed for dates as we only support bday and anniversary
|
||||
// and not custom dates
|
||||
ContactsTag.setCustomTagVisibility(isCustomTagVisible);
|
||||
|
||||
ContactsTag.fillTagOptions(tagsList, contactTag, options);
|
||||
|
||||
MainNavigation.go('view-select-tag', 'right-left');
|
||||
if (document.activeElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
var TagSelector = {
|
||||
|
||||
show: function goToSelectTag(pContactTag) {
|
||||
contactTag = pContactTag;
|
||||
|
||||
var tagViewElement = document.getElementById('view-select-tag');
|
||||
if (!lazyLoadedTagsDom) {
|
||||
LazyLoader.load(tagViewElement, function() {
|
||||
showSelectTag();
|
||||
lazyLoadedTagsDom = true;
|
||||
});
|
||||
}
|
||||
else {
|
||||
showSelectTag();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.TagSelector = TagSelector;
|
||||
}(window));
|
||||
45
gaia/apps/communications/contacts/js/utilities/telemetry.js
Executable file
45
gaia/apps/communications/contacts/js/utilities/telemetry.js
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2015, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
/* global AdvancedTelemetryHelper */
|
||||
/* global LazyLoader */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
var namespace = 'telemetry_gaia_contacts';
|
||||
|
||||
var dependencies = LazyLoader.load(
|
||||
['/shared/js/settings_listener.js',
|
||||
'/shared/js/advanced_telemetry_helper.js']);
|
||||
|
||||
var Telemetry = {
|
||||
logImportUsage: function(name) {
|
||||
return dependencies.then(() => {
|
||||
var histogramName = namespace + '_import_' + name;
|
||||
var count = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_COUNT,
|
||||
histogramName);
|
||||
count.add(1);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.Telemetry = Telemetry;
|
||||
// For testing purposes
|
||||
exports.TelemetryReady = dependencies;
|
||||
|
||||
})(window);
|
||||
Loading…
Reference in a new issue