Fix camera switching and Contacts/SMS activity routing across shared hosts. Integrate Marketplace installation and native standalone recovery tools, Bluetooth runtime packages and trusted certificates. Update English/French READMEs and the shared changelog with tested scope and remaining radio/audio/Bluetooth limitations. Preview17 remains the existing APK; no new native image is released. Validation: shared JavaScript and native Python tests; Android packaging and payload parity from an isolated publication checkout.
1509 lines
45 KiB
JavaScript
Executable file
1509 lines
45 KiB
JavaScript
Executable file
'use strict';
|
|
/* global ActivityHandler */
|
|
/* global ConfirmDialog */
|
|
/* global ContactPhotoHelper */
|
|
/* global Contacts */
|
|
/* global ContactsTag */
|
|
/* global fb */
|
|
/* global LazyLoader */
|
|
/* global MozActivity */
|
|
/* global Normalizer */
|
|
/* global ContactsService */
|
|
/* global utils */
|
|
/* global TAG_OPTIONS */
|
|
/* global ActionMenu */
|
|
/* global ICEData */
|
|
/* global MergeHelper */
|
|
/* global MainNavigation */
|
|
/* global MatchService */
|
|
/* global ContactsService */
|
|
/* global Matcher */
|
|
/* global Search */
|
|
/* global TagSelector */
|
|
|
|
var contacts = window.contacts || {};
|
|
|
|
contacts.Form = (function() {
|
|
var counters = {
|
|
'tel': 0,
|
|
'email': 0,
|
|
'adr': 0,
|
|
'date': 0,
|
|
'note': 0
|
|
};
|
|
|
|
var currentContact = {};
|
|
var dom,
|
|
contactForm,
|
|
deleteContactButton,
|
|
addNewDateButton,
|
|
thumb,
|
|
thumbAction,
|
|
saveButton,
|
|
formHeader,
|
|
formTitle,
|
|
currentContactId,
|
|
givenName,
|
|
company,
|
|
familyName,
|
|
configs,
|
|
_,
|
|
formView,
|
|
throbber,
|
|
mode,
|
|
cancelHandler,
|
|
mergeHandler,
|
|
nonEditableValues,
|
|
deviceContact,
|
|
fbContact,
|
|
currentPhoto;
|
|
|
|
const CONTACTS_APP_ORIGIN = location.origin;
|
|
|
|
var FB_CLASS = 'facebook';
|
|
var INVALID_CLASS = 'invalid';
|
|
|
|
// Remove icon button id
|
|
var IMG_DELETE_CLASS = 'img-delete-button';
|
|
|
|
// The size we want our contact photos to be
|
|
var PHOTO_WIDTH = 320;
|
|
var PHOTO_HEIGHT = 320;
|
|
// bug 1038414: ask for an image about 2MP before
|
|
// doing the crop to save memory in both apps
|
|
var MAX_PHOTO_SIZE = 200000;
|
|
|
|
var touchstart = 'ontouchstart' in window ? 'touchstart' : 'mousedown';
|
|
|
|
// Indicates whether a tel number has been deleted
|
|
// (useful for warning about ICE Contacts)
|
|
var deletedTelNumber = false;
|
|
|
|
var textFieldsCache = {
|
|
_textFields: null,
|
|
|
|
get: function textFieldsCache_get() {
|
|
if (!this._textFields) {
|
|
var fields = contactForm.querySelectorAll('input[data-field]');
|
|
|
|
var fbFields =
|
|
Array.slice(contactForm.querySelectorAll(
|
|
'.facebook input[data-field]'));
|
|
var invalidFields =
|
|
Array.slice(contactForm.querySelectorAll(
|
|
'.invalid input[data-field]'));
|
|
|
|
this._textFields = Array.filter(fields, function(field) {
|
|
return (fbFields.indexOf(field) === -1 &&
|
|
invalidFields.indexOf(field) === -1);
|
|
});
|
|
}
|
|
|
|
return this._textFields;
|
|
},
|
|
|
|
clear: function textFieldsCache_clear() {
|
|
this._textFields = null;
|
|
}
|
|
};
|
|
|
|
var initContainers = function cf_initContainers() {
|
|
deleteContactButton = dom.querySelector('#delete-contact');
|
|
thumb = dom.querySelector('#thumbnail-photo');
|
|
thumbAction = dom.querySelector('#thumbnail-action');
|
|
thumbAction.querySelector('#photo-button').onclick = photoAction;
|
|
saveButton = dom.querySelector('#save-button');
|
|
addNewDateButton = dom.querySelector('#add-new-date');
|
|
contactForm = dom.getElementById('contact-form');
|
|
formHeader = dom.querySelector('#contact-form-header');
|
|
formTitle = dom.getElementById('contact-form-title');
|
|
currentContactId = dom.getElementById('contact-form-id');
|
|
givenName = dom.getElementById('givenName');
|
|
company = dom.getElementById('org');
|
|
familyName = dom.getElementById('familyName');
|
|
formView = dom.getElementById('view-contact-form');
|
|
throbber = dom.getElementById('throbber');
|
|
var phonesContainer = dom.getElementById('contacts-form-phones');
|
|
var emailContainer = dom.getElementById('contacts-form-emails');
|
|
var addressContainer = dom.getElementById('contacts-form-addresses');
|
|
var dateContainer = dom.getElementById('contacts-form-dates');
|
|
var noteContainer = dom.getElementById('contacts-form-notes');
|
|
var phoneTemplate = dom.getElementById('add-phone-#i#');
|
|
var emailTemplate = dom.getElementById('add-email-#i#');
|
|
var addressTemplate = dom.getElementById('add-address-#i#');
|
|
var dateTemplate = dom.getElementById('add-date-#i#');
|
|
var noteTemplate = dom.getElementById('add-note-#i#');
|
|
configs = {
|
|
'tel': {
|
|
template: phoneTemplate,
|
|
tags: TAG_OPTIONS['phone-type'],
|
|
fields: ['value', 'type', 'carrier'],
|
|
container: phonesContainer
|
|
},
|
|
'email': {
|
|
template: emailTemplate,
|
|
tags: TAG_OPTIONS['email-type'],
|
|
fields: ['value', 'type'],
|
|
container: emailContainer
|
|
},
|
|
'adr': {
|
|
template: addressTemplate,
|
|
tags: TAG_OPTIONS['address-type'],
|
|
fields: [
|
|
'type',
|
|
'streetAddress',
|
|
'postalCode',
|
|
'locality',
|
|
'countryName'
|
|
],
|
|
container: addressContainer
|
|
},
|
|
'date': {
|
|
template: dateTemplate,
|
|
tags: TAG_OPTIONS['date-type'],
|
|
fields: ['value', 'type'],
|
|
container: dateContainer
|
|
},
|
|
'note': {
|
|
template: noteTemplate,
|
|
tags: TAG_OPTIONS['address-type'],
|
|
fields: ['note'],
|
|
container: noteContainer
|
|
}
|
|
};
|
|
};
|
|
|
|
var init = function cf_init(tags, currentDom) {
|
|
dom = currentDom || document;
|
|
|
|
_ = navigator.mozL10n.get;
|
|
initContainers();
|
|
|
|
dom.addEventListener('input', function input(event) {
|
|
checkDisableButton();
|
|
});
|
|
|
|
contactForm.addEventListener(touchstart, function click(event) {
|
|
var tgt = event.target;
|
|
if (tgt.tagName == 'BUTTON' && tgt.getAttribute('type') == 'reset') {
|
|
event.preventDefault();
|
|
var input = tgt.previousElementSibling;
|
|
|
|
if (input.getAttribute('name').startsWith('tel') &&
|
|
input.dataset.field === 'value') {
|
|
var telId = input.id;
|
|
var telIndex = telId.substring(telId.indexOf('_') + 1);
|
|
var carrierField =
|
|
document.getElementById('carrier' + '_' + telIndex);
|
|
carrierField.parentNode.classList.add(INVALID_CLASS);
|
|
|
|
textFieldsCache.clear();
|
|
deletedTelNumber = true;
|
|
}
|
|
input.value = '';
|
|
checkDisableButton();
|
|
}
|
|
});
|
|
|
|
thumbAction.addEventListener(touchstart, function click(event) {
|
|
// Removing current photo
|
|
if (event.target.tagName == 'BUTTON') {
|
|
saveButton.removeAttribute('disabled');
|
|
}
|
|
});
|
|
|
|
formView.addEventListener('ValueModified', function onValueModified(event) {
|
|
if (!event.detail) {
|
|
return;
|
|
}
|
|
|
|
if (!emptyForm() && event.detail.prevValue !== event.detail.newValue) {
|
|
saveButton.removeAttribute('disabled');
|
|
}
|
|
});
|
|
|
|
// Add listeners
|
|
utils.listeners.add({
|
|
'#contact-form-header': [
|
|
{
|
|
event: 'action',
|
|
handler: Contacts.cancel // Cancel edition
|
|
}
|
|
],
|
|
'#save-button': saveContact,
|
|
'#contact-form button[data-field-type]': newField
|
|
});
|
|
};
|
|
|
|
// Renders the birthday as per the locale
|
|
function renderDate(date, bdayInputText) {
|
|
if (!date) {
|
|
return;
|
|
}
|
|
|
|
bdayInputText.textContent = utils.misc.formatDate(date, {
|
|
year: 'numeric',
|
|
month: 'numeric',
|
|
day: 'numeric'
|
|
});
|
|
bdayInputText.classList.remove('placeholder');
|
|
}
|
|
|
|
function onInputDate(bdayInputText, e) {
|
|
renderDate(e.target.valueAsDate, bdayInputText);
|
|
}
|
|
|
|
var newField = function newField(evt) {
|
|
return contacts.Form.onNewFieldClicked(evt);
|
|
};
|
|
|
|
var render = function cf_render(contact, callback, pFbContactData,
|
|
fromUpdateActivity) {
|
|
var fbContactData = pFbContactData || [];
|
|
|
|
fbContact = fbContactData[2] || {};
|
|
nonEditableValues = fbContactData[1] || {};
|
|
deviceContact = contact;
|
|
var renderedContact = fbContactData[0] || deviceContact;
|
|
|
|
resetForm();
|
|
|
|
// We need to check against the string 'undefined' because of bug 951829.
|
|
renderedContact && renderedContact.id && renderedContact.id != 'undefined' ?
|
|
showEdit(renderedContact, fromUpdateActivity) : showAdd(renderedContact);
|
|
|
|
// reset the scroll from (possible) previous renders
|
|
contactForm.parentNode.scrollTop = 0;
|
|
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
};
|
|
|
|
function extractValue(value) {
|
|
if (value) {
|
|
if (Array.isArray(value) && value.length > 0 && value[0]) {
|
|
return value[0].trim();
|
|
} else if (!Array.isArray(value)) {
|
|
return value.trim();
|
|
}
|
|
}
|
|
|
|
return '';
|
|
// The last statement allows to return the value in case we are getting
|
|
// just params from an activity and not a mozContact.
|
|
}
|
|
|
|
function fillDates(contact) {
|
|
if (contact.bday) {
|
|
contact.date = [];
|
|
|
|
contact.date.push({
|
|
type: 'birthday',
|
|
value: contact.bday
|
|
});
|
|
}
|
|
|
|
if (contact.anniversary) {
|
|
contact.date = contact.date || [];
|
|
contact.date.push({
|
|
type: 'anniversary',
|
|
value: contact.anniversary
|
|
});
|
|
}
|
|
}
|
|
|
|
function renderPhoto(contact) {
|
|
if (contact.photo && contact.photo.length > 0) {
|
|
currentPhoto = ContactPhotoHelper.getFullResolution(contact);
|
|
var button = addRemoveIconToPhoto();
|
|
// Only can be removed a device contact photo
|
|
if (!(deviceContact.photo && deviceContact.photo.length > 0)) {
|
|
button.classList.add('hide');
|
|
// Avoid saving the image to the Contacts DB
|
|
thumbAction.classList.add(FB_CLASS);
|
|
}
|
|
}
|
|
|
|
utils.dom.updatePhoto(currentPhoto, thumb);
|
|
}
|
|
|
|
var showEdit = function showEdit(contact, fromUpdateActivity) {
|
|
mode = 'edit';
|
|
if (!contact || !contact.id) {
|
|
return;
|
|
}
|
|
if (!fromUpdateActivity) {
|
|
saveButton.setAttribute('disabled', 'disabled');
|
|
}
|
|
saveButton.setAttribute('data-l10n-id', 'update');
|
|
currentContact = contact;
|
|
deleteContactButton.parentNode.classList.remove('hide');
|
|
formTitle.setAttribute('data-l10n-id', 'editContact');
|
|
currentContactId.value = contact.id;
|
|
givenName.value = extractValue(contact.givenName);
|
|
familyName.value = extractValue(contact.familyName);
|
|
company.value = extractValue(contact.org);
|
|
|
|
if (nonEditableValues[company.value]) {
|
|
var nodeClass = company.parentNode.classList;
|
|
nodeClass.add(FB_CLASS);
|
|
}
|
|
|
|
renderPhoto(contact);
|
|
|
|
fillDates(contact);
|
|
|
|
['tel', 'email', 'adr', 'date', 'note'].forEach(function(field) {
|
|
renderTemplate(field, contact[field]);
|
|
});
|
|
|
|
deleteContactButton.onclick = function deleteClicked(event) {
|
|
var msg = 'deleteConfirmMsg';
|
|
var yesObject = {
|
|
title: 'delete',
|
|
isDanger: true,
|
|
callback: function onAccept() {
|
|
deleteContact(currentContact);
|
|
ConfirmDialog.hide();
|
|
if (ActivityHandler.currentlyHandling) {
|
|
formHeader.triggerAction();
|
|
}
|
|
}
|
|
};
|
|
|
|
var noObject = {
|
|
title: 'cancel',
|
|
callback: function onCancel() {
|
|
ConfirmDialog.hide();
|
|
}
|
|
};
|
|
|
|
ConfirmDialog.show(null, msg, noObject, yesObject);
|
|
};
|
|
};
|
|
|
|
// Checks whether is an ICE contact or not
|
|
function isIceContact(contact, cb) {
|
|
LazyLoader.load('js/utilities/ice_data.js', function() {
|
|
ICEData.getActiveIceContacts().then(function iceloaded(iceContacts) {
|
|
cb(iceContacts.indexOf(contact.id) !== -1);
|
|
});
|
|
});
|
|
}
|
|
|
|
var showAdd = function showAdd(params) {
|
|
mode = 'add';
|
|
formView.classList.remove('skin-organic');
|
|
if (!params || params == -1 || !params.id) {
|
|
currentContact = {};
|
|
}
|
|
saveButton.setAttribute('disabled', 'disabled');
|
|
saveButton.setAttribute('data-l10n-id', 'done');
|
|
deleteContactButton.parentNode.classList.add('hide');
|
|
formTitle.setAttribute('data-l10n-id', 'addContact');
|
|
|
|
params = params || {};
|
|
|
|
givenName.value = extractValue(params.givenName);
|
|
familyName.value = extractValue(params.lastName || params.familyName);
|
|
company.value = extractValue(params.company || params.org);
|
|
|
|
renderPhoto(params);
|
|
|
|
fillDates(params);
|
|
|
|
['tel', 'email', 'adr', 'date', 'note'].forEach(function(field) {
|
|
renderTemplate(field, params[field]);
|
|
});
|
|
|
|
checkDisableButton();
|
|
};
|
|
|
|
|
|
// template, fields, cont, counter
|
|
/**
|
|
* Render Template
|
|
*
|
|
* @param {string} type Type of template, eg. 'tel'
|
|
* @param {object[]} toRender
|
|
*/
|
|
var renderTemplate = function cf_rendTemplate(type, toRender) {
|
|
if (!Array.isArray(toRender)) {
|
|
toRender = [{value: toRender}];
|
|
}
|
|
|
|
for (var i = 0; i < toRender.length; i++) {
|
|
insertField(type, toRender[i] || {});
|
|
}
|
|
};
|
|
|
|
function checkAddDateButton() {
|
|
addNewDateButton.disabled = (getActiveFormDates() >= 2);
|
|
}
|
|
|
|
function getActiveFormDates() {
|
|
var fbDates = dom.querySelectorAll('.date-template' + '.' + FB_CLASS);
|
|
|
|
return counters.date - fbDates.length;
|
|
}
|
|
|
|
var onNewFieldClicked = function onNewFieldClicked(evt) {
|
|
var type = evt.target.dataset.fieldType;
|
|
evt.preventDefault();
|
|
// Workaround until 809452 is fixed.
|
|
// What we are avoiding with this condition is removing / restoring
|
|
// a field when the event is simulated by a ENTER Keyboard click
|
|
if (evt.explicitOriginalTarget === evt.target) {
|
|
contacts.Form.insertField(type, null, [
|
|
'inserted',
|
|
'displayed'
|
|
]);
|
|
}
|
|
textFieldsCache.clear();
|
|
// For dates only two instances
|
|
if (type === 'date') {
|
|
// Disable the add date button if necessary
|
|
checkAddDateButton();
|
|
}
|
|
return false;
|
|
};
|
|
|
|
function checkCarrierTel(carrierInput, event) {
|
|
var telInput = event.target;
|
|
var value = telInput.value;
|
|
|
|
if (!value || !value.trim()) {
|
|
deletedTelNumber = true;
|
|
|
|
// If it was not previously filled then it will be disabled
|
|
if (!telInput.dataset.wasFilled) {
|
|
carrierInput.setAttribute('disabled', 'disabled');
|
|
}
|
|
else {
|
|
// Otherwise marked as invalid in order not to submit it
|
|
carrierInput.parentNode.classList.add(INVALID_CLASS);
|
|
textFieldsCache.clear();
|
|
}
|
|
}
|
|
else {
|
|
deletedTelNumber = false;
|
|
|
|
// Marked as filled
|
|
telInput.dataset.wasFilled = true;
|
|
// Enabling and marking as valid
|
|
carrierInput.removeAttribute('disabled');
|
|
carrierInput.parentNode.classList.remove(INVALID_CLASS);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* We cannot relay on the counter, but in the next id after the
|
|
* last field.
|
|
* See bug 1113134 for related explanation.
|
|
*/
|
|
function getNextTemplateId(container) {
|
|
var nodes = container.childNodes;
|
|
if (!nodes || nodes.length === 0) {
|
|
return 0;
|
|
}
|
|
|
|
var lastNode = nodes[nodes.length - 1];
|
|
var value = lastNode.dataset.index;
|
|
return value ? parseInt(value) + 1 : 0;
|
|
}
|
|
|
|
var insertField = function insertField(type, object, targetClasses) {
|
|
if (!type || !configs[type]) {
|
|
console.error('Inserting field with unknown type');
|
|
return;
|
|
}
|
|
var obj = object || {};
|
|
var config = configs[type];
|
|
var template = config.template;
|
|
var tags = ContactsTag.filterTags(type, null, config.tags);
|
|
|
|
var container = config.container;
|
|
|
|
var default_type = tags[0] && tags[0].type || '';
|
|
var currField = {};
|
|
var infoFromFB = false;
|
|
|
|
config.fields.forEach(function(currentElem) {
|
|
var def = (currentElem === 'type') ? default_type : '';
|
|
var defObj = (typeof(obj) === 'string') ? obj : obj[currentElem];
|
|
var value = '';
|
|
var isDate = (defObj && typeof defObj.getMonth === 'function');
|
|
|
|
currField[currentElem] = (defObj && typeof(defObj) === 'object' &&
|
|
!isDate ? defObj.toString() : defObj);
|
|
value = currField[currentElem] || def;
|
|
if (currentElem === 'type') {
|
|
currField.type_value = value;
|
|
|
|
// Do localization for built-in types
|
|
if (isBuiltInType(value, tags)) {
|
|
currField.type_l10n_id = value;
|
|
value = _(value) || value;
|
|
}
|
|
}
|
|
if (!isDate) {
|
|
currField[currentElem] = Normalizer.escapeHTML(value, true);
|
|
}
|
|
|
|
if (!infoFromFB && value && nonEditableValues[value]) {
|
|
infoFromFB = true;
|
|
}
|
|
});
|
|
currField.i = getNextTemplateId(container);
|
|
|
|
var rendered = utils.templates.render(template, currField);
|
|
// Controlling that if no tel phone is present carrier field is disabled
|
|
if (type === 'tel') {
|
|
var carrierInput = rendered.querySelector('input[data-field="carrier"]');
|
|
var telInput = rendered.querySelector('input[data-field="value"]');
|
|
|
|
var cb = checkCarrierTel.bind(null, carrierInput);
|
|
|
|
telInput.addEventListener('input', cb, true);
|
|
|
|
checkCarrierTel(carrierInput, {target: telInput});
|
|
}
|
|
|
|
// Adding listener to properly render dates
|
|
if (type === 'date') {
|
|
var dateInput = rendered.querySelector('input[type="date"]');
|
|
|
|
// Setting the max value as today's date
|
|
var currentDate = new Date();
|
|
dateInput.setAttribute('max', currentDate.getFullYear() + '-' +
|
|
(currentDate.getMonth() + 1) + '-' +
|
|
currentDate.getDate());
|
|
|
|
var dateInputText = dateInput.previousElementSibling;
|
|
if (currField.value) {
|
|
dateInput.valueAsDate = currField.value;
|
|
renderDate(currField.value, dateInputText);
|
|
}
|
|
else {
|
|
dateInputText.setAttribute('data-l10n-id', 'date-span-placeholder');
|
|
}
|
|
|
|
dateInput.addEventListener('input',
|
|
onInputDate.bind(null, dateInputText));
|
|
}
|
|
|
|
if (infoFromFB) {
|
|
var nodeClass = rendered.classList;
|
|
nodeClass.add(FB_CLASS);
|
|
}
|
|
|
|
// The remove button should not appear on FB disabled fields
|
|
if (!rendered.classList.contains(FB_CLASS)) {
|
|
var removeEl = removeFieldIcon(rendered.id, type);
|
|
rendered.insertBefore(removeEl, rendered.firstChild);
|
|
}
|
|
|
|
// Add event listeners
|
|
var boxTitle = rendered.querySelector('legend.action');
|
|
if (boxTitle) {
|
|
boxTitle.addEventListener('click', onGoToSelectTag);
|
|
}
|
|
|
|
// This will happen when the fields are added by the user on demand
|
|
if (Array.isArray(targetClasses)) {
|
|
rendered.classList.add(targetClasses[0]);
|
|
window.setTimeout(() => rendered.classList.add(targetClasses[1]));
|
|
}
|
|
|
|
container.classList.remove('empty');
|
|
container.appendChild(rendered);
|
|
counters[type]++;
|
|
|
|
// Finally we need to check the status of the add date button
|
|
if (type === 'date') {
|
|
checkAddDateButton();
|
|
}
|
|
};
|
|
|
|
var onGoToSelectTag = function onGoToSelectTag(evt) {
|
|
evt.preventDefault();
|
|
var target = evt.currentTarget;
|
|
LazyLoader.load('/contacts/js/utilities/tagSelector.js', function() {
|
|
TagSelector.show(target.children[0]);
|
|
});
|
|
return false;
|
|
};
|
|
|
|
|
|
var deleteContact = function deleteContact(contact) {
|
|
var deleteSuccess = function deleteSuccess() {
|
|
if (window.Search && Search.isInSearchMode()) {
|
|
Search.invalidateCache();
|
|
Search.removeContact(contact.id);
|
|
Search.exitSearchMode();
|
|
}
|
|
|
|
MainNavigation.home();
|
|
};
|
|
|
|
ContactsService.remove(
|
|
contact,
|
|
function(e) {
|
|
if (e) {
|
|
console.error('Error removing the contact');
|
|
return;
|
|
}
|
|
deleteSuccess();
|
|
}
|
|
);
|
|
};
|
|
|
|
var getCurrentPhoto = function cf_getCurrentPhoto() {
|
|
var photo;
|
|
var isFacebook = thumbAction.classList.contains(FB_CLASS);
|
|
if (!isFacebook) {
|
|
photo = currentPhoto;
|
|
}
|
|
return photo; // we return undefined on purpose here
|
|
};
|
|
|
|
var CATEGORY_WHITE_LIST = ['gmail', 'live'];
|
|
function updateCategoryForImported(contact) {
|
|
if (Array.isArray(contact.category)) {
|
|
var total = CATEGORY_WHITE_LIST.length;
|
|
var idx = -1;
|
|
for (var i = 0; i < total; i++) {
|
|
idx = contact.category.indexOf(CATEGORY_WHITE_LIST[i]);
|
|
if (idx !== -1) {
|
|
break;
|
|
}
|
|
}
|
|
if (idx !== -1) {
|
|
contact.category[idx] = contact.category[idx] + '/updated';
|
|
}
|
|
}
|
|
}
|
|
|
|
var fillContact = function(contact, done) {
|
|
createName(contact);
|
|
|
|
getPhones(contact);
|
|
getEmails(contact);
|
|
getAddresses(contact);
|
|
getNotes(contact);
|
|
getDates(contact);
|
|
|
|
var currentPhoto = getCurrentPhoto();
|
|
if (!currentPhoto) {
|
|
done(contact);
|
|
return;
|
|
}
|
|
|
|
utils.thumbnailImage(currentPhoto, function gotTumbnail(thumbnail) {
|
|
if (currentPhoto !== thumbnail) {
|
|
contact.photo = [currentPhoto, thumbnail];
|
|
} else {
|
|
contact.photo = [currentPhoto];
|
|
}
|
|
done(contact);
|
|
});
|
|
};
|
|
|
|
var saveContact = function saveContact() {
|
|
saveButton.setAttribute('disabled', 'disabled');
|
|
showThrobber();
|
|
|
|
var cancelObject = {
|
|
title: 'ok',
|
|
callback: function onCancel() {
|
|
ConfirmDialog.hide();
|
|
continueSavingContact();
|
|
}
|
|
};
|
|
|
|
if (deletedTelNumber) {
|
|
isIceContact(currentContact, function(result) {
|
|
if (result === true) {
|
|
var msgId = 'ICEContactDelTel';
|
|
var selector = 'div:not([data-template]) .textfield[type="tel"]';
|
|
var telInputs = document.querySelectorAll(selector);
|
|
var hasNumber = false;
|
|
|
|
for (var i = 0, len = telInputs.length; i < len; i++) {
|
|
if (telInputs[i].value.trim()) {
|
|
hasNumber = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!hasNumber) {
|
|
msgId = 'ICEContactDelTelAll';
|
|
ICEData.removeICEContact(currentContact.id);
|
|
}
|
|
ConfirmDialog.show(null, {'id': msgId},
|
|
cancelObject);
|
|
}
|
|
else {
|
|
continueSavingContact();
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
continueSavingContact();
|
|
}
|
|
};
|
|
|
|
function continueSavingContact() {
|
|
currentContact = currentContact || {};
|
|
currentContact = deviceContact || currentContact;
|
|
var deviceGivenName = currentContact.givenName || [''];
|
|
var deviceFamilyName = currentContact.familyName || [''];
|
|
|
|
var myContact = {
|
|
id: document.getElementById('contact-form-id').value,
|
|
additionalName: [''],
|
|
name: ['']
|
|
};
|
|
|
|
var inputs = { givenName, familyName };
|
|
|
|
for (var field in inputs) {
|
|
var value = inputs[field].value;
|
|
if (value && value.length > 0) {
|
|
myContact[field] = [value];
|
|
} else {
|
|
myContact[field] = null;
|
|
}
|
|
}
|
|
|
|
if (!company.parentNode.classList.contains(FB_CLASS) &&
|
|
company.value && company.value.length > 0) {
|
|
myContact.org = [company.value];
|
|
}
|
|
|
|
if (currentContact.category) {
|
|
myContact.category = currentContact.category;
|
|
}
|
|
|
|
fillContact(myContact, function contactFilled(myContact) {
|
|
// Check if all fields but address are empty
|
|
// and inspect address by it self.
|
|
var fields = ['givenName', 'familyName', 'org', 'tel',
|
|
'email', 'note', 'bday', 'anniversary', 'adr'];
|
|
|
|
// Load what we need to check myContact
|
|
LazyLoader.load('/contacts/js/utilities/mozContact.js', function() {
|
|
if (utils.mozContact.haveEmptyFields(myContact, fields)) {
|
|
return;
|
|
}
|
|
|
|
var contact;
|
|
if (myContact.id) { //Editing a contact
|
|
currentContact.tel = [];
|
|
currentContact.email = [];
|
|
currentContact.org = [];
|
|
currentContact.adr = [];
|
|
currentContact.note = [];
|
|
currentContact.photo = [];
|
|
currentContact.bday = null;
|
|
currentContact.anniversary = null;
|
|
var readOnly = ['id', 'updated', 'published'];
|
|
for (var field in myContact) {
|
|
if (readOnly.indexOf(field) == -1) {
|
|
currentContact[field] = myContact[field];
|
|
}
|
|
}
|
|
contact = currentContact;
|
|
|
|
if (fb.isFbContact(contact)) {
|
|
// If it is a FB Contact not linked it will be automatically linked
|
|
// As now there is additional contact data entered by the user
|
|
if (!fb.isFbLinked(contact)) {
|
|
var fbContact = new fb.Contact(contact);
|
|
// Here the contact has been promoted to linked but not saved yet
|
|
fbContact.promoteToLinked();
|
|
}
|
|
|
|
setPropagatedFlag('givenName', deviceGivenName[0], contact);
|
|
setPropagatedFlag('familyName', deviceFamilyName[0], contact);
|
|
createName(contact);
|
|
}
|
|
} else {
|
|
contact = utils.misc.toMozContact(myContact);
|
|
}
|
|
|
|
updateCategoryForImported(contact);
|
|
|
|
var callbacks = cookMatchingCallbacks(contact);
|
|
cancelHandler = doCancel.bind(callbacks);
|
|
formHeader.addEventListener('action', cancelHandler);
|
|
doMatch(contact, callbacks);
|
|
});
|
|
});
|
|
}
|
|
|
|
var cookMatchingCallbacks = function cookMatchingCallbacks(contact) {
|
|
return {
|
|
onmatch: function(results) {
|
|
MatchService.showDuplicateContacts();
|
|
|
|
mergeHandler = function mergeHandler(e) {
|
|
if (e.origin !== CONTACTS_APP_ORIGIN) {
|
|
return;
|
|
}
|
|
|
|
var data = e.data;
|
|
switch (data.type) {
|
|
case 'duplicate_contacts_loaded':
|
|
// UI ready, passing duplicate contacts
|
|
var duplicateContacts = {};
|
|
Object.keys(results).forEach(function(id) {
|
|
duplicateContacts[id] = {
|
|
matchingContactId: id,
|
|
matchings: results[id].matchings
|
|
};
|
|
});
|
|
|
|
window.postMessage({
|
|
type: 'show_duplicate_contacts',
|
|
data: {
|
|
name: getCompleteName(getDisplayName(contact)),
|
|
duplicateContacts: duplicateContacts
|
|
}
|
|
}, CONTACTS_APP_ORIGIN);
|
|
|
|
break;
|
|
|
|
case 'merge_duplicate_contacts':
|
|
window.removeEventListener('message', mergeHandler);
|
|
Contacts.goBack();
|
|
|
|
// List of duplicate contacts to merge (identifiers)
|
|
var list = [];
|
|
var ids = data.data;
|
|
Object.keys(ids).forEach(id => {
|
|
list.push(results[id]);
|
|
});
|
|
|
|
doMerge(contact, list, function finished() {
|
|
// Contacts merged goes to contact list automatically without
|
|
// saving, the merger does the live better to us
|
|
if (ActivityHandler.currentlyHandling) {
|
|
ActivityHandler.postNewSuccess(contact);
|
|
}
|
|
|
|
window.postMessage({
|
|
type: 'duplicate_contacts_merged',
|
|
data: ids
|
|
}, CONTACTS_APP_ORIGIN);
|
|
});
|
|
|
|
break;
|
|
|
|
case 'ready':
|
|
// The list of duplicate contacts has been loaded
|
|
formHeader.removeEventListener('action', cancelHandler);
|
|
hideThrobber();
|
|
|
|
break;
|
|
|
|
case 'window_close':
|
|
// If user igonores duplicate contacts we save the contact
|
|
window.removeEventListener('message', mergeHandler);
|
|
Contacts.goBack();
|
|
doSave(contact, true);
|
|
|
|
break;
|
|
}
|
|
};
|
|
|
|
window.addEventListener('message', mergeHandler);
|
|
},
|
|
onmismatch: function() {
|
|
// Saving because there aren't duplicate contacts
|
|
doSave(contact);
|
|
formHeader.removeEventListener('action', cancelHandler);
|
|
}
|
|
};
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
// Fills the contact data to display if no givenName and familyName
|
|
function getDisplayName(contact) {
|
|
if (hasName(contact)) {
|
|
return { givenName: contact.givenName, familyName: contact.familyName };
|
|
}
|
|
|
|
var givenName = [];
|
|
if (Array.isArray(contact.name) && contact.name.length > 0) {
|
|
givenName.push(contact.name[0]);
|
|
} else if (contact.org && contact.org.length > 0) {
|
|
givenName.push(contact.org[0]);
|
|
} else if (contact.tel && contact.tel.length > 0) {
|
|
givenName.push(contact.tel[0].value);
|
|
} else if (contact.email && contact.email.length > 0) {
|
|
givenName.push(contact.email[0].value);
|
|
} else {
|
|
givenName.push(_('noName'));
|
|
}
|
|
|
|
return { givenName: givenName, modified: true };
|
|
}
|
|
|
|
function hasName(contact) {
|
|
return (Array.isArray(contact.givenName) && contact.givenName[0] &&
|
|
contact.givenName[0].trim()) ||
|
|
(Array.isArray(contact.familyName) && contact.familyName[0] &&
|
|
contact.familyName[0].trim());
|
|
}
|
|
|
|
|
|
var doMerge = function doMerge(contact, list, cb) {
|
|
var callbacks = {
|
|
success: cb,
|
|
error: function(e) {
|
|
console.error('Failed merging duplicate contacts: ', e.name);
|
|
cb();
|
|
}
|
|
};
|
|
|
|
LazyLoader.load('/contacts/js/utilities/merge_helper.js', function() {
|
|
MergeHelper.merge(contact, list).then(callbacks.success, callbacks.error);
|
|
});
|
|
};
|
|
|
|
var doCancel = function doCancel() {
|
|
formHeader.removeEventListener('action', cancelHandler);
|
|
window.removeEventListener('message', mergeHandler);
|
|
this.onmatch = this.onmismatch = null;
|
|
window.postMessage({
|
|
type: 'abort',
|
|
data: ''
|
|
}, CONTACTS_APP_ORIGIN);
|
|
hideThrobber();
|
|
};
|
|
|
|
var doMatch = function doMatch(contact, callbacks) {
|
|
LazyLoader.load(['/shared/js/text_normalizer.js',
|
|
'/shared/js/simple_phone_matcher.js',
|
|
'/shared/js/contacts/contacts_matcher.js'], function() {
|
|
Matcher.match(contact, 'active', callbacks);
|
|
});
|
|
};
|
|
|
|
var doSave = function doSave(contact, noTransition) {
|
|
// Deleting auxiliary objects created for dates
|
|
delete contact.date;
|
|
|
|
// When we're adding new contact, it shouldn't have id at the beginning.
|
|
// If We have one, means we are editing the current contact.
|
|
// We will use this information below.
|
|
var editingContact = contact.id !== 'undefined';
|
|
|
|
ContactsService.save(
|
|
utils.misc.toMozContact(contact),
|
|
function(e) {
|
|
if (e) {
|
|
hideThrobber();
|
|
console.error('Error saving contact', e);
|
|
return;
|
|
}
|
|
hideThrobber();
|
|
// Reloading contact, as it only allows to be updated once
|
|
if (ActivityHandler.currentlyHandling) {
|
|
ActivityHandler.postNewSuccess(contact);
|
|
}
|
|
if (!noTransition) {
|
|
Contacts.cancel();
|
|
}
|
|
|
|
// Since editing current contact returns to the details view,
|
|
// and adding a new one returns to the contacts list,
|
|
// we call setCurrent() only in the first case,
|
|
// so NFC listeners are not set on the Contact List
|
|
// (Bug 1041455).
|
|
if (editingContact) {
|
|
Contacts.setCurrent(contact);
|
|
// The change event may arrive while the edit form is still visible.
|
|
Contacts.updateContactDetail(contact.id);
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
var showThrobber = function showThrobber() {
|
|
throbber.classList.remove('hide');
|
|
};
|
|
|
|
var hideThrobber = function hideThrobber() {
|
|
throbber.classList.add('hide');
|
|
};
|
|
|
|
/**
|
|
* Creates a complete name from the received contact's `givenName` and
|
|
* `familyName` fields.
|
|
*
|
|
* @param {object} contact MozContactObject to process
|
|
*/
|
|
var createName = function createName(contact) {
|
|
var givenName = '', familyName = '';
|
|
|
|
if (Array.isArray(contact.givenName)) {
|
|
givenName = contact.givenName[0].trim();
|
|
}
|
|
|
|
if (Array.isArray(contact.familyName)) {
|
|
familyName = contact.familyName[0].trim();
|
|
}
|
|
|
|
var completeName = (givenName + ' ' + familyName).trim();
|
|
contact.name = completeName ? [completeName] : [];
|
|
};
|
|
|
|
var setPropagatedFlag = function setPropagatedFlag(field, value, contact) {
|
|
if (!Array.isArray(contact[field]) || !contact[field][0] ||
|
|
!contact[field][0].trim()) {
|
|
// Here the user is deleting completely the field then we get the
|
|
// original facebook field value
|
|
fb.setPropagatedFlag(field, contact);
|
|
contact[field] = fbContact[field];
|
|
} else if (contact[field][0] !== value) {
|
|
// The user is changing the value of the field then we have a local field.
|
|
// It implies not propagation
|
|
fb.removePropagatedFlag(field, contact);
|
|
}
|
|
};
|
|
|
|
function isBuiltInType(type, tagList) {
|
|
return tagList.some(function(tag) {
|
|
return tag.type === type;
|
|
});
|
|
}
|
|
|
|
var getPhones = function getPhones(contact) {
|
|
var selector = '#view-contact-form form div.phone-template:not(.facebook)';
|
|
var phones = dom.querySelectorAll(selector);
|
|
for (var i = 0; i < phones.length; i++) {
|
|
var currentPhone = phones[i];
|
|
var index = currentPhone.dataset.index;
|
|
var numberField = dom.getElementById('number_' + index);
|
|
var value = numberField.value;
|
|
if (!value) {
|
|
continue;
|
|
}
|
|
|
|
var type = [dom.getElementById('tel_type_' + index).dataset.value || ''];
|
|
var carrierSelector = 'carrier_' + index;
|
|
var carrier = dom.getElementById(carrierSelector).value || '';
|
|
contact.tel = contact.tel || [];
|
|
/*jshint -W075 */
|
|
contact.tel.push({ value, type, carrier });
|
|
}
|
|
};
|
|
|
|
var getEmails = function getEmails(contact) {
|
|
var selector = '#view-contact-form form div.email-template:not(.facebook)';
|
|
var emails = dom.querySelectorAll(selector);
|
|
for (var i = 0; i < emails.length; i++) {
|
|
var currentEmail = emails[i];
|
|
var index = currentEmail.dataset.index;
|
|
var emailField = dom.getElementById('email_' + index);
|
|
var value = emailField.value;
|
|
value = value && value.trim();
|
|
if (!value) {
|
|
continue;
|
|
}
|
|
|
|
var type = [
|
|
dom.getElementById('email_type_' + index).dataset.value || ''];
|
|
contact.email = contact.email || [];
|
|
contact.email.push({ value, type });
|
|
}
|
|
};
|
|
|
|
var getDates = function getDate(contact) {
|
|
var selector = '#view-contact-form form div.date-template';
|
|
var dates = dom.querySelectorAll(selector);
|
|
var bdayVal = null, anniversaryVal = null;
|
|
|
|
for (var i = 0; i < dates.length; i++) {
|
|
var currentDate = dates[i];
|
|
|
|
if (dates[i].classList.contains(FB_CLASS)) {
|
|
continue;
|
|
}
|
|
|
|
var arrayIndex = currentDate.dataset.index;
|
|
var dateField = dom.getElementById('date_' + arrayIndex);
|
|
var dateValue = dateField.valueAsDate;
|
|
|
|
selector = 'date_type_' + arrayIndex;
|
|
var type = dom.getElementById(selector).dataset.value || '';
|
|
if (!dateValue || !type) {
|
|
continue;
|
|
}
|
|
|
|
// Date value is referred to current TZ but it is not needed to normalize
|
|
// as that will be done only when the date is presented to the user
|
|
// by calculating the corresponding offset
|
|
switch (type) {
|
|
case 'birthday':
|
|
bdayVal = dateValue;
|
|
break;
|
|
case 'anniversary':
|
|
anniversaryVal = dateValue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
contact.bday = bdayVal;
|
|
contact.anniversary = anniversaryVal;
|
|
};
|
|
|
|
var getAddresses = function getAddresses(contact) {
|
|
var selector =
|
|
'#view-contact-form form div.address-template:not(.facebook)';
|
|
var addresses = dom.querySelectorAll(selector);
|
|
for (var i = 0; i < addresses.length; i++) {
|
|
var currentAddress = addresses[i];
|
|
var arrayIndex = currentAddress.dataset.index;
|
|
var addressField = dom.getElementById('streetAddress_' + arrayIndex);
|
|
var addressValue = addressField.value || '';
|
|
addressValue = addressValue.trim();
|
|
selector = 'address_type_' + arrayIndex;
|
|
var typeField = dom.getElementById(selector).dataset.value || '';
|
|
typeField = typeField.trim();
|
|
selector = 'locality_' + arrayIndex;
|
|
var locality = dom.getElementById(selector).value || '';
|
|
locality = locality.trim();
|
|
selector = 'postalCode_' + arrayIndex;
|
|
var postalCode = dom.getElementById(selector).value || '';
|
|
postalCode = postalCode.trim();
|
|
selector = 'countryName_' + arrayIndex;
|
|
var countryName = dom.getElementById(selector).value || '';
|
|
countryName = countryName.trim();
|
|
|
|
// Sanity check for pameters, check all params but the typeField
|
|
if (addressValue === '' && locality === '' &&
|
|
postalCode === '' && countryName === '') {
|
|
continue;
|
|
}
|
|
|
|
contact.adr = contact.adr || [];
|
|
contact.adr.push({
|
|
streetAddress: addressValue,
|
|
postalCode: postalCode,
|
|
locality: locality,
|
|
countryName: countryName,
|
|
type: [typeField]
|
|
});
|
|
}
|
|
};
|
|
|
|
var getNotes = function getNotes(contact) {
|
|
var selector = '#view-contact-form form div.note-template';
|
|
var notes = dom.querySelectorAll(selector);
|
|
for (var i = 0; i < notes.length; i++) {
|
|
var currentNote = notes[i];
|
|
var arrayIndex = currentNote.dataset.index;
|
|
var noteField = dom.getElementById('note_' + arrayIndex);
|
|
var noteValue = noteField.value;
|
|
noteValue = noteValue && noteValue.trim();
|
|
if (!noteValue) {
|
|
continue;
|
|
}
|
|
|
|
contact.note = contact.note || [];
|
|
contact.note.push(noteValue);
|
|
}
|
|
};
|
|
|
|
var resetForm = function resetForm() {
|
|
currentPhoto = null;
|
|
deletedTelNumber = false;
|
|
|
|
thumbAction.querySelector('p').classList.remove('hide');
|
|
var removeIcon = thumbAction.querySelector('button.' + IMG_DELETE_CLASS);
|
|
if (removeIcon) {
|
|
thumbAction.removeChild(removeIcon);
|
|
}
|
|
saveButton.removeAttribute('disabled');
|
|
|
|
addNewDateButton.disabled = false;
|
|
|
|
resetRemoved();
|
|
currentContactId.value = '';
|
|
currentContact = {};
|
|
givenName.value = '';
|
|
familyName.value = '';
|
|
company.value = '';
|
|
thumb.style.backgroundImage = '';
|
|
var phones = dom.querySelector('#contacts-form-phones');
|
|
var emails = dom.querySelector('#contacts-form-emails');
|
|
var addresses = dom.querySelector('#contacts-form-addresses');
|
|
var dates = dom.querySelector('#contacts-form-dates');
|
|
var notes = dom.querySelector('#contacts-form-notes');
|
|
|
|
[phones, emails, addresses, dates, notes].forEach(
|
|
utils.dom.removeChildNodes);
|
|
|
|
counters = {
|
|
'tel': 0,
|
|
'email': 0,
|
|
'adr': 0,
|
|
'date': 0,
|
|
'note': 0
|
|
};
|
|
textFieldsCache.clear();
|
|
formView.scrollTop = 0;
|
|
};
|
|
|
|
var resetRemoved = function cf_resetRemoved() {
|
|
var removedFields = dom.querySelectorAll('.facebook');
|
|
for (var i = 0; i < removedFields.length; i++) {
|
|
removedFields[i].classList.remove(FB_CLASS);
|
|
}
|
|
thumbAction.classList.remove('with-photo');
|
|
var removeButton = thumbAction.querySelector('button');
|
|
if (removeButton) {
|
|
thumbAction.removeChild(removeButton);
|
|
}
|
|
};
|
|
|
|
var checkDisableButton = function checkDisableButton() {
|
|
var saveButton = dom.getElementById('save-button');
|
|
if (emptyForm()) {
|
|
saveButton.setAttribute('disabled', 'disabled');
|
|
} else {
|
|
saveButton.removeAttribute('disabled');
|
|
}
|
|
};
|
|
|
|
|
|
var emptyForm = function emptyForm() {
|
|
var textFields = textFieldsCache.get();
|
|
for (var i = textFields.length - 1; i >= 0; i--) {
|
|
if ((textFields[i].value && textFields[i].value.trim()) ||
|
|
(textFields[i].valueAsDate)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
var removeFieldIcon = function removeFieldIcon(selector, type) {
|
|
var delButton = document.createElement('button');
|
|
|
|
delButton.className = IMG_DELETE_CLASS; // + ' fillflow-row-action';
|
|
delButton.setAttribute('data-l10n-id', 'removeField');
|
|
delButton.setAttribute('data-type', type);
|
|
|
|
delButton.onclick = function removeElement(event) {
|
|
// Workaround until 809452 is fixed.
|
|
// What we are avoiding with this condition is removing / restoring
|
|
// a field when the event is simulated by a ENTER Keyboard click
|
|
if (event.clientX === 0 && event.clientY === 0) {
|
|
return false;
|
|
}
|
|
event.preventDefault();
|
|
var container = configs[type].container;
|
|
var elem = document.getElementById(selector);
|
|
|
|
if (type !== 'photo') {
|
|
elem.parentNode.removeChild(elem);
|
|
if (container.querySelectorAll('[data-field]').length === 0) {
|
|
container.classList.add('empty');
|
|
}
|
|
}
|
|
else {
|
|
// TODO: Implement the new delete image flow
|
|
console.warn('Delete image');
|
|
}
|
|
|
|
if (type === 'tel') {
|
|
deletedTelNumber = true;
|
|
}
|
|
|
|
// Update the aria label for acessibility
|
|
var delButton = event.target;
|
|
delButton.setAttribute('data-l10n-id', 'removeField');
|
|
|
|
counters[type]--;
|
|
// In this version only two dates are allowed
|
|
if (type === 'date') {
|
|
checkAddDateButton();
|
|
}
|
|
|
|
textFieldsCache.clear();
|
|
checkDisableButton();
|
|
};
|
|
|
|
return delButton;
|
|
};
|
|
|
|
var addRemoveIconToPhoto = function cf_addRemIconPhoto() {
|
|
// Ensure the removed and FB class names are conveniently reseted
|
|
thumbAction.classList.remove(FB_CLASS);
|
|
|
|
var out = thumbAction.querySelector('button.' + IMG_DELETE_CLASS);
|
|
if (!out) {
|
|
out = removeFieldIcon(thumbAction.id, 'photo');
|
|
thumbAction.appendChild(out);
|
|
}
|
|
else {
|
|
// Ensure it is visible
|
|
out.classList.remove('hide');
|
|
}
|
|
thumbAction.classList.add('with-photo');
|
|
|
|
return out;
|
|
};
|
|
|
|
function canRemovePhoto() {
|
|
var out = getCurrentPhoto() !== null;
|
|
|
|
if (fb.isFbContact(currentContact)) {
|
|
out = Array.isArray(deviceContact.photo) && deviceContact.photo[0];
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
function photoAction() {
|
|
if (canRemovePhoto()) {
|
|
removeOrUpdatePhoto();
|
|
}
|
|
else {
|
|
pickImage();
|
|
}
|
|
}
|
|
|
|
function removeOrUpdatePhoto() {
|
|
LazyLoader.load('/contacts/js/action_menu.js', function() {
|
|
var prompt = new ActionMenu('photo-options');
|
|
prompt.addToList({id: 'remove-photo'}, removePhoto);
|
|
|
|
prompt.addToList({id: 'change-photo'}, pickImage);
|
|
|
|
prompt.show();
|
|
});
|
|
}
|
|
|
|
function removePhoto() {
|
|
currentPhoto = null;
|
|
// If photo is removed, the FB photo of a contact is always restoredf
|
|
if (fb.isFbContact(currentContact)) {
|
|
// The local contact now does not have a photo
|
|
deviceContact.photo = null;
|
|
var fbPhoto = ContactPhotoHelper.getFullResolution(fbContact);
|
|
utils.dom.updatePhoto(fbPhoto, thumb);
|
|
}
|
|
else {
|
|
thumbAction.classList.remove('with-photo');
|
|
utils.dom.updatePhoto(null, thumb);
|
|
}
|
|
|
|
if (!emptyForm()) {
|
|
saveButton.removeAttribute('disabled');
|
|
}
|
|
}
|
|
|
|
var pickImage = function pickImage() {
|
|
var activity = new MozActivity({
|
|
name: 'pick',
|
|
data: {
|
|
type: 'image/jpeg',
|
|
maxFileSizeBytes: MAX_PHOTO_SIZE
|
|
}
|
|
});
|
|
|
|
activity.onsuccess = function success() {
|
|
addRemoveIconToPhoto();
|
|
if (!emptyForm()) {
|
|
saveButton.removeAttribute('disabled');
|
|
}
|
|
// XXX
|
|
// this.result.blob is valid now, but it won't stay valid
|
|
// (see https://bugzilla.mozilla.org/show_bug.cgi?id=806503)
|
|
// And it might not be the size we want, anyway, so we make
|
|
// our own copy that is at the right size.
|
|
resizeBlob(this.result.blob, PHOTO_WIDTH, PHOTO_HEIGHT,
|
|
function(resized) {
|
|
utils.dom.updatePhoto(resized, thumb);
|
|
currentPhoto = resized;
|
|
if (fb.isFbContact(currentContact)) {
|
|
// We temporarily mark that there is a local photo chosen
|
|
deviceContact.photo = [currentPhoto];
|
|
}
|
|
});
|
|
};
|
|
|
|
activity.onerror = function() {
|
|
window.console.error('Error in the activity', activity.error);
|
|
};
|
|
|
|
return false;
|
|
};
|
|
|
|
function resizeBlob(blob, target_width, target_height, 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() {
|
|
var image_width = img.width;
|
|
var image_height = img.height;
|
|
var scalex = image_width / target_width;
|
|
var scaley = image_height / target_height;
|
|
var scale = Math.min(scalex, scaley);
|
|
|
|
var w = target_width * scale;
|
|
var h = target_height * scale;
|
|
var x = (image_width - w) / 2;
|
|
var y = (image_height - h) / 2;
|
|
|
|
var canvas = document.createElement('canvas');
|
|
canvas.width = target_width;
|
|
canvas.height = target_height;
|
|
var context = canvas.getContext('2d', { willReadFrequently: true });
|
|
|
|
context.drawImage(img, x, y, w, h, 0, 0, target_width, target_height);
|
|
cleanupImg();
|
|
canvas.toBlob(function(resized) {
|
|
context = null;
|
|
canvas.width = canvas.height = 0;
|
|
canvas = null;
|
|
callback(resized);
|
|
} , 'image/jpeg');
|
|
};
|
|
}
|
|
|
|
return {
|
|
'init': init,
|
|
'render': render,
|
|
'insertField': insertField,
|
|
'saveContact': saveContact,
|
|
'onNewFieldClicked': onNewFieldClicked
|
|
};
|
|
})();
|