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
23
gaia/apps/communications/contacts/views/matching/js/boot.js
Executable file
23
gaia/apps/communications/contacts/views/matching/js/boot.js
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
/* global LazyLoader, MatchingUI */
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
LazyLoader.load(['/shared/js/l10n.js']).then(function() {
|
||||
// TODO Add if needed
|
||||
});
|
||||
});
|
||||
|
||||
window.onload = function() {
|
||||
var dependencies = [
|
||||
'/shared/js/contact_photo_helper.js',
|
||||
'/shared/js/sanitizer.js',
|
||||
'/shared/js/contacts/import/utilities/misc.js',
|
||||
'/contacts/views/matching/js/matching_ui.js'
|
||||
];
|
||||
|
||||
window.screen.mozLockOrientation('portrait-primary');
|
||||
|
||||
LazyLoader.load(dependencies).then(function() {
|
||||
MatchingUI.init();
|
||||
});
|
||||
};
|
||||
213
gaia/apps/communications/contacts/views/matching/js/matching_controller.js
Executable file
213
gaia/apps/communications/contacts/views/matching/js/matching_controller.js
Executable file
|
|
@ -0,0 +1,213 @@
|
|||
'use strict';
|
||||
/* global Curtain */
|
||||
/* global LazyLoader */
|
||||
/* global MergeHelper */
|
||||
/* global Matcher */
|
||||
|
||||
/**
|
||||
* This object perform each operation related to the actions that an user
|
||||
* can do in the matching contacts view
|
||||
*/
|
||||
(function(exports) {
|
||||
var CONTACTS_APP_ORIGIN = location.origin;
|
||||
|
||||
var contact = null,
|
||||
matchings = {};
|
||||
|
||||
/*
|
||||
* This library implements two modes of work ('matching' by default):
|
||||
*
|
||||
* 1) 'matching': find duplicates adding a contact trigger that checks for
|
||||
* duplicates. If there are duplicates, it shows them in a
|
||||
* list where users are able to check what contacts will be
|
||||
* finally merged.
|
||||
*
|
||||
* 2) 'listing': this library shows a list of contacts received from the
|
||||
* caller by means of a postMessage mechanism. Users can
|
||||
* check what contacts will be merged but this action will be
|
||||
* performed by the caller. Basically the library sends the
|
||||
* list of identifiers to the caller by the same mechanism.
|
||||
*/
|
||||
var type = 'matching';
|
||||
|
||||
var parentWindow = null;
|
||||
|
||||
function start(cid) {
|
||||
if (!cid) {
|
||||
type = 'listing';
|
||||
window.addEventListener('message', duplicateContactsHandler);
|
||||
parentWindow.postMessage({
|
||||
type: 'duplicate_contacts_loaded'
|
||||
}, CONTACTS_APP_ORIGIN);
|
||||
return;
|
||||
}
|
||||
|
||||
Curtain.show('wait', 'duplicateContacts');
|
||||
|
||||
// Finding duplicate contacts
|
||||
var callbacks = {
|
||||
onmatch: showUI,
|
||||
onmismatch: function() {
|
||||
Curtain.show('alert', 'noDuplicateContacts');
|
||||
Curtain.onok = abort;
|
||||
}
|
||||
};
|
||||
|
||||
Curtain.oncancel = function oncancel() {
|
||||
// Removing callbacks
|
||||
callbacks.onmatch = callbacks.onmismatch = null;
|
||||
abort();
|
||||
};
|
||||
|
||||
var matcherDependencies = ['/shared/js/text_normalizer.js',
|
||||
'/shared/js/simple_phone_matcher.js',
|
||||
'/shared/js/contacts/contacts_matcher.js'];
|
||||
LazyLoader.load(matcherDependencies, function loaded() {
|
||||
// Give some time to see the searching for duplicates message
|
||||
setTimeout(function() {
|
||||
parentWindow.ContactsService.get(cid, function success(mContact) {
|
||||
// Master contact
|
||||
contact = mContact;
|
||||
Matcher.match(contact, 'active', callbacks);
|
||||
}, abort);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
function duplicateContactsHandler(e) {
|
||||
if (e.origin !== CONTACTS_APP_ORIGIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = e.data;
|
||||
|
||||
if (data && data.type === 'show_duplicate_contacts') {
|
||||
window.removeEventListener('message', duplicateContactsHandler);
|
||||
|
||||
// Incoming contact
|
||||
contact = {
|
||||
name: [data.data.name]
|
||||
};
|
||||
var duplicateContacts = data.data.duplicateContacts;
|
||||
var total = Object.keys(duplicateContacts).length;
|
||||
var matchingReady = function() {
|
||||
if (--total === 0) {
|
||||
window.addEventListener('message', contactsMergedHandler);
|
||||
showUI(duplicateContacts);
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(duplicateContacts).forEach(function(cid) {
|
||||
parentWindow.ContactsService.get(cid, function success(contact) {
|
||||
duplicateContacts[cid] = {
|
||||
matchingContact: contact,
|
||||
matchings: duplicateContacts[cid].matchings
|
||||
};
|
||||
matchingReady();
|
||||
}, matchingReady);
|
||||
});
|
||||
|
||||
} else if(data && data.type === 'sync'){
|
||||
parentWindow = e.source;
|
||||
navigator.mozL10n.once(function localized(evt) {
|
||||
// The controller is started when the literals are available
|
||||
start(window.location.search.substring('contactId'.length + 2));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function contactsMergedHandler(e) {
|
||||
if (e.origin !== CONTACTS_APP_ORIGIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.data && e.data.type === 'duplicate_contacts_merged') {
|
||||
window.removeEventListener('message', contactsMergedHandler);
|
||||
|
||||
Curtain.hide(function() {
|
||||
parentWindow.postMessage({
|
||||
type: 'window_close',
|
||||
data: ''
|
||||
}, CONTACTS_APP_ORIGIN);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showUI(results) {
|
||||
matchings = results;
|
||||
window.dispatchEvent(new CustomEvent('initUI', {
|
||||
'detail': {
|
||||
'type': type,
|
||||
'contact': contact,
|
||||
'results': results
|
||||
}
|
||||
}));
|
||||
|
||||
window.addEventListener('UIReady', function fn() {
|
||||
window.removeEventListener('UIReady', fn);
|
||||
// We start the open-animation when the UI is ready
|
||||
if (type === 'matching') {
|
||||
Curtain.hide(sendReadyEvent, CONTACTS_APP_ORIGIN);
|
||||
} else {
|
||||
sendReadyEvent();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendReadyEvent() {
|
||||
parentWindow.postMessage({
|
||||
type: 'ready',
|
||||
data: ''
|
||||
}, CONTACTS_APP_ORIGIN);
|
||||
}
|
||||
|
||||
function abort() {
|
||||
var notifyParent = function cmc_notifyParent() {
|
||||
parentWindow.postMessage({
|
||||
type: 'abort',
|
||||
data: ''
|
||||
}, CONTACTS_APP_ORIGIN);
|
||||
};
|
||||
Curtain.hide(notifyParent);
|
||||
}
|
||||
|
||||
function merge(checkedContacts) {
|
||||
Curtain.show('wait', 'mergingDuplicateContacts');
|
||||
Curtain.hideMenu(); // Hide cancel button
|
||||
|
||||
if (type === 'listing') {
|
||||
// Delegating the merge action to caller and this is the end
|
||||
parentWindow.postMessage({
|
||||
type: 'merge_duplicate_contacts',
|
||||
data: checkedContacts
|
||||
}, CONTACTS_APP_ORIGIN);
|
||||
return;
|
||||
}
|
||||
|
||||
var MERGE_DEPS = ['/contacts/js/utilities/merge_helper.js'];
|
||||
LazyLoader.load(MERGE_DEPS, function loaded() {
|
||||
var cb = function cb() {
|
||||
Curtain.hide(function() {
|
||||
parentWindow.postMessage({
|
||||
type: 'window_close',
|
||||
data: ''
|
||||
}, CONTACTS_APP_ORIGIN);
|
||||
});
|
||||
};
|
||||
|
||||
var list = [];
|
||||
Object.keys(checkedContacts).forEach(function(id) {
|
||||
list.push(matchings[id]);
|
||||
});
|
||||
|
||||
MergeHelper.merge(contact, list).then(cb, cb);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('message', duplicateContactsHandler);
|
||||
|
||||
window.addEventListener('merge', evt => {
|
||||
merge(evt.detail.checkedContacts);
|
||||
});
|
||||
|
||||
})(window);
|
||||
304
gaia/apps/communications/contacts/views/matching/js/matching_ui.js
Executable file
304
gaia/apps/communications/contacts/views/matching/js/matching_ui.js
Executable file
|
|
@ -0,0 +1,304 @@
|
|||
'use strict';
|
||||
/* global ContactPhotoHelper */
|
||||
/* global ImageLoader */
|
||||
/* global LazyLoader */
|
||||
/* global Sanitizer */
|
||||
/* jshint nonew: false */
|
||||
|
||||
/**
|
||||
* This object handles every interaction between the user and the UI of the
|
||||
* matching contacts view
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var CONTACTS_APP_ORIGIN = location.origin;
|
||||
|
||||
var listDependencies = [
|
||||
'/shared/js/contacts/utilities/image_loader.js'
|
||||
];
|
||||
|
||||
var MatchingUI = {
|
||||
_: null,
|
||||
|
||||
// Counter for checked list items
|
||||
checked: 0,
|
||||
|
||||
// Hash contains identifiers of checked contacts
|
||||
checkedContacts: {},
|
||||
|
||||
// Field order when showing matching details main reason
|
||||
fieldOrder: ['tel', 'email', 'name'],
|
||||
|
||||
init: function() {
|
||||
this._ = navigator.mozL10n.get;
|
||||
this.mergeButton = document.getElementById('merge-action');
|
||||
if (!this.mergeButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.duplicateMessage = document.querySelector('#duplicate-msg > p');
|
||||
this.contactsList =
|
||||
document.querySelector('#contacts-list-container > ol');
|
||||
|
||||
this.contactsList.addEventListener('click', this.onClick.bind(this)
|
||||
, true);
|
||||
this.mergeButton.addEventListener('click', this.onMerge.bind(this));
|
||||
|
||||
window.addEventListener('initUI', evt => {
|
||||
this.load(evt.detail.type, evt.detail.contact, evt.detail.results);
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Loads the UI that implements the merge of duplicate contacts
|
||||
*
|
||||
* @param{Object} Master contact
|
||||
*
|
||||
* @param{Object} Hash of matching contacts from Matcher module
|
||||
*
|
||||
*/
|
||||
load: function(type, contact, results) {
|
||||
this.matchingResults = results;
|
||||
|
||||
document.body.dataset.mode = type;
|
||||
var contactName = this.getDisplayName(contact);
|
||||
var params = { name: Sanitizer.escapeHTML `${contactName}` };
|
||||
|
||||
if (type === 'matching') {
|
||||
// "Suggested duplicate contacts for xxx"
|
||||
navigator.mozL10n.setAttributes(this.duplicateMessage,
|
||||
'suggestedDuplicatesMessage',
|
||||
params);
|
||||
} else {
|
||||
navigator.mozL10n.setAttributes(document.querySelector('title'),
|
||||
'duplicatesFoundTitle');
|
||||
// "xxx duplicates information in the following contacts"
|
||||
navigator.mozL10n.setAttributes(this.duplicateMessage,
|
||||
'foundDuplicatesMessage',
|
||||
params);
|
||||
}
|
||||
|
||||
// Rendering the duplicate contacts list
|
||||
this.renderList(results);
|
||||
},
|
||||
|
||||
templateDuplicateContact: function(contact) {
|
||||
return Sanitizer.createSafeHTML `<li data-uuid="${contact.id}"
|
||||
class="block-item">
|
||||
<label class="pack-checkbox">
|
||||
<input type="checkbox" checked>
|
||||
<span></span>
|
||||
</label>
|
||||
<aside class="pack-end">
|
||||
<img data-src="${contact.thumb}">
|
||||
</aside>
|
||||
<p>
|
||||
<bdi class="ellipsis-dir-fix">${contact.displayName}</bdi>
|
||||
</p>
|
||||
<p class="match-main-reason">
|
||||
<bdi class="ellipsis-dir-fix">${contact.mainReason}</bdi>
|
||||
</p>
|
||||
</li>`;
|
||||
},
|
||||
|
||||
renderList: function(contacts) {
|
||||
LazyLoader.load(listDependencies, () => {
|
||||
// For each contact in the list
|
||||
var contactsKeys = Object.keys(contacts);
|
||||
contactsKeys.forEach(id => {
|
||||
// New contact appended
|
||||
this.checkedContacts[id] = id;
|
||||
var contact = this.cookContact(contacts[id]);
|
||||
|
||||
// Use a template node so we can append it and also access the node
|
||||
var item = document.createElement('template');
|
||||
item.innerHTML =
|
||||
Sanitizer.unwrapSafeHTML(this.templateDuplicateContact(contact));
|
||||
|
||||
this.contactsList.appendChild(item.content);
|
||||
|
||||
if (contact.email1 === '') {
|
||||
var emailField = item.content.querySelector('p:last-child');
|
||||
emailField && emailField.parentNode.removeChild(emailField);
|
||||
}
|
||||
});
|
||||
|
||||
this.checked = contactsKeys.length;
|
||||
this.checkMergeButton();
|
||||
|
||||
new ImageLoader('#main', 'li');
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(new CustomEvent('UIReady'));
|
||||
// Remove the hidden ui
|
||||
document.body.classList.remove('ui-not-ready');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* It creates a new copy becase it is forbidden to add new attributes to
|
||||
* original contacts provided by the API
|
||||
*
|
||||
* @param(Object) Contact object provided by Gecko
|
||||
*/
|
||||
cookContact: function(matching) {
|
||||
var contact = matching.matchingContact;
|
||||
var reasons = matching.matchings || {};
|
||||
|
||||
var out = {};
|
||||
|
||||
this.populate(contact, out, Object.getOwnPropertyNames(contact));
|
||||
this.populate(contact, out,
|
||||
Object.getOwnPropertyNames(Object.getPrototypeOf(contact)));
|
||||
|
||||
out.displayName = this.getDisplayName(contact);
|
||||
out.mainReason = this.selectMainReason(reasons);
|
||||
var photo = ContactPhotoHelper.getThumbnail(out);
|
||||
if (photo) {
|
||||
out.thumb = window.URL.createObjectURL(photo);
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
getCompleteName: function(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
|
||||
getDisplayName: function(contact) {
|
||||
if (this.hasName(contact)) {
|
||||
return this.getCompleteName(contact);
|
||||
}
|
||||
|
||||
var name = [];
|
||||
if (Array.isArray(contact.name) && contact.name[0] &&
|
||||
contact.name[0].trim()) {
|
||||
name.push(contact.name[0]);
|
||||
} else if (contact.org && contact.org[0] && contact.org[0].trim()) {
|
||||
name.push(contact.org[0]);
|
||||
} else if (contact.tel && contact.tel[0]) {
|
||||
name.push(contact.tel[0].value);
|
||||
} else if (contact.email && contact.email[0]) {
|
||||
name.push(contact.email[0].value);
|
||||
} else {
|
||||
name.push(this._('noName'));
|
||||
}
|
||||
|
||||
return name[0];
|
||||
},
|
||||
|
||||
hasName: function(contact) {
|
||||
return (Array.isArray(contact.givenName) && contact.givenName[0] &&
|
||||
contact.givenName[0].trim()) ||
|
||||
(Array.isArray(contact.familyName) && contact.familyName[0] &&
|
||||
contact.familyName[0].trim());
|
||||
},
|
||||
|
||||
selectMainReason: function(matchings) {
|
||||
var out = '';
|
||||
|
||||
for (var j = 0; j < this.fieldOrder.length; j++) {
|
||||
var aField = this.fieldOrder[j];
|
||||
var theMatchings = matchings[aField];
|
||||
if (Array.isArray(theMatchings) && theMatchings[0]) {
|
||||
out = theMatchings[0].matchedValue;
|
||||
if (out) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
populate: function(source, target, propertyNames) {
|
||||
propertyNames.forEach(function(property) {
|
||||
var propertyValue = source[property];
|
||||
if (propertyValue) {
|
||||
target[property] = propertyValue;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onClose: function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
parent.postMessage({
|
||||
type: 'window_close',
|
||||
data: ''
|
||||
}, CONTACTS_APP_ORIGIN);
|
||||
},
|
||||
|
||||
onClick: function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (e.target.tagName === 'INPUT') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var li = e.target.closest('li');
|
||||
var uuid = li.dataset.uuid;
|
||||
var el = li.querySelector('input');
|
||||
|
||||
if (el.checked) {
|
||||
--this.checked;
|
||||
delete this.checkedContacts[uuid];
|
||||
} else {
|
||||
++this.checked;
|
||||
this.checkedContacts[uuid] = uuid;
|
||||
}
|
||||
|
||||
// Change manually the status as we prevented this
|
||||
el.checked = !el.checked;
|
||||
|
||||
this.checkMergeButton();
|
||||
},
|
||||
|
||||
isMatch: function(matchings, aField, fieldValue) {
|
||||
var noMatch = true;
|
||||
if (matchings[aField]) {
|
||||
// Using every to short circuit the checking when a match is found.
|
||||
noMatch = matchings[aField].every(function(obj) {
|
||||
var val = fieldValue.value || fieldValue;
|
||||
if (obj.matchedValue === val) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return !noMatch;
|
||||
},
|
||||
|
||||
checkMergeButton: function() {
|
||||
navigator.mozL10n.setAttributes(this.mergeButton,
|
||||
'mergeActionButtonLabel',
|
||||
{ n: this.checked });
|
||||
this.mergeButton.disabled = (this.checked === 0);
|
||||
},
|
||||
|
||||
onMerge: function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
window.dispatchEvent(new CustomEvent('merge', {
|
||||
'detail': {
|
||||
'checkedContacts': this.checkedContacts
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
exports.MatchingUI = MatchingUI;
|
||||
})(window);
|
||||
Loading…
Reference in a new issue