Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
641 lines
19 KiB
JavaScript
641 lines
19 KiB
JavaScript
'use strict';
|
|
|
|
/* jshint nonew: false */
|
|
|
|
/* global ActivityHandler */
|
|
/* global COMMS_APP_ORIGIN */
|
|
/* global Contacts */
|
|
/* global ContactsButtons */
|
|
/* global ContactPhotoHelper */
|
|
/* globals ContactToVcardBlob */
|
|
/* global fb */
|
|
/* global NFC */
|
|
/* global ICEData */
|
|
/* global LazyLoader */
|
|
/* global MozActivity */
|
|
/* global Normalizer */
|
|
/* global SCALE_RATIO */
|
|
/* global TAG_OPTIONS */
|
|
/* global utils */
|
|
/* global VcardFilename */
|
|
/* global MatchService */
|
|
/* global WebrtcClient */
|
|
/* global MainNavigation */
|
|
/* global ContactsService */
|
|
|
|
var contacts = window.contacts || {};
|
|
|
|
contacts.Details = (function() {
|
|
var photoPos = 7;
|
|
var initMargin = 8;
|
|
var DEFAULT_TEL_TYPE = 'other';
|
|
// If it is a favourite on/off change, I cancel the render
|
|
var isAFavoriteChange = false;
|
|
var contactData,
|
|
contactDetails,
|
|
listContainer,
|
|
detailsName,
|
|
detailsNameText,
|
|
orgTitle,
|
|
datesTemplate,
|
|
addressesTemplate,
|
|
socialTemplate,
|
|
duplicateTemplate,
|
|
notesTemplate,
|
|
isFbContact,
|
|
isFbLinked,
|
|
editContactButton,
|
|
cover,
|
|
favoriteMessage,
|
|
detailsInner,
|
|
dom,
|
|
currentSocial,
|
|
header,
|
|
_;
|
|
|
|
var init = function cd_init(currentDom) {
|
|
_ = navigator.mozL10n.get;
|
|
dom = currentDom || document;
|
|
header = dom.querySelector('#details-view-header');
|
|
contactDetails = dom.querySelector('#contact-detail');
|
|
listContainer = dom.querySelector('#details-list');
|
|
detailsName = dom.querySelector('#contact-name-title');
|
|
detailsNameText = dom.querySelector('#contact-name-title bdi');
|
|
orgTitle = dom.querySelector('#org-title');
|
|
datesTemplate = dom.querySelector('#dates-template-\\#i\\#');
|
|
addressesTemplate = dom.querySelector('#address-details-template-\\#i\\#');
|
|
socialTemplate = dom.querySelector('#social-template-\\#i\\#');
|
|
duplicateTemplate = dom.querySelector('#duplicate-contacts-template');
|
|
editContactButton = dom.querySelector('#edit-contact-button');
|
|
cover = dom.querySelector('#cover-img');
|
|
detailsInner = dom.querySelector('#contact-detail-inner');
|
|
favoriteMessage = dom.querySelector('#toggle-favorite');
|
|
notesTemplate = dom.querySelector('#note-details-template-\\#i\\#');
|
|
|
|
initPullEffect(cover);
|
|
|
|
// to avoid race conditions with NFC, we load it before handleDetails
|
|
LazyLoader.load('/contacts/js/nfc.js', () => {
|
|
favoriteMessage.addEventListener('click', toggleFavorite);
|
|
editContactButton.addEventListener('click', showEditContact);
|
|
header.addEventListener('action', handleDetailsBack);
|
|
});
|
|
|
|
ContactsButtons.init(listContainer, contactDetails, ActivityHandler);
|
|
};
|
|
|
|
var getWebrtcClientResources = function getWebrtcClientResources(cb) {
|
|
if (typeof cb !== 'function') {
|
|
return;
|
|
}
|
|
LazyLoader.load(
|
|
[
|
|
'/contacts/style/webrtc-client/webrtc_client.css',
|
|
'/contacts/js/webrtc-client/webrtc_client.js'
|
|
],
|
|
cb
|
|
);
|
|
};
|
|
|
|
var handleDetailsBack = function handleDetailsBack() {
|
|
// disable NFC listeners when going out of Details view
|
|
stopNFC();
|
|
|
|
if (WebrtcClient) {
|
|
getWebrtcClientResources(WebrtcClient.stop);
|
|
}
|
|
|
|
if (ActivityHandler.currentActivityIsNot(['import'])) {
|
|
ActivityHandler.postCancel();
|
|
MainNavigation.home();
|
|
}
|
|
else if (window.ICEView && window.ICEView.iceListDisplayed) {
|
|
ICEData.getActiveIceContacts().then(function(list) {
|
|
if (!Array.isArray(list) || list.length === 0) {
|
|
MainNavigation.home();
|
|
}
|
|
else {
|
|
doHandleDetailsBack();
|
|
}
|
|
}, doHandleDetailsBack);
|
|
}
|
|
else {
|
|
doHandleDetailsBack();
|
|
}
|
|
};
|
|
|
|
var doHandleDetailsBack = function() {
|
|
var hashParams = window.location.hash.split('?');
|
|
var params = hashParams.length > 1 ?
|
|
utils.extractParams(hashParams[1]) : -1;
|
|
|
|
// post message to parent page included Contacts app.
|
|
if (params.back_to_previous_tab === '1') {
|
|
var message = { 'type': 'contactsiframe', 'message': 'back' };
|
|
window.parent.postMessage(message, COMMS_APP_ORIGIN);
|
|
} else {
|
|
MainNavigation.back(resetPhoto);
|
|
}
|
|
};
|
|
|
|
var showEditContact = function showEditContact() {
|
|
// Disable NFC listeners when editing a contact
|
|
stopNFC();
|
|
Contacts.showForm(true, contactData);
|
|
};
|
|
|
|
var setContact = function cd_setContact(currentContact) {
|
|
contactData = currentContact;
|
|
startNFC(currentContact);
|
|
};
|
|
|
|
// Needed for now because of external call from contacts.js
|
|
var startNFC = function(contact) {
|
|
LazyLoader.load('/contacts/js/nfc.js', () => {
|
|
NFC.startListening(contact);
|
|
});
|
|
};
|
|
|
|
var stopNFC = function() {
|
|
LazyLoader.load('/contacts/js/nfc.js', () => {
|
|
NFC.stopListening();
|
|
});
|
|
};
|
|
|
|
var initPullEffect = function cd_initPullEffect(cover) {
|
|
var maxPosition = Math.round(150 * SCALE_RATIO);
|
|
var startPosition = 0;
|
|
|
|
function onTouchStart(e) {
|
|
if (contactDetails.classList.contains('no-photo')) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
startPosition = e.changedTouches[0].clientY;
|
|
|
|
contactDetails.classList.add('up');
|
|
cover.classList.add('up');
|
|
|
|
window.addEventListener('touchmove', onTouchMove, true);
|
|
window.addEventListener('touchend', onTouchEnd, true);
|
|
}
|
|
|
|
function onTouchEnd(e) {
|
|
e.preventDefault();
|
|
|
|
contactDetails.style.transform = null;
|
|
contactDetails.classList.add('up');
|
|
|
|
cover.style.transform = null;
|
|
cover.classList.add('up');
|
|
|
|
window.removeEventListener('touchmove', onTouchMove, true);
|
|
window.removeEventListener('touchend', onTouchEnd, true);
|
|
}
|
|
|
|
function onTouchMove(e) {
|
|
e.preventDefault();
|
|
|
|
var deltaY = e.changedTouches[0].clientY - startPosition;
|
|
deltaY = Math.min(maxPosition, Math.max(0, deltaY));
|
|
|
|
var calc = 'calc(' + initMargin + 'rem + ' + deltaY + 'px)';
|
|
contactDetails.style.transform = 'translateY(' + calc + ')';
|
|
contactDetails.classList.remove('up');
|
|
|
|
// Divide by 40 (4 times slower and in rems)
|
|
var coverPosition = (-photoPos + (deltaY / 40)) + 'rem';
|
|
cover.style.transform = 'translateY(' + coverPosition + ')';
|
|
cover.classList.remove('up');
|
|
}
|
|
|
|
cover.addEventListener('touchstart', onTouchStart, true);
|
|
};
|
|
|
|
// readOnly tells us if we should allow editing the rendered contact.
|
|
var render = function cd_render(currentContact, fbContactData, readOnly) {
|
|
|
|
if(isAFavoriteChange){
|
|
isAFavoriteChange = false;
|
|
return Promise.resolve(isAFavoriteChange);
|
|
}
|
|
|
|
|
|
contactData = currentContact || contactData;
|
|
|
|
startNFC(contactData);
|
|
|
|
isFbContact = fb.isFbContact(contactData);
|
|
isFbLinked = fb.isFbLinked(contactData);
|
|
|
|
// Initially enabled and only disabled if necessary
|
|
editContactButton.removeAttribute('disabled');
|
|
editContactButton.classList.remove('hide');
|
|
header.setAttribute('action', 'back');
|
|
socialTemplate.classList.remove('hide');
|
|
|
|
if (readOnly) {
|
|
editContactButton.classList.add('hide');
|
|
header.setAttribute('action', 'close');
|
|
socialTemplate.classList.add('hide');
|
|
}
|
|
|
|
if (!fbContactData && isFbContact) {
|
|
var fbContact = new fb.Contact(contactData);
|
|
var req = fbContact.getData();
|
|
|
|
req.onsuccess = function do_reload() {
|
|
doReloadContactDetails(req.result);
|
|
};
|
|
|
|
req.onerror = function() {
|
|
window.console.error('FB: Error while loading FB contact data');
|
|
doReloadContactDetails(contactData);
|
|
};
|
|
} else {
|
|
doReloadContactDetails(fbContactData || contactData);
|
|
}
|
|
};
|
|
|
|
// Fills the contact data to display if no givenName and familyName
|
|
var getDisplayName = function getDisplayName(contact) {
|
|
var name = _('noName');
|
|
|
|
if (hasName(contact)) {
|
|
name = contact.name[0];
|
|
} else if (hasContent(contact.tel)) {
|
|
name = contact.tel[0].value;
|
|
} else if (hasContent(contact.email)) {
|
|
name = contact.email[0].value;
|
|
}
|
|
return name;
|
|
};
|
|
|
|
function hasContent(field) {
|
|
return (Array.isArray(field) &&
|
|
field.length > 0 &&
|
|
field[0].value &&
|
|
field[0].value.trim());
|
|
}
|
|
|
|
function hasName(contact) {
|
|
return (Array.isArray(contact.name) &&
|
|
contact.name[0] &&
|
|
contact.name[0].trim());
|
|
}
|
|
|
|
//
|
|
// Method that generates HTML markup for the contact
|
|
//
|
|
var doReloadContactDetails = function doReloadContactDetails(contact) {
|
|
detailsNameText.textContent = getDisplayName(contact);
|
|
contactDetails.classList.remove('no-photo');
|
|
contactDetails.classList.remove('fb-contact');
|
|
contactDetails.classList.remove('up');
|
|
utils.dom.removeChildNodes(listContainer);
|
|
|
|
renderFavorite(contact);
|
|
renderOrg(contact);
|
|
|
|
ContactsButtons.renderPhones(contact);
|
|
ContactsButtons.renderEmails(contact);
|
|
|
|
renderWebrtcClient(contactData);// Don't share the FB info
|
|
|
|
renderAddresses(contact);
|
|
|
|
renderDates(contact);
|
|
|
|
renderNotes(contact);
|
|
|
|
renderShareButton(contact);
|
|
|
|
if (!fb.isFbContact(contact) || fb.isFbLinked(contact)) {
|
|
renderDuplicate(contact);
|
|
}
|
|
|
|
renderPhoto(contact);
|
|
};
|
|
|
|
var renderFavorite = function cd_renderFavorite(contact) {
|
|
var favorite = isFavorite(contact);
|
|
toggleFavoriteMessage(favorite);
|
|
|
|
header.classList.toggle('favorite', !!favorite);
|
|
};
|
|
|
|
var isFavorite = function isFavorite(contact) {
|
|
return contact != null && contact.category != null &&
|
|
contact.category.indexOf('favorite') != -1;
|
|
};
|
|
|
|
var toggleFavorite = function toggleFavorite() {
|
|
var contact = contactData;
|
|
|
|
var favorite = !isFavorite(contact);
|
|
toggleFavoriteMessage(favorite);
|
|
if (favorite) {
|
|
contact.category = contact.category || [];
|
|
contact.category.push('favorite');
|
|
} else {
|
|
if (!contact.category) {
|
|
return;
|
|
}
|
|
var pos = contact.category.indexOf('favorite');
|
|
if (pos > -1) {
|
|
contact.category.splice(pos, 1);
|
|
}
|
|
}
|
|
|
|
// Disabling button while saving the contact
|
|
favoriteMessage.style.pointerEvents = 'none';
|
|
|
|
var promise = new Promise(function(resolve, reject) {
|
|
ContactsService.save(
|
|
utils.misc.toMozContact(contact),
|
|
function(e) {
|
|
if (e) {
|
|
favoriteMessage.style.pointerEvents = 'auto';
|
|
console.error('Error saving favorite');
|
|
reject('Error saving favorite');
|
|
resolve(false);
|
|
return;
|
|
}
|
|
|
|
isAFavoriteChange = true;
|
|
ContactsService.get(
|
|
contact.id,
|
|
function onSuccess(savedContact) {
|
|
renderFavorite(savedContact);
|
|
setContact(savedContact);
|
|
favoriteMessage.style.pointerEvents = 'auto';
|
|
},
|
|
function onError() {
|
|
console.error('Error reloading contact');
|
|
favoriteMessage.style.pointerEvents = 'auto';
|
|
}
|
|
);
|
|
resolve(isAFavoriteChange);
|
|
}
|
|
);
|
|
}).then();
|
|
|
|
return promise;
|
|
};
|
|
|
|
var toggleFavoriteMessage = function toggleFavMessage(isFav) {
|
|
var cList = favoriteMessage.classList;
|
|
var l10nId = isFav ? 'removeFavorite' : 'addFavorite';
|
|
favoriteMessage.setAttribute('data-l10n-id', l10nId);
|
|
isFav ? cList.add('on') : cList.remove('on');
|
|
};
|
|
|
|
var renderOrg = function cd_renderOrg(contact) {
|
|
if (contact.org && contact.org.length > 0 && contact.org[0] !== '') {
|
|
orgTitle.textContent = contact.org[0];
|
|
orgTitle.classList.remove('hide');
|
|
} else {
|
|
orgTitle.classList.add('hide');
|
|
orgTitle.textContent = '';
|
|
}
|
|
};
|
|
|
|
var renderDates = function cd_renderDates(contact) {
|
|
if (!contact.bday && !contact.anniversary) {
|
|
return;
|
|
}
|
|
|
|
var dateString = '';
|
|
|
|
var fields = ['bday', 'anniversary'];
|
|
var l10nIds = ['birthday', 'anniversary'];
|
|
|
|
var rendered = 0;
|
|
for (var j = 0; j < fields.length; j++) {
|
|
var value = contact[fields[j]];
|
|
if (!value) {
|
|
continue;
|
|
}
|
|
try {
|
|
dateString = utils.misc.formatDate(value);
|
|
} catch (err) {
|
|
console.error('Error parsing date');
|
|
continue;
|
|
}
|
|
var element = utils.templates.render(datesTemplate, {
|
|
i: ++rendered,
|
|
date: dateString,
|
|
type: _(l10nIds[j])
|
|
});
|
|
|
|
listContainer.appendChild(element);
|
|
}
|
|
};
|
|
|
|
var renderShareButton = function cd_renderShareButton(contact) {
|
|
var social = utils.templates.render(socialTemplate, {
|
|
i: contact.id
|
|
});
|
|
currentSocial = social;
|
|
var shareButton = social.querySelector('#share_button');
|
|
|
|
shareButton.addEventListener('click', shareContact);
|
|
shareButton.classList.remove('hide');
|
|
|
|
listContainer.appendChild(social);
|
|
};
|
|
|
|
var renderWebrtcClient = function renderWebrtcClient(contact) {
|
|
getWebrtcClientResources(function onLoaded() {
|
|
WebrtcClient.start(contact);
|
|
});
|
|
};
|
|
|
|
var renderAddresses = function cd_renderAddresses(contact) {
|
|
if (!contact.adr) {
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < contact.adr.length; i++) {
|
|
var currentAddress = contact.adr[i];
|
|
// Sanity check
|
|
if (utils.mozContact.haveEmptyFields(currentAddress,
|
|
['streetAddress', 'postalCode', 'locality', 'countryName'])) {
|
|
continue;
|
|
}
|
|
var address = currentAddress.streetAddress || '';
|
|
var escapedStreet = Normalizer.escapeHTML(address, true);
|
|
var locality = currentAddress.locality;
|
|
var escapedLocality = Normalizer.escapeHTML(locality, true);
|
|
var escapedType = Normalizer.escapeHTML(currentAddress.type, true);
|
|
var country = currentAddress.countryName || '';
|
|
var escapedCountry = Normalizer.escapeHTML(country, true);
|
|
var postalCode = currentAddress.postalCode || '';
|
|
var escapedPostalCode = Normalizer.escapeHTML(postalCode, true);
|
|
|
|
var addressField = {
|
|
streetAddress: escapedStreet,
|
|
postalCode: escapedPostalCode,
|
|
locality: escapedLocality || '',
|
|
countryName: escapedCountry,
|
|
type: _(escapedType) || escapedType ||
|
|
TAG_OPTIONS['address-type'][0].value,
|
|
'type_l10n_id': currentAddress.type,
|
|
i: i
|
|
};
|
|
var template = utils.templates.render(addressesTemplate, addressField);
|
|
listContainer.appendChild(template);
|
|
}
|
|
};
|
|
|
|
var renderDuplicate = function cd_renderDuplicate(contact) {
|
|
var dupItem = utils.templates.render(duplicateTemplate, {});
|
|
var findMergeButton = dupItem.querySelector('#find-merge-button');
|
|
findMergeButton.disabled = true;
|
|
|
|
if (contacts.List.total > 1) {
|
|
// Only have this active if contact list has more than one entry
|
|
findMergeButton.disabled = false;
|
|
findMergeButton.addEventListener('click', function finding() {
|
|
MatchService.match(contact.id);
|
|
});
|
|
}
|
|
|
|
listContainer.appendChild(dupItem);
|
|
};
|
|
|
|
var renderNotes = function cd_rederNotes(contact) {
|
|
if (!contact.note || contact.note.length === 0) {
|
|
return;
|
|
}
|
|
var container = document.createElement('li');
|
|
var title = document.createElement('h2');
|
|
title.setAttribute('data-l10n-id', 'comments');
|
|
container.appendChild(title);
|
|
for (var i = 0; i < contact.note.length; i++) {
|
|
var currentNote = contact.note[i];
|
|
var noteField = {
|
|
note: Normalizer.escapeHTML(currentNote, true) || '',
|
|
i: i
|
|
};
|
|
var template = utils.templates.render(notesTemplate, noteField);
|
|
container.appendChild(template);
|
|
listContainer.appendChild(container);
|
|
}
|
|
};
|
|
|
|
var calculateHash = function calculateHash(photo, cb) {
|
|
var START_BYTES = 127;
|
|
var BYTES_HASH = 16;
|
|
|
|
var out = [photo.type, photo.size];
|
|
|
|
// We skip the first bytes that typically are headers
|
|
var chunk = photo.slice(START_BYTES, START_BYTES + BYTES_HASH);
|
|
var reader = new FileReader();
|
|
reader.onloadend = function() {
|
|
out.push(reader.result);
|
|
cb(out.join(''));
|
|
};
|
|
reader.onerror = function() {
|
|
window.console.error('Error while calculating the hash: ',
|
|
reader.error.name);
|
|
cb(out.join(''));
|
|
};
|
|
reader.readAsDataURL(chunk);
|
|
};
|
|
|
|
var updateHash = function updateHash(photo, cover) {
|
|
calculateHash(photo, function(hash) {
|
|
cover.dataset.imgHash = hash;
|
|
});
|
|
};
|
|
|
|
var renderPhoto = function cd_renderPhoto(contact) {
|
|
contactDetails.classList.remove('up');
|
|
if (isFbContact) {
|
|
contactDetails.classList.add('fb-contact');
|
|
}
|
|
|
|
var photo = ContactPhotoHelper.getFullResolution(contact);
|
|
if (photo) {
|
|
var currentHash = cover.dataset.imgHash;
|
|
if (!currentHash) {
|
|
utils.dom.updatePhoto(photo, cover);
|
|
updateHash(photo, cover);
|
|
}
|
|
else {
|
|
// Need to recalculate the hash and see whether the images changed
|
|
calculateHash(photo, function(newHash) {
|
|
if (currentHash !== newHash) {
|
|
utils.dom.updatePhoto(photo, cover);
|
|
cover.dataset.imgHash = newHash;
|
|
}
|
|
else {
|
|
// Only for testing purposes
|
|
cover.dataset.photoReady = 'true';
|
|
}
|
|
});
|
|
}
|
|
|
|
contactDetails.classList.add('up');
|
|
cover.classList.add('translated');
|
|
contactDetails.classList.add('translated');
|
|
var clientHeight = contactDetails.clientHeight -
|
|
(initMargin * 10 * SCALE_RATIO);
|
|
if (detailsInner.offsetHeight < clientHeight) {
|
|
cover.style.overflow = 'hidden';
|
|
} else {
|
|
cover.style.overflow = 'auto';
|
|
}
|
|
} else {
|
|
resetPhoto();
|
|
}
|
|
};
|
|
|
|
var resetPhoto = function cd_resetPhoto() {
|
|
cover.classList.remove('translated');
|
|
contactDetails.classList.remove('translated');
|
|
cover.style.backgroundImage = '';
|
|
cover.style.overflow = 'auto';
|
|
contactDetails.style.transform = '';
|
|
contactDetails.classList.add('no-photo');
|
|
cover.dataset.imgHash = '';
|
|
};
|
|
|
|
var shareContact = function cd_shareContact() {
|
|
const VCARD_DEPS = [
|
|
'/shared/js/text_normalizer.js',
|
|
'/shared/js/contact2vcard.js',
|
|
'/shared/js/setImmediate.js'
|
|
];
|
|
|
|
LazyLoader.load(VCARD_DEPS,function vcardLoaded() {
|
|
ContactToVcardBlob([contactData], function blobReady(vcardBlob) {
|
|
VcardFilename(contactData).then(filename => {
|
|
new MozActivity({
|
|
name: 'share',
|
|
data: {
|
|
type: 'text/vcard',
|
|
number: 1,
|
|
blobs: [new window.File([vcardBlob], filename, {
|
|
type: 'text/x-vcard'
|
|
})],
|
|
filenames: [filename]
|
|
}
|
|
});
|
|
});
|
|
// The MIME of the blob should be this for some MMS gateways
|
|
}, { type: 'text/x-vcard'} );
|
|
});
|
|
};
|
|
|
|
return {
|
|
'init': init,
|
|
'setContact': setContact,
|
|
'toggleFavorite': toggleFavorite,
|
|
'render': render,
|
|
'defaultTelType': DEFAULT_TEL_TYPE,
|
|
'startNFC': startNFC
|
|
};
|
|
})();
|