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:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,7 @@
{
"thumbnail": {
"format": "image/jpeg",
"size": 65,
"quality": 1
}
}

View file

@ -0,0 +1,9 @@
{
"defaultContactsOrder": "givenName",
"facebookEnabled": true,
"operationsTimeout": 25000,
"logLevel": "DEBUG",
"facebookSyncPeriod": 24,
"testToken": "",
"defaultImage": true
}

View file

@ -0,0 +1,13 @@
<element name="confirmation-message" extends="form">
<template>
<section>
<h1>Confirmation title</h1>
<p>Confirmation message</p>
</section>
<menu>
<button>Action 1</button>
<button>Action 2</button>
</menu>
</template>
</element>

View file

@ -0,0 +1,98 @@
<element name="view-contact-details" extends="section">
<template>
<gaia-header id='details-view-header' action="back">
<h1 id='contact-name-title'><bdi class="ellipsis-dir-fix"></bdi></h1>
<div class='favorite-indicator'></div>
<button id="edit-contact-button" data-icon="edit-contact" data-l10n-id="editButton"></button>
</gaia-header>
<article id="contact-detail-wrapper" class="view-body">
<div id="cover-img"></div>
<section id="contact-detail" class="view-body-inner">
<li data-phone id='phone-details-template-#i#' data-template role="region" aria-label="Phone" data-l10n-id="phoneDetail">
<h2>
<span data-l10n-id="#type_l10n_id#">#type#</span>
<span class="carrier-wrapper" hidden>
<span data-l10n-id="separator"></span>
<span class="carrier">#carrier#</span>
</span>
</h2>
<div class="item">
<div class="item-body-exp">
<div class="button activity call-action icon icon-call" id="call-or-pick-#i#">
<b aria-label="Call" data-l10n-id="makeCall"><bdi class="ellipsis-dir-fix">#value#</bdi></b>
<span class="js-sim-indication"></span>
</div>
</div>
<div class="item-media pull-right">
<button id="send-sms-button-#i#" class="activity send-sms icon icon-message" data-l10n-id="send-sms-button" aria-label="SMS"></button>
</div>
</div>
</li>
<li data-social class="social-actions" id="social-template-#i#" data-template role="region">
<h2 data-l10n-id="social-network" id="social-label">Social Network</h2>
<button id="share_button" data-l10n-id="share-contact" class="activity icon icon-share">
Share
</button>
</li>
<li class="duplicate-actions" id="duplicate-contacts-template" data-template role="region">
<h2 data-l10n-id="organise-contacts">Organize Contacts</h2>
<button id="find-merge-button" data-l10n-id="find-merge-contacts" class="activity icon icon-find-and-merge">
Find duplicate contacts
</button>
</li>
<li data-address id='address-details-template-#i#' data-template role="region" aria-label="Address" data-l10n-id="addressDetail">
<h2 data-l10n-id="#type_l10n_id#">#type#</h2>
<div class="item">
<div class="item-body-exp">
<a href="#" class="action action-block">
<b>
<span class="force-align" dir="auto">#streetAddress#</span>
#postalCode#
#locality#
#countryName#
</b>
</a>
</div>
</div>
</li>
<li data-mail id='email-details-template-#i#' data-template role="region" aria-label="Email" data-l10n-id="emailDetail">
<h2 data-l10n-id="#type_l10n_id#"></h2>
<div class="item">
<div class="item-body-exp">
<button id="email-or-pick-#i#" class="activity icon icon-email">
<b aria-label="Email" data-l10n-id="composeEmail" dir="ltr">#value#</b>
</button>
</div>
<div class="item-media pull-right">
<button id="send-sms-to-email-button-#i#" class="activity send-sms icon icon-message" data-l10n-id="send-sms-button" aria-label="SMS"></button>
</div>
</div>
</li>
<li id="dates-template-#i#" data-template role="region" aria-label="Date" data-l10n-id="dateDetail">
<h2 data-l10n-id="#type_l10n_id#">#type#</h2>
<p><strong>#date#</strong></p>
</li>
<p data-comment id='note-details-template-#i#' data-template class="comment-notes">#note#</p>
<div id='contact-detail-inner'>
<h1 id='org-title' class="force-align" dir="auto"></h1>
<ul id='details-list' role="presentation">
</ul>
<ul role="presentation">
<li role="presentation">
<button id='toggle-favorite' class="fillflow-row icon icon-fav">
Add as favorite
</button>
</li>
</ul>
</div>
</section>
</article>
</template>
</element>

View file

@ -0,0 +1,18 @@
<!-- bug 1060080: taken from details.html, should be unified -->
<element name="email-details" extends="section">
<template>
<li data-mail id='email-details-template-#i#' data-template role="region" aria-label="Email" data-l10n-id="emailDetail">
<h2 data-l10n-id="#type_l10n_id#">#type#</h2>
<div class="item">
<div class="item-body-exp">
<button id="email-or-pick-#i#" class="activity icon icon-email">
<b aria-label="Email" data-l10n-id="composeEmail" dir="ltr">#value#</b>
</button>
</div>
<div class="item-media pull-right">
<button id="send-sms-to-email-button-#i#" class="activity send-sms icon icon-message" data-l10n-id="send-sms-button" aria-label="SMS"></button>
</div>
</div>
</li>
</template>
</element>

View file

@ -0,0 +1,212 @@
<element name="view-contact-form" extends="section">
<template>
<gaia-header id="contact-form-header" action="close">
<h1 id='contact-form-title' data-l10n-id="addContact"></h1>
<button id="save-button" data-l10n-id="done">Done</button>
</gaia-header>
<progress id="throbber" value="0" max="100" class="pack-activity hide"></progress>
<div id='add-phone-#i#' role="listitem" data-template class='phone-template field-template' data-index="#i#">
<fieldset class="fillflow-row">
<div class="two_fields first-row">
<legend class="action" role="combobox" aria-haspopup="true" aria-labelledby="tel_type_#i#">
<span aria-hidden="true" data-taglist="phone-type" name="tel" data-value="#type_value#"
id="tel_type_#i#" data-field="type" data-l10n-id="#type_l10n_id#">
#type#
</span>
</legend>
<p class="setbox-item">
<input placeholder="Carrier" data-field="carrier" data-l10n-id="carrier" name="tel[#i#][carrier]" class="textfield" type="text" x-inputmode="latin-prose" value="#carrier#" id="carrier_#i#" dir="auto">
<button type="reset">Clear</button>
</p>
</div>
<section class="second-row">
<p class="setbox-item">
<bdi>
<input placeholder="Phone" data-field="value" data-l10n-id="tel" name="tel[#i#][value]" class="textfield" type="tel" value="#value#" id="number_#i#">
<button type="reset">Clear</button>
</bdi>
</p>
</section>
</fieldset>
</div>
<div id='add-email-#i#' role="listitem" data-template class='email-template field-template' data-index="#i#">
<fieldset class="fillflow-row">
<legend class="action first-row" role="combobox" aria-haspopup="true" aria-labelledby="email_type_#i#">
<span aria-hidden="true" data-taglist="email-type" name="email[#i#][type]" data-value="#type_value#" id="email_type_#i#" data-field="type" data-l10n-id="#type_l10n_id#">#type#</span>
</legend>
<section>
<p class="setbox-item second-row">
<input placeholder="Email" data-field="value" data-l10n-id="email" name="email[#i#][value]" class="textfield" type="email" value="#value#" id="email_#i#">
<button type="reset">Clear</button>
</p>
</section>
</fieldset>
</div>
<div id='add-address-#i#' role="listitem" data-template class='address-template field-template' data-index="#i#">
<fieldset class="fillflow-row">
<section>
<p class="setbox-item first-row">
<input placeholder="Street" data-field="streetAddress" data-l10n-id="streetAddress" name="adr[#i#][streetAddress]" class="textfield" type="text" x-inputmode="latin-prose" value="#streetAddress#" id="streetAddress_#i#" dir="auto">
<button type="reset">Clear</button>
</p>
<div class="two_fields second-row">
<legend class="action" role="combobox" aria-haspopup="true" aria-labelledby="address_type_#i#">
<span aria-hidden="true" data-taglist="address-type" name="adr[#i#][type]" data-value="#type_value#" id="address_type_#i#" data-field="type" data-l10n-id="#type_l10n_id#">#type#</span>
</legend>
<p class="setbox-item">
<input placeholder="City" data-field="locality" data-l10n-id="locality" name="adr[#i#][locality]" class="textfield" type="text" x-inputmode="latin-prose" value="#locality#" id="locality_#i#" dir="auto">
<button type="reset">Clear</button>
</p>
</div>
<div class="two_fields third-row">
<p class="setbox-item">
<input placeholder="Country" data-field="countryName" data-l10n-id="countryName" name="adr[#i#][countryName]" class="textfield" type="text" x-inputmode="latin-prose" value="#countryName#" id="countryName_#i#" dir="auto">
<button type="reset">Clear</button>
</p>
<p class="setbox-item">
<input placeholder="Zip Code" data-field="postalCode" data-l10n-id="postalCode" name="adr[#i#][postalCode]" class="textfield" type="text" x-inputmode="verbatim" value="#postalCode#" id="postalCode_#i#" dir="auto">
<button type="reset">Clear</button>
</p>
</div>
</section>
</fieldset>
</div>
<div id='add-date-#i#' role="listitem" data-template class='date-template field-template' data-exclusive="true" data-custom="false" data-index="#i#">
<fieldset class="fillflow-row first-row">
<div class="two_fields">
<legend class="action" role="combobox" aria-haspopup="true" aria-labelledby="date_type_#i#">
<span aria-hidden="true" data-taglist="date-type" name="date[#i#][type]" data-value="#type_value#" id="date_type_#i#" data-field="type" data-l10n-id="#type_l10n_id#">#type#</span>
</legend>
<section>
<p class="setbox-item">
<span aria-hidden="true" id="date-text_#i#" class="datetime placeholder">
Date
</span>
<input placeholder="date" data-field="#type#" data-l10n-id="date" name="date[#i#][value]" type="date" id="date_#i#" aria-labelledby="date-text_#i#">
</p>
</section>
</div>
</fieldset>
</div>
<div id='add-note-#i#' role="listitem" data-template class='note-template field-template' data-index="#i#">
<dl role="presentation" class="setbox fillflow-row first-row">
<dd class="setbox-body">
<p class="setbox-item">
<textarea placeholder="Comment" data-field="note"
data-l10n-id="comment" name="comment[#i#]" class="textfield"
type="text" x-inputmode="latin-prose" id="note_#i#">
#note#
</textarea>
<button type="reset">Clear</button>
</p>
</dd>
</dl>
</div>
<article class="view-body">
<div class="view-body-inner">
<form id="contact-form" action="#" class="fill-flow">
<input name="id" id="contact-form-id" type="hidden" value="">
<section class="item first">
<div id="thumbnail-action" href="#" class="item-media fillflow-media">
<div role="button" id="photo-button" class="bb-button">
<p>
<span class="icon icon-addimage"></span>
<em data-l10n-id="addPhoto">Add photo</em>
</p>
<p class="edit-photo">
<em data-l10n-id="edit-picture">Edit Picture</em>
</p>
<img id="thumbnail-photo" alt="">
</div>
</div>
<div class="item-body-exp with-overflow">
<p class="fillflow-row input-container">
<input placeholder="Name" data-field="givenName" data-l10n-id="givenName" name="givenName" class="textfield" type="text" x-inputmode="latin-prose" value="" id="givenName" dir="auto" autocorrect="off" autocapitalize="on" spellcheck="false" x-inputmode="verbatim">
<button type="reset">Clear</button>
</p>
<p class="fillflow-row input-container">
<input placeholder="Last name" data-field="familyName" data-l10n-id="familyName" name="familyName" class="textfield" type="text" x-inputmode="latin-prose" value="" id="familyName" dir="auto" autocorrect="off" autocapitalize="on" spellcheck="false" x-inputmode="verbatim">
<button type="reset">Clear</button>
</p>
</div>
<div class="with-overflow">
<p class="fillflow-row">
<input placeholder="Company Name" data-field="org" data-l10n-id="org" name="org" class="textfield" type="text" x-inputmode="verbatim" value="" id="org" dir="auto">
<button type="reset" id="clear-org">Clear</button>
</p>
</div>
</section>
<ul role="presentation">
<li role="region" aria-label="Phones" data-l10n-id="phones">
<section>
<div id="contacts-form-phones" role="list"></div>
</section>
<button data-field-type="tel" id='add-new-phone' data-l10n-id="addPhone" class="fillflow-row action action-add icon icon-add">
Add phone number
</button>
<hr>
</li>
<li role="region" aria-label="Emails" data-l10n-id="emails">
<section>
<div id="contacts-form-emails" role="list"></div>
</section>
<button data-field-type="email" id='add-new-email' data-l10n-id="addEmail" class="fillflow-row action action-add icon icon-add">
Add email address
</button>
<hr>
</li>
<li role="region" aria-label="Addresses" data-l10n-id="addresses">
<section>
<div id="contacts-form-addresses" role="list"></div>
</section>
<button data-field-type="adr" id='add-new-address' data-l10n-id="addAddress" class="fillflow-row action action-add icon icon-add">
Add new address
</button>
<hr>
</li>
<li role="region" aria-label="Dates" data-l10n-id="dates">
<section>
<div id="contacts-form-dates" role="list"></div>
</section>
<button data-field-type="date" id='add-new-date' data-l10n-id="addDate" class="fillflow-row action action-add icon icon-add">
Add Date
</button>
<hr>
</li>
<li role="region" aria-label="Notes" data-l10n-id="notes">
<section>
<div id="contacts-form-notes" role="list"></div>
</section>
<button data-field-type="note" id='add-new-note' data-l10n-id="addComment" class="fillflow-row action action-add icon icon-add">
Add comment
</button>
<hr>
</li>
</ul>
</form>
<footer class="tail-actions hide">
<button id='delete-contact' class="fillflow-row action action-delete danger">
<b data-l10n-id="deleteContact">Delete contact</b>
</button>
</footer>
</div>
</article>
</template>
</element>

View file

@ -0,0 +1,13 @@
<element name="ice-view" extends="section">
<template>
<gaia-header id="ice-header" action="back">
<h1 data-l10n-id="ice_contacts"></h1>
</gaia-header>
<arcticle class="view-body">
<section id="ice-list" data-type="list" class="active">
<ol id="ice-group" role="">
</ol>
</section>
</arcticle>
</template>
</element>

View file

@ -0,0 +1,22 @@
<element name="multiple-select-view" extends="section">
<template>
<template id="contactToImportTemplate">
<li>
<aside class="pack-end" aria-hidden="true">
<span data-type="img"></span>
</aside>
<p>Name</p>
<p>Org</p>
</li>
</template>
<gaia-header id="multiple-select-view-header" action="close">
<h1 id="multiple-select-view-title">Filename</h1>
<button id="save-button" data-l10n-id="importAll">Import all</button>
</gaia-header>
<article class="view-body">
<ol id="multiple-select-container" class="view-body-inner" data-type="list">
</ol>
</article>
</template>
</element>

View file

@ -0,0 +1,15 @@
<element name="confirm-form" extends="form">
<template>
<section id="progress-activity" class="hide">
<h1 id="progress-title"></h1>
<p role="status">
<span id="progress-msg"></span>
<progress id="progress-element" class="pack-activity" value="0" max="100"></progress>
</p>
</section>
<menu>
<button data-l10n-id="cancel" id="cancel-overlay">Cancel</button>
</menu>
</template>
</element>

View file

@ -0,0 +1,25 @@
<!-- bug 1060080: taken from details.html, should be unified -->
<element name="phone-details" extends="section">
<template>
<li data-phone id='phone-details-template-#i#' data-template role="region" aria-label="Phone" data-l10n-id="phoneDetail">
<h2>
<span data-l10n-id="#type_l10n_id#">#type#</span>
<span class="carrier-wrapper" hidden>
<span data-l10n-id="separator"></span>
<span class="carrier">#carrier#</span>
</span>
</h2>
<div class="item">
<div class="item-body-exp">
<div class="button activity call-action icon icon-call" id="call-or-pick-#i#">
<b aria-label="Call" data-l10n-id="makeCall"><bdi class="ellipsis-dir-fix">#value#</bdi></b>
<span class="js-sim-indication"></span>
</div>
</div>
<div class="item-media pull-right">
<button id="send-sms-button-#i#" class="activity send-sms icon icon-message" data-l10n-id="send-sms-button" aria-label="SMS"></button>
</div>
</div>
</li>
</template>
</element>

View file

@ -0,0 +1,7 @@
<element name="view-screenshot" extends="section">
<template>
<article id="view-screenshot-body">
</article>
</template>
</element>

View file

@ -0,0 +1,26 @@
<element name="search-view" extends="section">
<template>
<form id="searchview-container" class="search" role="search">
<p>
<label role="presentation">
<input type="search" name="search" class="textfield" x-inputmode="verbatim"
id="search-contact" placeholder="Search" data-l10n-id="search-contact">
<button type="reset">Clear</button>
</label>
</p>
<button id="cancel-search" data-l10n-id="cancel" type="submit">Cancel</button>
</form>
<section id="groups-list-search">
<ol id="search-list" data-type="list" class="active" role="listbox">
</ol>
</section>
<section id="search-process">
<p id="no-result" class="hide" data-l10n-id="noResults">No contacts found</p>
<p id="search-progress" class="hidden" role="status">
<progress class="small"></progress>
</p>
</section>
</template>
</element>

View file

@ -0,0 +1,130 @@
<element name="settings" extends="article">
<template>
<section data-theme="organic" id="view-settings" role="region" class="skin-organic view view-bottom theme-settings">
<gaia-header>
<h1 data-l10n-id="settings"></h1>
<button id="settings-close" data-l10n-id="done">Done</button>
</gaia-header>
<article class="view-body" id="settings-article">
<section role="region" class="view-body-inner">
<ul data-type="list" role="presentation">
<li id="settingsOrder" role="checkbox">
<gaia-switch name="order.lastname">
<label data-l10n-id="contactsOrderBy"></label>
</gaia-switch>
</li>
</ul>
<gaia-subheader skin="organic" id="transfer-contacts-header">
<span data-l10n-id="transferContactsTitle">Transfer Contacts</span>
</gaia-subheader>
<ul data-type="list" id="sources" role="listbox" aria-labelledby="transfer-contacts-header">
<li role="presentation" id="importContacts" data-source="sim">
<button role="option" class="icon icon-import" data-l10n-id="importContactsButton">
Import Contacts
</button>
</li>
<li role="presentation" id="exportContacts" data-source="sd">
<button role="option" class="icon icon-export" data-l10n-id="exportContactsButton">
Export Contacts
</button>
</li>
</ul>
<gaia-subheader skin="organic" id="contacts-management-header">
<span data-l10n-id="ContactManagementTitle">Contacts Management</span>
</gaia-subheader>
<ul data-type="list" id="contactsManagement" role="listbox" aria-labelledby="contacts-management-header">
<li role="presentation">
<button role="option" id="set-ice" class="action" data-l10n-id="setICEButton">
Set ICE Contacts
</button>
</li>
<li role="presentation">
<button role="option" id="bulkDelete" class="action" data-l10n-id="bulkDeleteButton">
Delete Contacts
</button>
</li>
</ul>
</section>
</article>
</section>
<section data-theme="organic" id="import-settings" role="region" class="skin-organic view view-right theme-settings">
<gaia-header id="import-settings-header" action="back">
<h1 id='import-settings-title'></h1>
</gaia-header>
<article class="view-body">
<section role="region" class="view-body-inner">
<ul role="listbox" data-type="list" id="import-options">
<li role="presentation" id="import-sd-option" data-source="sd">
<button role="option" class="icon icon-sd" >
<span data-l10n-id="importMemoryCard">Memory card</span>
<p><span></span><time></time></p>
</button>
<p class="error-message"></p>
</li>
<li role="presentation" id="import-gmail-option" class="importService" data-source="gmail">
<button role="option" class="icon icon-gmail">
<span data-l10n-id="importGmail">Gmail</span>
<p><span></span><time></time></p>
</button>
</li>
<li role="presentation" id="import-live-option" class="importService" data-source="live">
<button role="option" class="icon icon-live">
<span data-l10n-id="importOutlook">Outlook</span>
<p><span></span><time></time></p>
</button>
<p class="error-message" data-l10n-id="genericNoConnection"></p>
</li>
</ul>
<ul role="listbox" data-type="list" id="export-options">
<li role="presentation" id="export-sd-option" data-source="sd">
<button role="option" class="icon icon-sd" data-l10n-id="memoryCard">
Memory card
</button>
<p class="error-message"></p>
</li>
<li role="presentation" id="export-bluetooth-option" data-source="bluetooth">
<button role="option" class="icon icon-bluetooth" data-l10n-id="bluetooth">
Bluetooth
</button>
</li>
</ul>
</section>
</article>
</section>
<section data-theme="organic" id="ice-settings" role="region" class="skin-organic view view-right theme-settings">
<gaia-header id="ice-settings-header" action="back">
<h1 id='ice-settings-title' data-l10n-id="ICESettingTitle"></h1>
</gaia-header>
<article class="view-body">
<section role="region" class="view-body-inner">
<p class="description" data-l10n-id="ICEDescription">
Add two contacts for making “In Case of Emergency” calls.
</p>
<ul data-type="list" role="listbox" aria-multiselectable="true">
<li role="option" id="ice-contacts-1-switch" class="ice-contacts-switch">
<gaia-switch name="ice-contact-1-enabled">
<label data-l10n-id="ICEContact1"></label>
</gaia-switch>
</li>
<li role="presentation">
<button role="option" id="select-ice-contact-1" class="icon icon-view" data-l10n-id="ICESelectContact" disabled>
Select a contact
</button>
</li>
<li role="option" id="ice-contacts-2-switch" class="ice-contacts-switch">
<gaia-switch name="ice-contact-2-enabled">
<label data-l10n-id="ICEContact2"></label>
</gaia-switch>
</li>
<li role="presentation">
<button role="option" id="select-ice-contact-2" class="icon icon-view" data-l10n-id="ICESelectContact" disabled>
Select a contact
</button>
</li>
</ul>
</section>
</article>
</section>
</template>
</element>

View file

@ -0,0 +1,6 @@
<element name="status-message" extends="section">
<template>
<p></p>
</template>
</element>

View file

@ -0,0 +1,19 @@
<element name="view-select-tag" extends="section">
<template>
<gaia-header id="settings-header" action="back">
<h1 data-l10n-id="label"></h1>
<button id="settings-done" data-l10n-id="done">Done</button>
</gaia-header>
<article class="view-body">
<form class="view-body-inner">
<ul id="tags-list" class="selection-list compact" role="listbox">
</ul>
<p>
<input class="textfield tagItem" type="text" placeholder="Custom Tag" value="" id='custom-tag' data-l10n-id="customTag" dir="auto">
<button id="custom-tag-reset" type="reset">Clear</button>
</p>
</form>
</article>
</template>
</element>

View file

@ -0,0 +1,107 @@
<!DOCTYPE html>
<html lang="en-US" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta name="theme-color" content="#00b7b7">
<title>Facebook Link</title>
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/utilities/templates.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/utilities/image_square.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/utilities/image_thumbnail.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/utilities/event_listeners.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/import/utilities/misc.js"></script> -->
<!-- <script defer="" type="text/javascript" src="oauth2/js/flow.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/pages/import/js/parameters.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/import/facebook/fb_utils.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/import/facebook/fb_query.js"></script> -->
<!-- <script type="text/javascript" src="/shared/js/async_storage.js"></script> -->
<!-- <script type="text/javascript" src="/shared/js/contacts/import/import_status_data.js"></script> -->
<!-- <script type="text/javascript" src="/shared/js/text_normalizer.js"></script> -->
<!-- <script type="text/javascript" src="/shared/js/binary_search.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/lazy_loader.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/pages/import/js/oauth20.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/fb/fb_request.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/import/facebook/fb_data.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/fb/fb_reader_utils.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/import/facebook/fb_contact_utils.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/import/facebook/fb_contact.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/pages/import/js/curtain.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/import/facebook/facebook_connector.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/facebook/js/fb_sync.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/utilities/image_loader.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/import/utilities/status.js"></script> -->
<!-- <script defer="" type="text/javascript" src="/shared/js/contacts/utilities/dom.js"></script> -->
<!-- <script defer="" type="text/javascript" src="js/fb/fb_link.js"></script> -->
<!-- starts up the functionality -->
<!-- <script defer="" src="js/fb/fb_link_init.js"></script> -->
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="ar:201601260729, bg:201601260729, bn-BD:201601260729, ca:201601260729, cs:201601260729, da:201601260729, de:201601260729, el:201601260729, en-US:201601260729, es:201601260729, eu:201601260729, fr:201601260729, gl:201601260729, hr:201601260729, hu:201601260729, it:201601260729, ja:201601260729, km:201601260729, ko:201601260729, lt:201601260729, mk:201601260729, ms:201601260729, ne-NP:201601260729, nl:201601260729, pa:201601260729, pl:201601260729, pt-BR:201601260729, ro:201601260729, ru:201601260729, sk:201601260729, sq:201601260729, sr-Cyrl:201601260729, sr-Latn:201601260729, sv-SE:201601260729, tr:201601260729, zh-CN:201601260729, zh-TW:201601260729">
<link rel="localization" href="/locales-obj/contacts.fb_link.{locale}.json">
<!-- <script type="text/javascript" src="/shared/js/l10n.js"></script> --><script src="./gaia_build_fb_link.js" type="text/javascript"></script>
<!-- Building Blocks -->
<link href="/shared/style/buttons.css" rel="stylesheet">
<link href="/shared/style/confirm.css" rel="stylesheet">
<link href="/shared/style/lists.css" rel="stylesheet">
<link href="/shared/style/status.css" rel="stylesheet">
<!-- Web Components -->
<!-- <script defer="" src="/shared/elements/config.js"></script> -->
<!-- <link rel="stylesheet" type="text/css" href="/shared/elements/gaia-icons/gaia-icons.css" /> -->
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
<!-- <script defer="" src="/shared/elements/gaia-header/dist/gaia-header.js"></script> --><script src="./gaia_build_defer_fb_link.js" defer="" type="text/javascript"></script>
<link rel="stylesheet" href="/shared/pages/import/style/import.css">
<link rel="stylesheet" href="/shared/pages/import/style/facebook.css">
<link rel="stylesheet" href="style/status.css">
<meta content="2.5" name="appVersion"></head>
<body role="application" class="theme-communications skin-comms">
<section role="region" id="content" class="link">
<gaia-header id="link-header" action="close">
<h1 data-l10n-id="fbFriends">Facebook Friends</h1>
</gaia-header>
<section id="main">
<!-- Contacts List Section -->
<section id="mainContent" class="link-list" data-state="selection" data-type="list">
<ol id="friends-list">
<li data-template="" data-uuid="#uid#">
<a href="#">
<aside class="pack-end">
<span data-type="img" data-src="https://graph.facebook.com/#uid#/picture?width=#picwidth#&amp;height=#picheight#"></span>
</aside>
<p>#name#</p>
<p>#email#</p>
</a>
</li>
</ol>
</section> <!-- mainContent -->
<form role="dialog" data-type="confirm" class="no-overlay">
<menu>
<button id="view-all" data-l10n-id="viewAll" class="full">View All Facebook Friends</button>
</menu>
</form>
</section> <!-- main -->
</section> <!-- role="region" -->
<section id="statusMsg" role="status"><!--
<p></p>
--></section>
</body>
</html>

View file

@ -0,0 +1,730 @@
;'use strict';var utils=window.utils||{};if(!utils.templates){(function(){var Templates=utils.templates={};function getTarget(element){var target=element;if(!element.tagName){target=document.querySelector(element);}
return target;}
function getTemplate(target,data){var templates=target.querySelectorAll('*[data-template]');if(templates.length===0){throw new Error('No template declared');}
if(templates.length>1&&templates.item(0).dataset.condition){throw new Error('Only one template supported in this version');}
return{template:templates.item(0),isMulti:false};}
function templateReplace(data){return function(text,property){var out;if(property.indexOf('.')===-1){out=data[property];if(Array.isArray(out)&&out.length>0){out=out[0];}}else{throw new Error('Dotted expressions not supported');}
if(typeof out==='undefined'){out=text;}
return out;};}
function add(element,data,mode,targetNode){var target=getTarget(element);var newElem;var theData=[data];if(data instanceof Array){theData=data;}
var multiTemplate=true;var template;var idx=0;theData.forEach(function(oneData){oneData._idx_=idx++;if(multiTemplate===true){var tresult=getTemplate(target,oneData);template=tresult.template;if(tresult.isMulti===false){multiTemplate=false;}}
if(template){newElem=this.render(template,oneData);target=targetNode||target;if(mode==='A'){target.appendChild(newElem);}else if(mode==='P'){if(target.firstChild){target.insertBefore(newElem,element.firstChild);}else{target.appendChild(newElem);}}}}.bind(Templates));return newElem;}
Templates.append=function(element,data,targetNode){var f=add.bind(this);return f(element,data,'A',targetNode);};Templates.prepend=function(element,data,targetNode){var f=add.bind(this);return f(element,data,'P',targetNode);};Templates.render=function(eleTemplate,data){var newElem=eleTemplate.cloneNode(true);newElem.removeAttribute('data-template');newElem.removeAttribute('data-condition');var pattern=/#(\w+[\w.]*)#/g;var inner=newElem.innerHTML;var replaceFunction=templateReplace(data);var ninner=inner.replace(pattern,replaceFunction);newElem.innerHTML=ninner;var attrs=newElem.attributes;var total=attrs.length;for(var c=0;c<total;c++){var val=attrs[c].value;var nval=val.replace(pattern,replaceFunction);newElem.setAttribute(attrs[c].name,nval);}
if(!newElem.id){if(data.id){newElem.id=data.id;}}
return newElem;};Templates.clear=function(element){var target=getTarget(element);var templates=target.querySelectorAll('*[data-template]');target.innerHTML='';var total=templates.length;for(var c=0;c<total;c++){target.appendChild(templates.item(c));}};})();};'use strict';var utils=window.utils||{};if(typeof utils.squareImage==='undefined'){utils.squareImage=function(blob,callback){var img=document.createElement('img');var url=URL.createObjectURL(blob);img.src=url;function cleanupImg(){img.src='';URL.revokeObjectURL(url);}
img.onerror=cleanupImg;img.onload=function onBlobLoad(){var width=img.width;var height=img.height;if(width===height){cleanupImg();callback(blob);}else{var canvas=document.createElement('canvas');var min=canvas.width=canvas.height=Math.min(width,height);var context=canvas.getContext('2d',{willReadFrequently:true});context.drawImage(img,(width-min)/2,(height-min)/2,min,min,0,0,min,min);cleanupImg();canvas.toBlob(function onCanvasToBlob(canvasBlob){context=null;canvas.width=canvas.height=0;canvas=null;callback(canvasBlob);},'image/jpeg',0.95);}};};};'use strict';var utils=window.utils||{};(function(utils){var DEPENDENCIES=['/shared/js/contacts/import/utilities/config.js','/shared/js/image_utils.js'];var dpr=window.devicePixelRatio||1;var CONFIG_FILE='/config-images.json';var DEFAULT_CONFIG={'thumbnail':{'format':'image/jpeg','size':65,'quality':1.0}};var THUMB_CONFIG=DEFAULT_CONFIG.thumbnail;if(typeof utils.thumbnailImage!=='undefined'){return;}
function getThumbsConfig(){return new Promise(function(resolve,reject){utils.config.load(CONFIG_FILE).then(function resolved(config){resolve(config);},function rejected(){resolve(DEFAULT_CONFIG);});});}
function scaleImage(blob,configData){var thumbConfig=configData&&configData.thumbnail||{};var thumbnailEdge=(thumbConfig.size||THUMB_CONFIG.size)*dpr;var format=thumbConfig.format||THUMB_CONFIG.format;var encodingQuality=thumbConfig.quality||THUMB_CONFIG.quality;return ImageUtils.resizeAndCropToCover(blob,thumbnailEdge,thumbnailEdge,format,encodingQuality);}
utils.thumbnailImage=function(blob,callback){LazyLoader.load(DEPENDENCIES,function(){getThumbsConfig().then(function(config){return scaleImage(blob,config);}).then(callback).catch(function(err){console.error('Error while converting image to thumbnail:',err.name);callback(blob);});});};})(utils);;'use strict';var utils=window.utils||{};if(!utils.listeners){(function(document){var Listeners=utils.listeners={};Listeners.add=function(config){try{for(var id in config){var handler=config[id];var nodes=document.querySelectorAll(id);for(var i=0;i<nodes.length;i++){var node=nodes[i];if(Array.isArray(handler)){handler.forEach(function handle(item){if(!item.hasOwnProperty('event')&&!item.hasOwnProperty('handler')){return;}
node.addEventListener(item.event,item.handler);});}else{node.addEventListener('click',handler);}}}}
catch(e){window.console.error('Error while registering listener for: ',id,e);}};})(document);};'use strict';var utils=window.utils||{};var SCALE_RATIO=window.devicePixelRatio||1;var LAST_IMPORT_TIMESTAMP_SUFFIX='_last_import_timestamp';function scale(size){return Math.round(SCALE_RATIO*size);}
if(!utils.misc){utils.misc={};utils.misc.toMozContact=function(contact){var outContact=contact;if(!(contact instanceof mozContact)){outContact=new mozContact(contact);outContact.id=contact.id||outContact.id;}
return outContact;};const FLAG_YEAR_IGNORED=9996;utils.misc.FLAG_YEAR_IGNORED=FLAG_YEAR_IGNORED;utils.misc.formatDate=function(date,customFormat){var dateString=null;try{var format=customFormat||{month:'short',day:'numeric'};var offset=date.getTimezoneOffset()*60*1000;var normalizedDate=new Date(date.getTime()+offset);var year=normalizedDate.getFullYear();if(year!==FLAG_YEAR_IGNORED){format.year='numeric';}
var f=new Intl.DateTimeFormat(navigator.languages,format);dateString=f.format(normalizedDate);}catch(err){console.error('Error parsing date: ',err);throw err;}
return dateString;};utils.misc.parseName=function(nameString){var MIN_LENGHT_SIGNIFICATIVE=3;var MIN_LENGHT_FN=2;var out={givenName:'',familyName:''};if(!nameString){return out;}
var str=nameString.trim();if(!str){return out;}
function startsWithUpper(str){var firstLetter=str.charAt(0);var capFirstLetter=firstLetter.toLocaleUpperCase();return(capFirstLetter===firstLetter);}
var tokens=nameString.split(/\s+/);var significativeTokens={};for(var j=0;j<tokens.length;j++){var token=tokens[j];if(token.length>MIN_LENGHT_SIGNIFICATIVE||startsWithUpper(token)){significativeTokens[token]=true;}}
var totalTokens=tokens.length;var lastToken=totalTokens-1;var rolePrevToken;var remainingTokens=Object.keys(significativeTokens).length;var outGivenNames=[],outFamilyNames=[];var numFns=0;while(lastToken>=0){if(significativeTokens[tokens[lastToken]]){remainingTokens--;}
var currToken=tokens[lastToken],nextToken=tokens[lastToken+1],prevToken=tokens[lastToken-1];if(!nextToken&&prevToken){outFamilyNames.push(currToken);numFns++;rolePrevToken='FN';}
else if(!prevToken){outGivenNames.push(currToken);rolePrevToken='GN';}
else if(nextToken){if(!significativeTokens[currToken]){if(rolePrevToken==='FN'){outFamilyNames.push(currToken);}
else{outGivenNames.push(currToken);}}
else if(currToken.length<MIN_LENGHT_FN){outGivenNames.push(currToken);rolePrevToken='GN';}
else{if(remainingTokens>=2&&numFns<2){outFamilyNames.push(currToken);numFns++;rolePrevToken='FN';}
else{outGivenNames.push(currToken);rolePrevToken='GN';}}}
lastToken--;}
out.givenName=outGivenNames.reverse().join(' ').trim();out.familyName=outFamilyNames.reverse().join(' ').trim();return out;};utils.misc.getPreferredPictureBox=function(){var imgThumbSize=oauthflow.params.facebook.imgThumbSize;var out={width:scale(imgThumbSize)};out.height=out.width;return out;};utils.misc.getPreferredPictureDetail=function(){var imgDetailWidth=oauthflow.params.facebook.imgDetailWidth;return scale(imgDetailWidth);};utils.misc.setTimestamp=function(type,callback){ImportStatusData.put(type+LAST_IMPORT_TIMESTAMP_SUFFIX,Date.now()).then(callback);};utils.misc.getTimestamp=function(type,callback){ImportStatusData.get(type+LAST_IMPORT_TIMESTAMP_SUFFIX).then(callback);};};;var oauthflow=this.oauthflow||{};oauthflow.params={"facebook":{"redirectURI":"https://www.facebook.com/connect/login_success.html","loginPage":"https://m.facebook.com/dialog/oauth/?","applicationId":"123456","scope":["friends_about_me","friends_birthday","friends_hometown","friends_location","friends_work_history","read_stream"],"redirectMsg":"http://intense-tundra-4122.herokuapp.com/fbowd/oauth2_new/dialogs_end.html","redirectLogout":"http://intense-tundra-4122.herokuapp.com/fbowd/oauth2_new/dialogs_end.html","imgDetailWidth":200,"imgThumbSize":120},"live":{"redirectURI":"https://www.mozilla.org/index.html","loginPage":"https://login.live.com/oauth20_authorize.srf?","applicationId":"123456","scope":["wl.basic","wl.contacts_emails","wl.contacts_phone_numbers","wl.contacts_birthday","wl.contacts_postal_addresses"],"logoutUrl":"https://login.live.com/logout.srf"},"gmail":{"redirectURI":"https://serene-cove-3587.herokuapp.com/liveowd/oauth2_new/flow_live.html","loginPage":"https://accounts.google.com/o/oauth2/auth?","applicationId":"664741361278.apps.googleusercontent.com","scope":["https://www.google.com/m8/feeds/"],"logoutUrl":"https://accounts.google.com/Logout"}};;'use strict';var fb=window.fb||{};window.fb=fb;(function(document){var Utils=fb.utils||{};fb.utils=Utils;var FRIEND_COUNT_QUERY='select friend_count from user where uid=me()';var CACHE_FRIENDS_KEY=Utils.CACHE_FRIENDS_KEY='numFacebookFriends';var LAST_UPDATED_KEY=Utils.LAST_UPDATED_KEY='lastUpdatedTime';Utils.ALARM_ID_KEY='nextAlarmId';Utils.SCHEDULE_SYNC_KEY='facebookShouldHaveScheduledAt';var REDIRECT_LOGOUT_URI=window.oauthflow?oauthflow.params.facebook.redirectLogout:'';var STORAGE_KEY=Utils.TOKEN_DATA_KEY='tokenData';Utils.setLastUpdate=function(value,cb){window.asyncStorage.setItem(LAST_UPDATED_KEY,{data:value},cb);};Utils.getLastUpdate=function(callback){window.asyncStorage.getItem(LAST_UPDATED_KEY,function(obj){var out=0;if(obj){out=obj.data||out;}
if(typeof callback==='function'){callback(out);}});};Utils.getNonCacheableUrl=function(url){if(url.indexOf('?')===-1){url+='?';}
return url+'&burst_cache='+Date.now();};Utils.getContactData=function(cid){var outReq=new Utils.Request();var req=navigator.mozContacts.find({filterBy:['id'],filterValue:cid,filterOp:'equals'});req.onsuccess=function(e){if(e.target.result&&e.target.result.length>0){outReq.done(e.target.result[0]);}
else{outReq.done(null);}};req.onerror=function(e){outReq.failed(e.target.error);};return outReq;};Utils.getMozContact=function(uid){var outReq=new Utils.Request();window.setTimeout(function get_mozContact_ByUid(){fb.getMozContactByUid(uid,function onsuccess(result){if(Array.isArray(result)&&result.length>0){outReq.done(result[0]);}else{outReq.done(null);}},function onerror(e){outReq.failed(e.target.error);});},0);return outReq;};Utils.getNumberMozContacts=function(uid){var outReq=new Utils.Request();window.setTimeout(function get_mozContact_ByUid(){fb.getMozContactByUid(uid,function onsuccess(result){if(Array.isArray(result)){outReq.done(result.length);}else{outReq.done(0);}},function onerror(error){outReq.failed(error);});},0);return outReq;};Utils.getAllFbContacts=function(){var outReq=new Utils.Request();window.setTimeout(function get_all_fb_contacts(){var filter={filterValue:fb.CATEGORY,filterOp:'contains',filterBy:['category']};var req=navigator.mozContacts.find(filter);req.onsuccess=function(e){outReq.done(e.target.result);};req.onerror=function(e){outReq.failed(e.target.error);};},0);return outReq;};Utils.getNumFbContacts=function(){var outReq=new Utils.Request();window.setTimeout(function get_num_fb_contacts(){var req=fb.contacts.getLength();req.onsuccess=function(){outReq.done(req.result);};req.onerror=function(){outReq.failed(req.error);};},0);return outReq;};Utils.getNumFbFriends=function(callback,access_token){fb.utils.runQuery(FRIEND_COUNT_QUERY,callback,access_token);};Utils.getCachedAccessToken=function(callback){ImportStatusData.get(STORAGE_KEY).then(function(data){var out=null;if(data){out=data.access_token||null;}
if(typeof callback==='function'){callback(out);}});};Utils.setCachedAccessToken=function(data,callback){ImportStatusData.put(STORAGE_KEY,data).then(function done(){if(typeof callback==='function'){callback();}},function error(){console.error('Error while saving access token');if(typeof callback==='function'){callback();}});};Utils.getCachedNumFbFriends=function(callback){ImportStatusData.get(CACHE_FRIENDS_KEY).then(function(data){if(typeof callback==='function'&&typeof data==='number'){callback(data);}});};Utils.setCachedNumFriends=function(value,cb){ImportStatusData.put(CACHE_FRIENDS_KEY,value).then(cb);};Utils.removeCachedNumFriends=function(callback){ImportStatusData.remove(CACHE_FRIENDS_KEY).then(function(){typeof callback==='function'&&callback();});};Utils.getImportChecked=function(callback){Utils.getCachedAccessToken(function(access_token){var out='logged-out';if(access_token){out='logged-in';}
else{Utils.getCachedNumFbFriends(function(value){if(value){out='renew-pwd';if(typeof callback==='function'){callback(out);}}});}
if(typeof callback==='function'){callback(out);}});};Utils.numFbFriendsData=function(callback){var localCb=callback.local;var remoteCb=callback.remote;Utils.getCachedNumFbFriends(localCb);function auxCallback(response){if(response.data&&response.data[0]&&response.data[0].friend_count){remoteCb(response.data[0].friend_count);}}
if(typeof remoteCb==='function'&&navigator.onLine===true){var remoteCallbacks={success:auxCallback,error:null,timeout:null};Utils.getCachedAccessToken(function(access_token){if(access_token){Utils.getNumFbFriends(remoteCallbacks,access_token);}});}};Utils.clearFbData=function(){var outReq=new Utils.Request();window.setTimeout(function do_clearFbData(){var ireq=fb.contacts.clear();ireq.onsuccess=function(){var req=Utils.getAllFbContacts();req.onsuccess=function(){var cleaner=new Utils.FbContactsCleaner(req.result,'clear');outReq.done(cleaner);window.setTimeout(cleaner.start,0);};req.onerror=function(){window.console.error('FB Clean. Error retrieving FB Contacts');outReq.failed(req.error);};};ireq.onerror=function(e){window.console.error('Error while clearing the FB Cache');outReq.failed(ireq.error);};},0);return outReq;};Utils.logout=function(){var outReq=new Utils.Request();var WINDOW_TIMER_INTERVAL=500;var LOGOUT_TIMEOUT=5000;var MAX_TIMER_TICKS=LOGOUT_TIMEOUT/WINDOW_TIMER_INTERVAL;window.setTimeout(function do_logout(){Utils.getCachedAccessToken(function getAccessToken(access_token){if(access_token){var logoutService='https://www.facebook.com/logout.php?';var params=['next'+'='+encodeURIComponent(REDIRECT_LOGOUT_URI),'access_token'+'='+access_token];var logoutParams=params.join('&');var logoutUrl=logoutService+logoutParams;var loggedOut=false;var m_listen=function(e){if(e.origin!==fb.CONTACTS_APP_ORIGIN){return;}
if(e.data==='closed'){loggedOut=true;ImportStatusData.remove(STORAGE_KEY);outReq.done();}
e.stopImmediatePropagation();window.removeEventListener('message',m_listen);};window.addEventListener('message',m_listen);var logoutWindow=window.open(logoutUrl,'','dialog');var timerTicks=0;var timerId=window.setInterval(function(){timerTicks++;var closed=(logoutWindow.closed===true);var timedOut=(timerTicks>=MAX_TIMER_TICKS);if(loggedOut||closed||timedOut){window.clearInterval(timerId);window.removeEventListener('message',m_listen);}
if(closed&&!loggedOut){outReq.failed('UserCancelled');}
else if(timedOut&&!loggedOut){outReq.failed('Timeout');}},WINDOW_TIMER_INTERVAL);}
else{outReq.done();}});},0);return outReq;};Utils.FbContactsCleaner=function(contacts,pmode){this.lcontacts=contacts;var total=contacts.length;var next=0;var self=this;var CHUNK_SIZE=5;var numResponses=0;var mustUpdate=(pmode==='update');var notifyClean=false;var mustHold=false;var holded=false;var mustFinish=false;this.start=function(){mustHold=holded=mustFinish=false;if(total>0){cleanContacts(0);}
else if(typeof self.onsuccess==='function'){window.setTimeout(self.onsuccess);}};this.hold=function(){mustHold=true;};this.finish=function(){mustFinish=true;if(holded){notifySuccess();}};this.resume=function(){mustHold=holded=mustFinish=false;window.setTimeout(function resume_clean(){cleanContacts(next);});};function successHandler(e){if(notifyClean||typeof self.oncleaned==='function'){notifyClean=true;var cleaned=e.target.number;window.setTimeout(function(){self.oncleaned(cleaned);},0);}
continueCb();}
function errorHandler(contactid,error){if(typeof self.onerror==='function'){self.onerror(contactid,error);}
continueCb();}
function cleanContacts(from){for(var idx=from;idx<(from+CHUNK_SIZE)&&idx<total;idx++){var contact=contacts[idx];var number=idx;var req,fbContact;if(fb.isFbLinked(contact)){if(mustUpdate){fbContact=new fb.Contact(contact);req=fbContact.unlink('hard');}
else{fb.unlinkClearAll(contact);req=navigator.mozContacts.save(utils.misc.toMozContact(contact));}}
else{if(mustUpdate){fbContact=new fb.Contact(contact);req=fbContact.remove();}
else{req=navigator.mozContacts.remove(utils.misc.toMozContact(contact));}}
req.number=number;req.onsuccess=successHandler;req.onerror=function(e){errorHandler(contact.id,e.target.error);};}}
function notifySuccess(){if(typeof self.onsuccess==='function'){window.setTimeout(self.onsuccess);}}
function finishHandler(){var req=fb.contacts.flush();req.onsuccess=notifySuccess;req.onerror=function cleaner_flushError(){errorHandler(null,req.error);};}
function continueCb(){next++;numResponses++;if(next<total&&numResponses===CHUNK_SIZE){numResponses=0;if(!mustHold&&!mustFinish){cleanContacts(next);}
else if(mustFinish&&!holded){finishHandler();}
if(mustHold){holded=true;}}
else if(next>=total){finishHandler();}}};})(document);;'use strict';var fb=this.fb||{};var self=this;fb.utils=this.fb.utils||{};fb.DEFAULT_TIMEOUT=30000;fb.utils.runQuery=function(query,callback,access_token){function QueryRequest(){this.cancel=function(){if(typeof this.oncancel==='function'){window.setTimeout(function(){this.oncancel();}.bind(this),0);}};}
var outReq=new QueryRequest();var queryService='https://graph.facebook.com/fql?q=';queryService+=encodeURIComponent(query);var params=['access_token'+'='+access_token,'format=json'];var queryParams=params.join('&');var remote=queryService+'&'+queryParams;var xhr=new XMLHttpRequest({mozSystem:true});outReq.xhr=xhr;outReq.oncancel=function(){this.xhr.abort();};xhr.open('GET',remote,true);xhr.responseType='json';xhr.timeout=fb.operationsTimeout||fb.DEFAULT_TIMEOUT;xhr.onload=function(e){if(xhr.status===200||xhr.status===400||xhr.status===0){if(callback&&typeof callback.success==='function'){self.setTimeout(function(){callback.success(xhr.response);},0);}}
else{self.console.error('FB: HTTP error executing query. ',query,' Status: ',xhr.status);if(callback&&typeof callback.error==='function'){self.setTimeout(callback.error,0);}}};xhr.ontimeout=function(e){self.console.error('FB: Timeout!!! while executing query',query);if(callback&&typeof callback.timeout==='function'){self.setTimeout(callback.timeout,0);}};xhr.onerror=function(e){self.console.error('FB: Error while executing query: ',query,': ',e);if(callback&&typeof callback.error==='function'){self.setTimeout(function(){callback.error(e);},0);}};xhr.send();return outReq;};fb.utils.getFriendPicture=function(uid,callback,access_token,targetPictureSize){var imgSrc='https://graph.facebook.com/'+uid+'/picture?';var params=['width='+targetPictureSize,'access_token='+access_token,'t='+Date.now()];var imgService=imgSrc+params.join('&');var xhr=new XMLHttpRequest({mozSystem:true});xhr.open('GET',imgService,true);xhr.responseType='blob';xhr.timeout=fb.operationsTimeout||fb.DEFAULT_TIMEOUT;xhr.onload=function(e){if(xhr.status===200||xhr.status===0){var mblob=e.target.response;if(typeof callback==='function'){self.setTimeout(function(){callback(mblob);},0);}}
else{self.console.error('FB: HTTP error retrieving img for uid: ',uid,' Status: ',xhr.status);if(typeof callback==='function'){self.setTimeout(function(){callback(null);},0);}}};xhr.ontimeout=function(e){self.console.error('FB: Timeout!!! while retrieving img for uid: ',uid);if(typeof callback==='function'){self.setTimeout(function(){callback(null);},0);}};xhr.onerror=function(e){self.console.error('FB: Error while retrieving img for uid: ',uid,'Error: ',e);if(typeof callback==='function'){self.setTimeout(function(){callback(null);},0);}};xhr.send();};;;;;;;;'use strict';var LazyLoader=(function(){function LazyLoader(){this._loaded={};this._isLoading={};}
LazyLoader.prototype={_js:function(file,callback){var script=document.createElement('script');script.src=file;script.async=false;script.addEventListener('load',callback);document.head.appendChild(script);this._isLoading[file]=script;},_css:function(file,callback){var style=document.createElement('link');style.type='text/css';style.rel='stylesheet';style.href=file;document.head.appendChild(style);callback();},_html:function(domNode,callback){if(domNode.getAttribute('is')){this.load(['/shared/js/html_imports.js'],function(){HtmlImports.populate(callback);}.bind(this));return;}
for(var i=0;i<domNode.childNodes.length;i++){if(domNode.childNodes[i].nodeType==document.COMMENT_NODE){domNode.innerHTML=domNode.childNodes[i].nodeValue;break;}}
window.dispatchEvent(new CustomEvent('lazyload',{detail:domNode}));callback();},getJSON:function(file,mozSystem){return new Promise(function(resolve,reject){var xhr;if(mozSystem){xhr=new XMLHttpRequest({mozSystem:true});}else{xhr=new XMLHttpRequest();}
xhr.open('GET',file,true);xhr.responseType='json';xhr.onerror=function(error){reject(error);};xhr.onload=function(){if(xhr.response!==null){resolve(xhr.response);}else{reject(new Error('No valid JSON object was found ('+
xhr.status+' '+xhr.statusText+')'));}};xhr.send();});},load:function(files,callback){var deferred={};deferred.promise=new Promise(resolve=>{deferred.resolve=resolve;});if(!Array.isArray(files)){files=[files];}
var loadsRemaining=files.length,self=this;function perFileCallback(file){if(self._isLoading[file]){delete self._isLoading[file];}
self._loaded[file]=true;if(--loadsRemaining===0){deferred.resolve();if(callback){callback();}}}
for(var i=0;i<files.length;i++){var file=files[i];if(this._loaded[file.id||file]){perFileCallback(file);}else if(this._isLoading[file]){this._isLoading[file].addEventListener('load',perFileCallback.bind(null,file));}else{var method,idx;if(typeof file==='string'){method=file.match(/\.([^.]+)$/)[1];idx=file;}else{method='html';idx=file.id;}
this['_'+method](file,perFileCallback.bind(null,idx));}}
return deferred.promise;}};return new LazyLoader();}());;'use strict';var fb=window.fb||{};if(typeof window.oauth2==='undefined'){(function(document){var oauth2=window.oauth2={};var accessTokenCbData;var STORAGE_KEY='tokenData';var APP_ORIGIN=location.origin;function clearStorage(service){ImportStatusData.remove(getKey(service));}
function getKey(service){var key=STORAGE_KEY;if(service!=='facebook'){key=STORAGE_KEY+'_'+service;}
return key;}
oauth2.getAccessToken=function(ready,state,service){accessTokenCbData={callback:ready,state:state,service:service};fb.testToken=fb.testToken||(parent.fb&&parent.fb.testToken);if(service==='facebook'&&typeof fb.testToken==='string'&&fb.testToken.trim().length>0){window.console.warn('Facebook. A test token will be used!');tokenDataReady({data:{access_token:fb.testToken,expires_in:0,state:state}});return;}
ImportStatusData.get(getKey(service)).then(function getAccessToken(tokenData){if(!tokenData||!tokenData.access_token){startOAuth(state,service);return;}
var access_token=tokenData.access_token;var expires=Number(tokenData.expires);var token_ts=tokenData.token_ts;if(expires!==0&&Date.now()-token_ts>=expires){window.console.warn('Access token has expired, restarting flow');startOAuth(state,service);return;}
if(typeof ready==='function'){ready(access_token);}});};function startOAuth(state,service){clearStorage(service);oauthflow.start(state,service);}
function tokenDataReady(e){var parameters=e.data;if(!parameters||!parameters.access_token){return;}
if(e.origin!==APP_ORIGIN){return;}
Curtain.show('wait',accessTokenCbData.state);window.setTimeout(function do_token_ready(){var access_token=parameters.access_token;if(parameters.state===accessTokenCbData.state){accessTokenCbData.callback(access_token);}else{window.console.error('FB: Error in state',parameters.state,accessTokenCbData.state);return;}
var end=parameters.expires_in;ImportStatusData.put(getKey(accessTokenCbData.service),{access_token:access_token,expires:end*1000,token_ts:Date.now()}).then(function notify_parent(){parent.postMessage({type:'token_stored',data:''},APP_ORIGIN);});},0);}
window.addEventListener('message',tokenDataReady);})(document);};'use strict';var fb=self.fb||{};self.fb=fb;fb.utils=self.fb.utils||{};self.fb.utils=fb.utils;if(typeof fb.utils.Request!=='function'){fb.utils.Request=function(){this.done=function(result){this.result=result;if(typeof this.onsuccess==='function'){var ev={};ev.target=this;window.setTimeout(function(){this.onsuccess(ev);}.bind(this),0);}};this.failed=function(error){this.error=error;if(typeof this.onerror==='function'){var ev={};ev.target=this;window.setTimeout(function(){this.onerror(ev);}.bind(this),0);}};};};'use strict';var fb=window.fb||{};(function(){var contacts=fb.contacts||{};fb.contacts=contacts;var Reader;var readerLoaded=false;var INDEX_ID=1;var isIndexDirty=false;var READER_LOADED_EV='reader_loaded';var TEL_INDEXER_JS='/shared/js/fb/fb_tel_index.js';var PHONE_MATCHER_JS='/shared/js/simple_phone_matcher.js';var FB_READER_JS='/shared/js/fb/fb_data_reader.js';var BINARY_SEARCH_JS='/shared/js/binary_search.js';contacts.UID_NOT_FOUND='UIDNotFound';contacts.ALREADY_EXISTS='AlreadyExists';if(!contacts.init){var proxyMethods=['get','getLength','getByPhone','search','refresh','init','restart'];proxyMethods.forEach(function(aMethod){contacts[aMethod]=defaultFunction.bind(null,aMethod);});LazyLoader.load(FB_READER_JS,onreaderLoaded);}
else{onreaderLoaded();}
function onreaderLoaded(){readerLoaded=true;Reader=fb.contacts;document.dispatchEvent(new CustomEvent(READER_LOADED_EV));}
function setIndex(index){Reader.dsIndex=index;isIndexDirty=false;}
function datastore(){return Reader.datastore;}
function index(){return Reader.dsIndex;}
function defaultFunction(target){var args=[];for(var j=1;j<arguments.length;j++){args.push(arguments[j]);}
if(!readerLoaded){document.addEventListener(READER_LOADED_EV,function rd_loaded(){document.removeEventListener(READER_LOADED_EV,rd_loaded);Reader[target].apply(this,args);});}
else{Reader[target].apply(this,args);}}
function initError(outRequest,error){outRequest.failed(error);}
function safeError(err){if(err&&err.name){return err;}
return{name:'UnknownError'};}
function defaultError(request){return defaultErrorCb.bind(null,request);}
function defaultSuccess(request){return defaultSuccessCb.bind(null,request);}
function defaultErrorCb(request,error){request.failed(safeError(error));}
function defaultSuccessCb(request,result){request.done(result);}
function doSave(obj,outRequest){LazyLoader.load([TEL_INDEXER_JS,PHONE_MATCHER_JS,BINARY_SEARCH_JS],function(){var uid=obj.uid;datastore().add(obj,uid).then(function success(){indexByPhone(obj,uid);isIndexDirty=true;outRequest.done();},function error(err){if(err.name==='ConstraintError'){err={name:contacts.ALREADY_EXISTS};}
outRequest.failed(err);});});}
function indexByPhone(obj,newId){if(Array.isArray(obj.tel)){obj.tel.forEach(function(aTel){var variants=SimplePhoneMatcher.generateVariants(aTel.value);variants.forEach(function(aVariant){index().byTel[aVariant]=newId;});TelIndexer.index(index().treeTel,aTel.value.substring(1),newId);});}}
function reIndexByPhone(oldObj,newObj,dsId){removePhoneIndex(oldObj);indexByPhone(newObj,dsId);TelIndexer.orderTree(index().treeTel);}
function removePhoneIndex(deletedFriend){if(Array.isArray(deletedFriend.tel)){deletedFriend.tel.forEach(function(aTel){TelIndexer.remove(index().treeTel,aTel.value.substring(1));var variants=SimplePhoneMatcher.generateVariants(aTel.value);variants.forEach(function(aVariant){delete index().byTel[aVariant];});});}}
contacts.save=function(obj){var retRequest=new fb.utils.Request();window.setTimeout(function save(){contacts.init(function(){doSave(obj,retRequest);},function(err){initError(retRequest,err);});},0);return retRequest;};contacts.update=function(obj){var retRequest=new fb.utils.Request();window.setTimeout(function save(){contacts.init(function(){doUpdate(obj,retRequest);},function(err){initError(retRequest,err);});},0);return retRequest;};function doUpdate(obj,outRequest){LazyLoader.load([TEL_INDEXER_JS,PHONE_MATCHER_JS,BINARY_SEARCH_JS],function(){var uid=obj.uid;var successCb=successUpdate.bind(null,outRequest);var errorCb=errorUpdate.bind(null,outRequest,uid);datastore().get(uid).then(function success(oldObj){if(!oldObj){errorCb({name:contacts.UID_NOT_FOUND});return;}
reIndexByPhone(oldObj,obj,uid);datastore().put(obj,uid).then(function success(){return datastore().put(index(),INDEX_ID);},errorCb).then(successCb,errorCb);},errorCb);});}
function successUpdate(outRequest){outRequest.done();}
function errorUpdate(outRequest,uid,error){window.console.error('Error while updating datastore for: ',uid);outRequest.failed(error);}
function doRemove(uid,outRequest,forceFlush){LazyLoader.load([TEL_INDEXER_JS,PHONE_MATCHER_JS,BINARY_SEARCH_JS],function(){var errorCb=errorRemove.bind(null,outRequest,uid);var objToDelete;datastore().get(uid).then(function success_get_remove(object){objToDelete=object;if(!objToDelete){errorRemove(outRequest,uid,{name:contacts.UID_NOT_FOUND});return;}
datastore().remove(uid).then(function success_rm(removed){successRemove(outRequest,objToDelete,forceFlush,removed);},errorCb);},errorCb);});}
function successRemove(outRequest,deletedFriend,forceFlush,removed){if(removed===true){isIndexDirty=true;removePhoneIndex(deletedFriend);if(forceFlush){var flushReq=fb.contacts.flush();flushReq.onsuccess=function(){isIndexDirty=false;outRequest.done(true);};flushReq.onerror=function(){outRequest.failed(flushReq.error);};}
else{outRequest.done(true);}}
else{outRequest.done(false);}}
function errorRemove(outRequest,uid,error){error=safeError(error);window.console.error('FB Data: Error while removing ',uid,': ',error.name);outRequest.failed(error);}
contacts.remove=function(uid,flush){var hasToFlush=(flush===true?flush:false);var retRequest=new fb.utils.Request();window.setTimeout(function remove(){contacts.init(function(){doRemove(uid,retRequest,hasToFlush);},function(err){initError(retRequest,err);});},0);return retRequest;};contacts.clear=function(){var outRequest=new fb.utils.Request();window.setTimeout(function clear(){contacts.init(function(){doClear(outRequest);},function(err){initError(outRequest,err);});},0);return outRequest;};function doClear(outRequest){datastore().clear().then(function success(){setIndex(null);datastore().put(index(),INDEX_ID).then(defaultSuccess(outRequest),function error(err){err=safeError(err);window.console.error('Error while re-creating the index: ',err.name);outRequest.failed(err);});},defaultError(outRequest));}
contacts.flush=function(){var outRequest=new fb.utils.Request();window.setTimeout(function do_Flush(){if(!(datastore())||!isIndexDirty){window.console.warn('The datastore has not been initialized or is not dirty');outRequest.done();return;}
TelIndexer.orderTree(index().treeTel);datastore().put(index(),INDEX_ID).then(defaultSuccess(outRequest),defaultError(outRequest));},0);return outRequest;};})();;'use strict';var fb=self.fb||{};self.fb=fb;if(!self.AuxFB){self.AuxFb=(function(){var CATEGORY='facebook';var NOT_LINKED='not_linked';var LINKED='fb_linked';function populate(source,target,propertyNames){propertyNames.forEach(function(property){var propertyValue=source[property];if(propertyValue){if(Array.isArray(propertyValue)){target[property]=propertyValue.slice(0,propertyValue.length);}else{target[property]=propertyValue;}}});}
function mergeFbData(dcontact,fbdata){var multipleFields=['email','tel','photo','org','adr'];multipleFields.forEach(function(field){if(!dcontact[field]){dcontact[field]=[];}
var items=fbdata[field];if(items){items.forEach(function(item){var dupList=checkDuplicates(field,item,dcontact[field],fbdata.shortTelephone);if(dupList.length===0){dcontact[field].push(item);}});}});var singleFields=['bday','anniversary'];singleFields.forEach(function(field){if(!dcontact[field]){dcontact[field]=fbdata[field];}});mergeNames(dcontact,fbdata);}
function mergeNames(devContact,fbContact){var namesChanged=false;var nameFields=['givenName','familyName'];nameFields.forEach(function(anameField){var fieldValue=devContact[anameField];var fbValue=fbContact[anameField];if((!Array.isArray(fieldValue)||fieldValue.length===0)&&Array.isArray(fbValue)&&fbValue.length>0){namesChanged=true;devContact[anameField]=(fieldValue&&fieldValue[0])||[];devContact[anameField].push(fbValue[0]);}});if(namesChanged){var givenName=devContact.givenName[0]||'';var familyName=devContact.familyName[0]||'';devContact.name=[givenName+' '+familyName];}}
function isFbContact(devContact){return(devContact&&devContact.category&&devContact.category.indexOf(CATEGORY)!==-1);}
function isFbLinked(devContact){return(devContact&&devContact.category&&devContact.category.indexOf(LINKED)!==-1);}
function getData(devContact){var outReq=new fb.utils.Request();window.setTimeout(function do_getData(){var uid=getFriendUid(devContact);if(uid){var fbreq=fb.contacts.get(uid);fbreq.onsuccess=function(){var fbdata=fbreq.result;var out=mergeContact(devContact,fbdata);outReq.done(out);};fbreq.onerror=function(){outReq.failed(fbreq.error);};}
else{outReq.done(devContact);}},0);return outReq;}
function getFriendUid(devContact){var out=devContact.uid;if(!out){if(isFbLinked(devContact)){out=getLinkedTo(devContact);}
else if(devContact.category){var idx=devContact.category.indexOf(CATEGORY);if(idx!==-1){out=devContact.category[idx+2];}}}
return out;}
function getLinkedTo(devContact){var out;if(devContact.category){var idx=devContact.category.indexOf(LINKED);if(idx!==-1){out=devContact.category[idx+1];}}
return out;}
function mergeContact(devContact,fbContact){var out=devContact;if(fbContact){out=Object.create(null);out.updated=devContact.updated;out.published=devContact.published;populate(devContact,out,Object.getOwnPropertyNames(devContact));populate(devContact,out,Object.getOwnPropertyNames(Object.getPrototypeOf(devContact)));mergeFbData(out,fbContact);}
return out;}
function getContactByNumber(number,onsuccess,onerror){var req=fb.contacts.getByPhone(number);req.onsuccess=function get_by_phone_success(e){var fbData=req.result;if(fbData){fb.getMozContactByUid(fbData.uid,function merge(result){if(Array.isArray(result)&&result[0]){var finalContact=fb.mergeContact(result[0],fbData);onsuccess(finalContact);}
else{onsuccess(null);}},function error_get_mozContact(err){console.error('Error getting mozContact: ',err.name);onerror(err);});}
else{onsuccess(null);}};req.onerror=function(){onerror(req.error);};}
function checkDuplicates(field,fbItem,devContactItems,extraFbItems){var potentialDuplicatesFields=['email','tel'];var out=[];if(devContactItems&&potentialDuplicatesFields.indexOf(field)!==-1){var total=devContactItems.length;for(var i=0;i<total;i++){var localValue=devContactItems[i].value;var fbValue=fbItem.value;if(localValue){var trimedLocal=localValue.trim();if(trimedLocal===fbValue||(field==='tel'&&Array.isArray(extraFbItems)&&extraFbItems.indexOf(trimedLocal)!==-1)){out.push(trimedLocal);out.push(fbValue);}}}}
return out;}
function getMozContactByUid(uid,onsuccess,onerror){var filter={filterBy:['category'],filterValue:uid,filterOp:'contains'};var req=navigator.mozContacts.find(filter);req.onsuccess=function(){onsuccess(req.result);};req.onerror=function(){onerror(req.error);};}
return{'isFbContact':isFbContact,'isFbLinked':isFbLinked,'getData':getData,'getFriendUid':getFriendUid,'getLinkedTo':getLinkedTo,'mergeContact':mergeContact,'getContactByNumber':getContactByNumber,'getMozContactByUid':getMozContactByUid,'checkDuplicates':checkDuplicates,get CATEGORY(){return CATEGORY;},get LINKED(){return LINKED;},get NOT_LINKED(){return NOT_LINKED;}};})();var props=Object.keys(self.AuxFb);for(var j=0,end=props.length;j<end;j++){var prop=props[j];if(typeof self.fb[prop]==='function'){self.fb[prop]=self.AuxFb[prop];}
else{Object.defineProperty(self.fb,prop,{value:(function(){return self.AuxFb[prop];})(),writable:false,enumerable:true,configurable:false});}}};'use strict';var fb=this.fb||{};fb.PROPAGATED_PREFIX='fb_propagated_';fb.PROFILE_PHOTO_URI='fb_profile_photo';fb.FRIEND_URI='fb_friend';fb.DEFAULT_PHONE_TYPE='other';fb.DEFAULT_EMAIL_TYPE='other';fb.FLAG_YEAR_IGNORED=9996;fb.CONTACTS_APP_ORIGIN=location.origin;fb.isPropagated=function fcu_isPropagated(field,devContact){return(devContact.category&&devContact.category.indexOf(fb.PROPAGATED_PREFIX+field)!==-1);};fb.removePropagatedFlag=function fcu_removePropagatedFlag(field,devContact){var idx=devContact.category.indexOf(fb.PROPAGATED_PREFIX+field);if(idx!==-1){devContact.category.splice(idx,1);}};fb.setPropagatedFlag=function fcu_setPropagatedFlag(field,devContact){var idx=devContact.category.indexOf(fb.PROPAGATED_PREFIX+field);if(idx===-1){devContact.category.push(fb.PROPAGATED_PREFIX+field);}};fb.getFriendPictureUrl=function(devContact){var out;var urls=devContact.url;if(urls){for(var c=0;c<urls.length;c++){var aurl=urls[c];if(aurl.type.indexOf(fb.PROFILE_PHOTO_URI)!==-1){out=aurl.value;break;}}}
return out;};fb.setFriendPictureUrl=function(devContact,url){var urls=devContact.url||[];urls.push({type:[fb.PROFILE_PHOTO_URI],value:url});devContact.url=urls;};fb.friend2mozContact=function(f){function normalizeFbPhoneNumber(phone){var out=phone.number;if(phone.country_code&&out.indexOf('+')!==0){out='+'+phone.country_code+out;}
return out;}
if(Array.isArray(f.familyName)){return f;}
f.familyName=[f.last_name?f.last_name.trim():(f.last_name||'')];var middleName=f.middle_name?f.middle_name.trim():(f.middle_name||'');f.additionalName=middleName;var firstName=f.first_name?f.first_name.trim():(f.first_name||'');f.givenName=[(firstName+' '+middleName).trim()];delete f.last_name;delete f.middle_name;delete f.first_name;if(f.email){f.email1=f.email;f.email=[{type:[fb.DEFAULT_EMAIL_TYPE],value:f.email}];}
else{f.email1='';}
if(Array.isArray(f.phones)&&f.phones.length>0){f.tel=[];f.shortTelephone=[];f.phones.forEach(function(aphone){f.tel.push({type:[fb.DEFAULT_PHONE_TYPE],value:normalizeFbPhoneNumber(aphone)});f.shortTelephone.push(aphone.number);});}
delete f.phones;f.uid=f.uid.toString();return f;};fb.getWorksAt=function(fbdata){var out='';if(fbdata.work&&fbdata.work.length>0){out=fbdata.work[0].employer.name;}
return out;};fb.getBirthDate=function getBirthDate(sbday){var out=new Date(0);var imonth=sbday.indexOf('/');var smonth=sbday.substring(0,imonth);var iyear=sbday.lastIndexOf('/');if(iyear===imonth){iyear=sbday.length;}
var sday=sbday.substring(imonth+1,iyear);var syear=sbday.substring(iyear+1,sbday.length);out.setUTCDate(parseInt(sday,10));out.setUTCMonth(parseInt(smonth,10)-1,parseInt(sday,10));if(syear&&syear.length>0){out.setUTCFullYear(parseInt(syear,10));}
else{out.setUTCFullYear(fb.FLAG_YEAR_IGNORED);}
out.setUTCHours(0);out.setUTCMinutes(0);out.setUTCSeconds(0);out.setUTCMilliseconds(0);return out;};fb.getAddresses=function(fbdata){function fillAddress(fbAddress,type){var outAddr={};outAddr.type=[type];outAddr.locality=fbAddress.city||'';outAddr.region=fbAddress.state||'';outAddr.countryName=fbAddress.country||'';return outAddr;}
var out=[];var addrTypes={'home':'hometown_location','current':'current_location'};Object.keys(addrTypes).forEach(function onAddressType(type){var addrObj=fbdata[addrTypes[type]];if(addrObj){out.push(fillAddress(addrObj,type));}});return out;};fb.markAsUnlinked=function(devContact){var category=devContact.category;var updatedCategory=[];if(category){var idx=category.indexOf(fb.CATEGORY);if(idx!==-1){for(var c=0;c<idx;c++){updatedCategory.push(category[c]);}
for(c=idx+3;c<category.length;c++){updatedCategory.push(category[c]);}}}
devContact.category=updatedCategory;return devContact;};fb.unlinkClearAll=function(devContact){fb.resetNames(devContact);fb.markAsUnlinked(devContact);};fb.resetNames=function resetNames(dContact){if(fb.isPropagated('givenName',dContact)){dContact.givenName=[''];fb.removePropagatedFlag('givenName',dContact);}
if(fb.isPropagated('familyName',dContact)){dContact.familyName=[''];fb.removePropagatedFlag('familyName',dContact);}
dContact.name=[dContact.givenName[0]+' '+dContact.familyName[0]];};fb.markFbCleaningInProgress=function(value){utils.cookie.update({fbCleaningInProgress:value});};;'use strict';var fb=window.fb||{};fb.Contact=function(deviceContact,cid){var contactData;var devContact=deviceContact;var contactid=cid;function doGetFacebookUid(data){return fb.getFriendUid(data);}
function getFacebookUid(){return doGetFacebookUid(deviceContact);}
function setFacebookUid(value){doSetFacebookUid(deviceContact,value);}
function doSetFacebookUid(dcontact,value){if(!dcontact.category){dcontact.category=[];}
if(dcontact.category.indexOf(fb.CATEGORY)===-1){markAsFb(dcontact);}
var idx=dcontact.category.indexOf(fb.CATEGORY);dcontact.category[idx+2]=value;}
function markAsFb(dcontact){if(!dcontact.category){dcontact.category=[];}
if(dcontact.category.indexOf(fb.CATEGORY)===-1){dcontact.category.push(fb.CATEGORY);dcontact.category.push(fb.NOT_LINKED);}
return dcontact;}
function markAsLinked(dcontact,uid){if(!dcontact.category){dcontact.category=[];}
if(dcontact.category.indexOf(fb.LINKED)===-1){dcontact.category.push(fb.CATEGORY);dcontact.category.push(fb.LINKED);dcontact.category.push(uid);}
return dcontact;}
function promoteToLinked(dcontact){var idx=dcontact.category.indexOf(fb.NOT_LINKED);if(idx!=-1){dcontact.category[idx]=fb.LINKED;}}
this.setData=function(data){contactData=data;};Object.defineProperty(this,'uid',{get:getFacebookUid,set:setFacebookUid,enumerable:true,});Object.defineProperty(this,'mozContact',{get:getDevContact});function getDevContact(){return devContact;}
this.save=function(){var outReq=new fb.utils.Request();if(contactData&&navigator.mozContacts){window.setTimeout(function save_do(){var photoList=contactData.fbInfo.photo;if(photoList&&photoList.length>0&&photoList[0]){utils.squareImage(photoList[0],function squared(squared_image){photoList[0]=squared_image;doSave(outReq);});}
else{doSave(outReq);}},0);}
else{throw'Data or mozContacts not available';}
return outReq;};function doSave(outReq){var contactInfo={};copyNames(contactData,contactInfo);if(contactData.fbInfo.url){contactInfo.url=contactData.fbInfo.url;}
doSetFacebookUid(contactInfo,contactData.uid);var fbReq=persistToFbCache(contactData);fbReq.onsuccess=function(){var mozContactsReq=navigator.mozContacts.save(utils.misc.toMozContact(contactInfo));mozContactsReq.onsuccess=function(e){outReq.done(fbReq.result);};mozContactsReq.onerror=function(e){window.console.error('FB: Error while saving on mozContacts',e.target.error);outReq.failed(e.target.error);};};fbReq.onerror=function(){window.console.error('FB: Error while saving on datastore',fbReq.error&&fbReq.error.name);outReq.failed(fbReq.error);};}
function persistToFbCache(contactData,mUpdate){var isUpdate=(mUpdate===true?mUpdate:false);var outReq=new fb.utils.Request();window.setTimeout(function persist_fb_do(){var data=Object.create(contactData.fbInfo);data.tel=contactData.tel||[];data.email=contactData.email||[];data.uid=contactData.uid;Object.keys(contactData.fbInfo).forEach(function(prop){data[prop]=contactData.fbInfo[prop];});copyNames(contactData,data);var updaterFn=(isUpdate===true?fb.contacts.update:fb.contacts.save);var fbReq=updaterFn(data);fbReq.onsuccess=function(){outReq.done(fbReq.result);};fbReq.onerror=function(){window.console.error('FB: Error while saving Fb data on the Datastore',fbReq.error&&fbReq.error.name);outReq.failed(fbReq.error);};},0);return outReq;}
this.update=function(contactData){function auxDoUpdate(contactData,outReq){if(!contactData.fbInfo.photo){var dataReq=fb.contacts.get(contactData.uid);dataReq.onsuccess=function(){contactData.fbInfo.photo=dataReq.result.photo;auxCachePersist(contactData,outReq,true);};dataReq.onerror=function(){window.console.error('Error while retrieving existing photo for ',contactData.uid,dataReq.error);outReq.failed(dataReq.error);};}
else{utils.squareImage(contactData.fbInfo.photo[0],function sq_img(squaredImg){contactData.fbInfo.photo[0]=squaredImg;auxCachePersist(contactData,outReq,true);});}}
function auxCachePersist(contactData,outReq,isUpdate){var fbReq=persistToFbCache(contactData,isUpdate);fbReq.onsuccess=function(){outReq.done(fbReq.result);};fbReq.onerror=function(){window.console.error('FB: Error while saving to FB cache: ',contactData.uid,fbReq.error);outReq.failed(fbReq.error);};}
var outReq=new fb.utils.Request();window.setTimeout(function update_do(){if(!fb.isFbLinked(devContact)){copyNames(contactData,devContact);}else{revisitPropagatedNames(contactData,devContact);}
if(contactData.fbInfo.photo){devContact.url=contactData.fbInfo.url;}
var auxReq=new fb.utils.Request();auxReq.onsuccess=function(){var mozContactsReq=navigator.mozContacts.save(utils.misc.toMozContact(devContact));mozContactsReq.onsuccess=function(e){outReq.done();};mozContactsReq.onerror=function(e){window.console.error('FB: Error while saving mozContact: ',devContact.id,e.target.error);outReq.failed(e.target.error);};};auxReq.onerror=function(e){outReq.failed(e.target.error);};auxDoUpdate(contactData,auxReq);},0);return outReq;};function asArrayOfValues(value){return Array.isArray(value)?value:[value];}
function copyNames(source,destination){destination.name=asArrayOfValues(source.name);destination.givenName=asArrayOfValues(source.givenName);destination.familyName=asArrayOfValues(source.familyName);destination.additionalName=asArrayOfValues(source.additionalName);}
this.merge=function(fbdata){return fb.mergeContact(devContact,fbdata);};this.getData=function(){return fb.getData(devContact);};this.getDataAndValues=function(){var outReq=new fb.utils.Request();window.setTimeout(function do_getData(){var uid=doGetFacebookUid(devContact);if(uid){var fbreq=fb.contacts.get(uid);fbreq.onsuccess=(function(){var fbdata=fbreq.result;var out1=this.merge(fbdata);var out2={};var duplicates={};Object.keys(fbdata).forEach(function(key){var dataElement=fbdata[key];if(dataElement&&Array.isArray(dataElement)&&key!=='photo'){dataElement.forEach(function(item){if(item.value&&item.value.length>0){var dupList=fb.checkDuplicates(key,item,devContact[key],fbdata.shortTelephone);dupList.forEach(function(aDup){duplicates[aDup]=true;});out2[item.value]=true;}
else if(typeof item==='string'&&item.length>0){out2[item]=true;}
else if(key==='adr'){if(item.locality){out2[item.locality]=true;}
if(item.countryName){out2[item.countryName]=true;}}});}
else if(key==='photo'){out2.hasPhoto=true;}
else if(dataElement){out2[dataElement]=true;}});Object.keys(duplicates).forEach(function(aDup){delete out2[aDup];});outReq.done([out1,out2,fbdata]);}).bind(this);fbreq.onerror=function(){outReq.failed(fbreq.error);};}
else{outReq.done([devContact,{}]);}}.bind(this),0);return outReq;};this.promoteToLinked=function(){promoteToLinked(devContact);};this.linkTo=function(fbFriend){var out=new fb.utils.Request();window.setTimeout(function do_linkTo(){if(!devContact){var req=fb.utils.getContactData(contactid);req.onsuccess=function(){devContact=req.result;doLink(devContact,fbFriend,out);};req.onerror=function(){throw'FB: Error while retrieving contact data';};}
else{doLink(devContact,fbFriend,out);}},0);return out;};function propagateField(field,from,to){var copied=false;if(!Array.isArray(to[field])||!to[field][0]||!to[field][0].trim()){to[field]=from[field];copied=true;}
return copied;}
function createName(contact){contact.name=[];if(Array.isArray(contact.givenName)){contact.name[0]=contact.givenName[0]+' ';}
if(Array.isArray(contact.familyName)){contact.name[0]+=contact.familyName[0];}}
function propagateNames(from,to){var isGivenNamePropagated=propagateField('givenName',from,to);var isFamilyNamePropagated=propagateField('familyName',from,to);if(isGivenNamePropagated||isFamilyNamePropagated){if(isGivenNamePropagated){fb.setPropagatedFlag('givenName',to);}
if(isFamilyNamePropagated){fb.setPropagatedFlag('familyName',to);}
createName(to);}}
function revisitPropagatedNames(from,to){if(fb.isPropagated('givenName',to)){to.givenName=from.givenName;}
if(fb.isPropagated('familyName',to)){to.familyName=from.familyName;}
createName(to);}
function doLink(contactdata,fbFriend,out){if(contactdata){if(fbFriend.uid){markAsLinked(contactdata,fbFriend.uid);if(fbFriend.photoUrl){fb.setFriendPictureUrl(contactdata,fbFriend.photoUrl);}
else if(fbFriend.mozContact){contactdata.url=fbFriend.mozContact.url;}}
else if(fbFriend.mozContact){markAsLinked(contactdata,doGetFacebookUid(fbFriend.mozContact));contactdata.url=fbFriend.mozContact.url;}
propagateNames(fbFriend.mozContact,contactdata);var mozContactsReq=navigator.mozContacts.save(utils.misc.toMozContact(contactdata));mozContactsReq.onsuccess=function(e){if(fbFriend.mozContact&&!fb.isFbLinked(fbFriend.mozContact)){var deleteReq=navigator.mozContacts.remove(utils.misc.toMozContact(fbFriend.mozContact));deleteReq.onsuccess=function(e){out.done(e.target.result);};deleteReq.onerror=function(e){window.console.error('FB: Error while linking');out.failed(e.target.error);};}
else{out.done(e.target.result);}};mozContactsReq.onerror=function(e){out.failed(e.target.error);};}
else{throw'FB: Contact data not defined';}}
this.unlink=function(type){var out=new fb.utils.Request();window.setTimeout(function do_unlink(){if(!devContact){var req=fb.utils.getContactData(contactid);req.onsuccess=function(){devContact=req.result;doUnlink(devContact,out,type);};req.onerror=function(){throw'FB: Error while retrieving contact data';};}
else{doUnlink(devContact,out,type);}},0);return out;};function resetNames(dContact){fb.resetNames(dContact);}
function doUnlink(dContact,out,type){var theType=type||'soft';var uid=doGetFacebookUid(dContact);resetNames(dContact);fb.markAsUnlinked(dContact);var req=navigator.mozContacts.save(utils.misc.toMozContact(dContact));req.onsuccess=function(e){if(theType!=='hard'){var fbNumReq=fb.utils.getNumberMozContacts(uid);fbNumReq.onsuccess=function(){if(fbNumReq.result>=1){out.done();}else{var fbDataReq=fb.contacts.get(uid);fbDataReq.onsuccess=function(){var imported=fbDataReq.result;var data={};copyNames(imported,data);data.url=imported.url;doSetFacebookUid(data,uid);var aMozContact=utils.misc.toMozContact(data);var reqRestore=navigator.mozContacts.save(aMozContact);reqRestore.onsuccess=function(e){out.done(aMozContact.id);};reqRestore.onerror=function(e){out.failed(e.target.error);};};fbDataReq.onerror=function(){window.console.error('FB: Error while unlinking contact data');out.failed(fbDataReq.error);};}};fbNumReq.onerror=function(){window.console.error('FB: Error while unlinking contact data');out.failed(fbNumReq.error);};}
else{var removeReq=fb.contacts.remove(uid,true);removeReq.onsuccess=function(){out.done(removeReq.result);};removeReq.onerror=function(){out.failed(removeReq.error);};}};req.onerror=function(e){out.failed(e.target.error);};}
this.remove=function(pforceFlush){var forceFlush=(pforceFlush===true)?pforceFlush:false;var out=new fb.utils.Request();window.setTimeout(function do_remove(){var uid=doGetFacebookUid(devContact);var fbNumReq=fb.utils.getNumberMozContacts(uid);fbNumReq.onsuccess=function num_onsuccess(){var removeReq;if(fbNumReq.result===1){removeReq=navigator.mozContacts.remove(utils.misc.toMozContact(devContact));removeReq.onsuccess=function(e){var fbReq=fb.contacts.remove(uid,forceFlush);fbReq.onsuccess=function(){out.done(fbReq.result);};fbReq.onerror=function(){out.failed(fbReq.error);};};removeReq.onerror=function(e){out.failed(e.target.error);};}
else{removeReq=navigator.mozContacts.remove(utils.misc.toMozContact(devContact));removeReq.onsuccess=function(e){out.done();};removeReq.onerror=function(e){out.failed(e.target.error);};}};},0);return out;};};;'use strict';var Curtain=(function(){var curtainFrame=parent.document.querySelector('#iframe_curtain');if(!curtainFrame){curtainFrame=document.createElement('iframe');curtainFrame.id='iframe_curtain';curtainFrame.src='/shared/pages/import/curtain.html';parent.document.body.appendChild(curtainFrame);}
function getDoc(){return curtainFrame.contentWindow.document;}
var cpuWakeLock,cancelButton,retryButton,okButton,progressElement,form,progressTitle;var messages=[];var elements=['error','timeout','wait','message','progress','alert'];if(getDoc().readyState==='complete'){init();}else{curtainFrame.addEventListener('load',function loaded(){curtainFrame.removeEventListener('load',loaded);init();});}
function init(){var doc=getDoc();cancelButton=doc.querySelector('#cancel');retryButton=doc.querySelector('#retry');okButton=doc.querySelector('#ok');progressElement=doc.querySelector('#progressElement');form=doc.querySelector('form');elements.forEach(function createElementRef(name){messages[name]=doc.getElementById(name+'Msg');});progressTitle=doc.getElementById('progressTitle');}
function doShow(type){form.classList.remove('no-menu');form.dataset.state=type;curtainFrame.classList.add('visible');curtainFrame.classList.remove('fade-out');curtainFrame.classList.add('fade-in');}
function capitalize(str){return str.charAt(0).toUpperCase()+str.slice(1);}
function Progress(pfrom){var from=pfrom;var counter=0;var total=0;progressElement.setAttribute('value',0);function showMessage(){document.l10n.setAttributes(messages.progress,'progressFB',{current:counter,total:total});}
this.update=function(){progressElement.setAttribute('value',(++counter*100)/total);showMessage();};this.setFrom=function(pfrom){from=capitalize(pfrom);progressTitle.setAttribute('data-l10n-id','progressFB3'+from+'Title');};this.setTotal=function(ptotal){total=ptotal;showMessage();};this.getValue=function(){return counter;};}
return{show:function(type,from){var out;from=capitalize(from);switch(type){case'wait':messages[type].setAttribute('data-l10n-id',type+from);break;case'timeout':document.l10n.formatValue('timeout'+from).then((from)=>{document.l10n.setAttributes(messages[type],'timeout1',{from:from});});break;case'error':document.l10n.formatValue('type'+from).then((from)=>{document.l10n.setAttributes(messages[type],'error1',{from:from});});break;case'alert':case'message':messages[type].setAttribute('data-l10n-id',type+from);break;case'progress':progressTitle.setAttribute('data-l10n-id',type+'FB3'+from+'Title');out=new Progress(from);cpuWakeLock=navigator.requestWakeLock('cpu');break;}
doShow(type);return out;},hide:function c_hide(hiddenCB){if(cpuWakeLock){cpuWakeLock.unlock();cpuWakeLock=null;}
curtainFrame.classList.add('fade-out');curtainFrame.addEventListener('animationend',function cu_fadeOut(ev){curtainFrame.removeEventListener('animationend',cu_fadeOut);curtainFrame.classList.remove('visible');curtainFrame.classList.remove('fade-out');curtainFrame.classList.remove('fade-in');delete form.dataset.state;if(typeof hiddenCB==='function'){hiddenCB();}});},set oncancel(cancelCb){if(typeof cancelCb==='function'){delete cancelButton.onclick;cancelButton.onclick=function on_cancel(e){delete cancelButton.onclick;cancelCb();return false;};}},set onretry(retryCb){if(typeof retryCb==='function'){delete retryButton.onclick;retryButton.onclick=function on_retry(e){delete retryButton.onclick;retryCb();return false;};}},set onok(okCb){if(typeof okCb==='function'){delete okButton.onclick;okButton.onclick=function on_ok(e){delete okButton.onclick;okCb();return false;};}},get visible(){return curtainFrame.classList.contains('visible');},hideMenu:function c_hideMenu(){form.classList.add('no-menu');}};})();;'use strict';if(!window.FacebookConnector){window.FacebookConnector=(function(){var nextUpdateTime;window.fb=window.fb||{};fb.operationsTimeout=parent.config?parent.config.operationsTimeout:null;var reusedFbContact=new fb.Contact();reusedFbContact.ready=true;function createFbImporter(clist,access_token){var out=new window.ContactsImporter(clist,access_token,this);out.persist=persistFbData;return out;}
function persistFbData(data,successCb,errorCb,options){var saveCbs={success:function(e){if(options==='not_match'){successCb();return;}
var cbs={onmatch:function(matches){Object.keys(matches).forEach(function(aMatchId){var fbContact=new
fb.Contact(matches[aMatchId].matchingContact);var mozContReq=fb.utils.getMozContact(data.uid);mozContReq.onsuccess=function(){var linkParams={uid:data.uid,mozContact:mozContReq.result};if(Array.isArray(data.photo)&&data.photo[0]){linkParams.photoUrl=data.pic_big;}
var req=fbContact.linkTo(linkParams);req.onsuccess=successCb;req.onerror=function error(e){window.console.error('Error while automatically linking : ',aMatchId,data.uid,e.target.name);successCb();};};mozContReq.onerror=function(e){window.console.error('Error getting mozContact data in automatic linking: ',e.target.error.name);successCb();};});},onmismatch:successCb};Matcher.match(data,'passive',cbs);},error:errorCb};saveFbContact(data,saveCbs.success,saveCbs.error);}
function saveFbContact(data,successCb,errorCb){var fbContact,successWrapperCb;if(reusedFbContact.ready){reusedFbContact.ready=null;fbContact=reusedFbContact;successWrapperCb=function onsuccess(e){reusedFbContact.ready=true;successCb(e);};}else{fbContact=new fb.Contact();successWrapperCb=successCb;}
fbContact.setData(data);var req=fbContact.save();req.onsuccess=successWrapperCb;req.onerror=errorCb;}
function setInfraForSync(existingContacts,friendsImported,callback){if(!fb.sync){(typeof callback==='function')&&callback();return;}
window.asyncStorage.getItem(fb.utils.ALARM_ID_KEY,function(data){if(!data||(existingContacts&&existingContacts.length===0)&&!friendsImported){fb.utils.setLastUpdate(nextUpdateTime,function(){var req=fb.sync.scheduleNextSync();if(typeof callback==='function'){req.onsuccess=callback;}});}});}
var UID_FILTER_IDX=5;var acc_tk;function buildFriendQuery(uid){var aquery1=[].concat(FRIENDS_QUERY);aquery1[UID_FILTER_IDX]='= '+uid;return aquery1.join('');}
var FRIENDS_QUERY=['SELECT uid, name, first_name, last_name, pic_big, current_location, ','middle_name, birthday_date, email, profile_update_time, ',' work, phones, hometown_location',' FROM user',' WHERE uid ','IN (SELECT uid1 FROM friend WHERE uid2=me())',' ORDER BY ','first_name'];var friendsQueryStr=FRIENDS_QUERY.join('');function contactDataLoaded(options,response){if(!response.error){nextUpdateTime=Date.now();var successCb=this.success;var friend=response.data[0];if(friend){var out1=self.adaptDataForShowing(friend);var photoCbs={};photoCbs.success=function(blobPicture){if(!blobPicture){pictureLoaded(out1,options,successCb);return;}
utils.thumbnailImage(blobPicture,function gotThumbnail(thumbnail){if(blobPicture!==thumbnail){out1.photo=[blobPicture,thumbnail];}else{out1.photo=[blobPicture];}
pictureLoaded(out1,options,successCb);});};self.downloadContactPicture(friend,acc_tk,photoCbs);}
else{window.console.error('FB: No Friend data found');this.error('No friend data found');}}
else{this.error(response.error);}}
function pictureLoaded(friend,options,success){var data=self.adaptDataForSaving(friend);persistFbData(data,function(){success({uid:friend.uid,url:friend.pic_big});},function error(err){console.error('Error while persisting: ',err);},options);window.asyncStorage.getItem(fb.utils.ALARM_ID_KEY,function(data){if(!data){fb.utils.setLastUpdate(nextUpdateTime,fb.sync.scheduleNextSync);}});}
function FacebookConnector(){}
FacebookConnector.prototype={listAllContacts:function(access_token,callbacks,options){if(options&&options.orderBy==='lastName'){FRIENDS_QUERY[FRIENDS_QUERY.length]='last_name';friendsQueryStr=FRIENDS_QUERY.join('');}
return fb.utils.runQuery(friendsQueryStr,{success:callbacks.success,error:callbacks.error,timeout:callbacks.timeout},access_token);},listDeviceContacts:function(callbacks){var req=fb.utils.getAllFbContacts();req.onsuccess=function successHandler(){callbacks.success(req.result);};req.onerror=callbacks.error;},getImporter:function(contactsList,access_token){return createFbImporter.bind(this)(contactsList,access_token);},importContact:function(uid,access_token,callbacks,options){acc_tk=access_token;var oneFriendQuery=buildFriendQuery(uid);var auxCallbacks={success:contactDataLoaded.bind(callbacks,options),error:callbacks.error,timeout:callbacks.timeout};fb.utils.runQuery(oneFriendQuery,auxCallbacks,access_token);},cleanContacts:function(contactsList,mode,cb){if(mode==='update'){var cleaner=new fb.utils.FbContactsCleaner(contactsList,mode);window.setTimeout(cleaner.start,0);cb(cleaner);}
else{var req=fb.utils.clearFbData();req.onsuccess=function(){cb(req.result);};req.onerror=function(e){window.console.error('Error while starting cleaning: ',e.target.error.name);cb(null);};}},adaptDataForShowing:function(source){var box=utils.misc.getPreferredPictureBox();var picWidth=box.width;var picHeight=box.height;var out=fb.friend2mozContact(source);out.contactPictureUri='https://graph.facebook.com/'+
source.uid+'/picture?type=square'+'&width='+picWidth+'&height='+picHeight+'&t='+Date.now();return out;},adaptDataForSaving:function fbAdapt(cfdata){var worksAt=fb.getWorksAt(cfdata);var address=fb.getAddresses(cfdata);var birthDate=null;if(cfdata.birthday_date&&cfdata.birthday_date.length>0){birthDate=fb.getBirthDate(cfdata.birthday_date);}
var fbInfo={bday:birthDate,org:[worksAt]};if(address){fbInfo.adr=address;}
if(cfdata.shortTelephone){fbInfo.shortTelephone=cfdata.shortTelephone;delete cfdata.shortTelephone;}
fbInfo.url=[];if(cfdata.photo){fbInfo.photo=cfdata.photo;if(cfdata.pic_big){fb.setFriendPictureUrl(fbInfo,cfdata.pic_big);}
delete cfdata.photo;}
cfdata.fbInfo=fbInfo;return cfdata;},getContactUid:function(deviceContact){return fb.getFriendUid(deviceContact);},get name(){return'facebook';},get automaticLogout(){return false;},downloadContactPicture:function(contact,access_token,callbacks){return fb.utils.getFriendPicture(contact.uid,callbacks.success,access_token,utils.misc.getPreferredPictureDetail());},oncontactsloaded:function(lfriends){nextUpdateTime=Date.now();fb.utils.setCachedNumFriends(lfriends.length);},oncontactsimported:function(existingContacts,friendsImported,cb){setInfraForSync(existingContacts,friendsImported,cb);fb.contacts.flush();},startSync:function(existingContacts,myFriendsByUid,syncFinished){var callbacks={success:syncFinished};fb.sync.startWithData(existingContacts,myFriendsByUid,callbacks);},scheduleNextSync:function(){fb.sync.scheduleNextSync();}};var self=new FacebookConnector();return self;})();};'use strict';var fb=window.fb||{};if(!fb.sync){(function(){var Sync=fb.sync={};var theWorker;var fbContactsById;var totalToChange=0,changed=0;var nextTimestamp;var completionCallback,errorCallback;var fbFriendsDataByUid;var logLevel=fb.logLevel||parent.fb.logLevel||'DEBUG';var isDebug=(logLevel==='DEBUG');var alarmFrame=null,currentAlarmRequest=null;var DELAY_ALARM_SCHED=5000;function debug(){if(isDebug){var theArgs=['<<FBSync>>'];for(var c=0;c<arguments.length;c++){theArgs.push(arguments[c]);}
window.console.log.apply(window.console,theArgs);}}
function startWorker(){theWorker=new Worker('/facebook/js/sync_worker.js');theWorker.onmessage=function(e){workerMessage(e.data);};theWorker.onerror=function(e){window.console.error('Worker Error',e.message,e.lineno,e.column);if(typeof errorCallback==='function'){errorCallback({type:'default_error'});}};}
function workerMessage(m){switch(m.type){case'query_error':var error=m.data||{};window.console.error('FB: Error reported by the worker',JSON.stringify(error));if(typeof errorCallback==='function'){errorCallback({name:'defaultError'});}
break;case'token_error':debug('FB: Token error reported by the worker');if(typeof errorCallback==='function'){errorCallback({name:'invalidToken'});}
break;case'timeout_error':debug('Timeout error reported by the worker');if(typeof errorCallback==='function'){errorCallback({name:'timeout'});}
break;case'trace':debug(m.data);break;case'friendRemoved':removeFbFriend(m.data.contactId);break;case'friendUpdated':updateFbFriend(m.data.contactId,fb.friend2mozContact(m.data.updatedFbData));break;case'totals':changed=0;totalToChange=m.data.totalToChange;nextTimestamp=m.data.queryTimestamp;var newFriendNumber=m.data.newFriendNumber;if(newFriendNumber){debug('New friend number: ',newFriendNumber);fb.utils.setCachedNumFriends(newFriendNumber);}
debug('Total to be changed: ',totalToChange);checkTotals();break;case'friendImgReady':debug('Friend Img Data ready: ',m.data.contactId);updateFbFriendWhenImageReady(m.data);break;}}
function updateFbFriendWhenImageReady(data){var contact=fbContactsById[data.contactId];var uid=fb.getFriendUid(contact);var updatedFbData=fbFriendsDataByUid[uid];if(!data.photo){updateFbFriend(data.contactId,updatedFbData);return;}
utils.thumbnailImage(data.photo,function gotTumbnail(thumbnail){var fbInfo={};fbInfo.photo=[data.photo,thumbnail];fb.setFriendPictureUrl(fbInfo,updatedFbData.pic_big);updatedFbData.fbInfo=fbInfo;updateFbFriend(data.contactId,updatedFbData);});}
function onsuccessCb(){changed++;checkTotals();}
function updateFbFriend(contactId,cfdata){fb.friend2mozContact(cfdata);cfdata.fbInfo=cfdata.fbInfo||{};cfdata.fbInfo.org=[fb.getWorksAt(cfdata)];var birthDate=null;if(cfdata.birthday_date&&cfdata.birthday_date.length>0){birthDate=fb.getBirthDate(cfdata.birthday_date);}
cfdata.fbInfo.bday=birthDate;var address=fb.getAddresses(cfdata);if(address){cfdata.fbInfo.adr=address;}
if(cfdata.shortTelephone){cfdata.fbInfo.shortTelephone=cfdata.shortTelephone;delete cfdata.shortTelephone;}
var fbContact=new fb.Contact(fbContactsById[contactId]);var fbReq=fbContact.update(cfdata);fbReq.onsuccess=function(){debug('Friend updated correctly',cfdata.uid);onsuccessCb();};fbReq.onerror=function(){window.console.error('FB: Error while saving contact data: ',cfdata.uid);changed++;checkTotals();};}
function removeFbFriend(contactId){debug('Removing Friend: ',contactId);var removedFriend=fbContactsById[contactId];var fbContact=new fb.Contact(removedFriend);if(fb.isFbLinked(removedFriend)){debug('Friend is linked: ',contactId);var req=fbContact.unlink('hard');req.onsuccess=onsuccessCb;req.onerror=function(){window.console.error('FB. Error while hard unlinking friend: ',contactId);changed++;checkTotals();};}
else{debug('Friend is not linked: ',contactId);var fbContactReq=fbContact.remove();fbContactReq.onsuccess=onsuccessCb;fbContactReq.onerror=function(){window.console.error('FB. Error while removing contact: ',contactId);changed++;checkTotals();};}}
function checkTotals(){if(changed===totalToChange){debug('Sync process finished!');if(window.contacts&&window.contacts.List){window.setTimeout(window.contacts.List.load,0);}
fb.utils.setLastUpdate(nextTimestamp,function sync_end(){completionCallback(totalToChange);});if(theWorker){theWorker.terminate();theWorker=null;}}}
Sync.start=function(params){if(params){completionCallback=params.success;errorCallback=params.error;}
totalToChange=0;changed=0;var req=fb.utils.getAllFbContacts();req.onsuccess=function(){var uids={};var fbContacts=req.result;if(fbContacts.length===0){debug('Nothing to be synchronized. No FB Contacts present');return;}
startWorker();fbContactsById={};var forceUpdate={};fbContacts.forEach(function(contact){fbContactsById[contact.id]=contact;var pictureUrl=fb.getFriendPictureUrl(contact);var uid=fb.getFriendUid(contact);uids[uid]={contactId:contact.id,photoUrl:pictureUrl};if(!pictureUrl){forceUpdate[uid]={contactId:contact.id};}});fb.utils.getLastUpdate(function run_worker(ts){fb.utils.getCachedAccessToken(function(access_token){theWorker.postMessage({type:'start',data:{uids:uids,imgNeedsUpdate:forceUpdate,timestamp:ts,access_token:access_token,operationsTimeout:fb.operationsTimeout,targetPictureSize:utils.misc.getPreferredPictureDetail()}});});});};req.onerror=function(){window.console.error('FB: Error while getting friends on the device',req.error.name);if(typeof errorCallback==='function'){errorCallback({name:'defaultError'});}};};Sync.scheduleNextSync=function(){currentAlarmRequest=new fb.utils.Request();window.setTimeout(function(){alarmFrame=document.createElement('iframe');alarmFrame.src='/facebook/fb_sync.html';alarmFrame.width=1;alarmFrame.height=1;alarmFrame.style.display='none';document.body.appendChild(alarmFrame);},0);return currentAlarmRequest;};function cleanAlarmSchedFrame(){alarmFrame.src=null;document.body.removeChild(alarmFrame);alarmFrame=null;}
Sync.onAlarmScheduled=function(date){debug('Next synch scheduled at: ',date);if(alarmFrame){window.setTimeout(cleanAlarmSchedFrame,DELAY_ALARM_SCHED);}
currentAlarmRequest.done(date);};Sync.onAlarmError=function(e){if(alarmFrame){window.setTimeout(cleanAlarmSchedFrame,DELAY_ALARM_SCHED);}
window.console.error('<<FB Sync>> Error while scheduling a new sync: ',e);currentAlarmRequest.failed(e);};Sync.debug=function(){debug.apply(this,arguments);};Sync.startWithData=function(contactList,myFriendsByUid,callbacks){completionCallback=callbacks.success;errorCallback=callbacks.error;nextTimestamp=Date.now();changed=0;totalToChange=Number.MAX_VALUE;debug('Starting Synchronization with data');fbFriendsDataByUid=myFriendsByUid;var toBeUpdated={};fb.utils.getLastUpdate(function import_updates(lastUpdate){var toBeChanged=0;var lastUpdateTime=Math.round(lastUpdate/1000);debug('Last update time: ',lastUpdateTime);fbContactsById={};contactList.forEach(function(aContact){fbContactsById[aContact.id]=aContact;var uid=fb.getFriendUid(aContact);var friendData=fbFriendsDataByUid[uid];if(friendData){var friendUpdate=friendData.profile_update_time;debug('Friend update Time ',friendUpdate,'for UID: ',uid);var profileImgUrl=fb.getFriendPictureUrl(aContact);if(friendUpdate>lastUpdateTime||profileImgUrl!==friendData.pic_big){debug('Friend changed!! : ',uid);if(profileImgUrl!==friendData.pic_big){debug('Profile img changed: ',profileImgUrl);toBeUpdated[uid]={contactId:aContact.id};}
else{debug('Updating friend: ',friendData.uid);toBeChanged++;updateFbFriend(aContact.id,friendData);}}
else{debug('Friend has not changed',uid);}}
else{debug('Removing friend: ',aContact.id);toBeChanged++;removeFbFriend(aContact.id);}});debug('First pass of Updates and removed finished');var toBeUpdatedList=Object.keys(toBeUpdated);if(toBeUpdatedList.length>0){totalToChange=toBeChanged+toBeUpdatedList.length;debug('Starting worker for updating img data');startWorker();fb.utils.getCachedAccessToken(function(access_token){theWorker.postMessage({type:'startWithData',data:{access_token:access_token,uids:toBeUpdated,operationsTimeout:fb.operationsTimeout,targetPictureSize:utils.misc.getPreferredPictureDetail()}});});}
else{totalToChange=toBeChanged;checkTotals();}});};})();};'use strict';if(!window.ImageLoader){var ImageLoader=function ImageLoader(pContainer,pItems){var container,items,itemsSelector,lastScrollTime,scrollLatency=100,scrollTimer,itemHeight,total,imgsLoading=0,loadImage=defaultLoadImage,self=this;init(pContainer,pItems);function init(pContainer,pItems){itemsSelector=pItems;container=document.querySelector(pContainer);attachHandlers();window.addEventListener('image-loader-resume',resuming);window.addEventListener('image-loader-pause',unload);load();}
function resuming(){window.clearTimeout(scrollTimer);attachHandlers();update();}
function onUpdate(evt){evt.stopPropagation();onScroll();}
function load(){window.clearTimeout(scrollTimer);scrollTimer=null;items=container.querySelectorAll(itemsSelector);itemHeight=items[0]?items[0].offsetHeight:1;total=items.length;window.setTimeout(update,0);}
function attachHandlers(){container.addEventListener('scroll',onScroll);document.addEventListener('onupdate',onUpdate);}
function unload(){container.removeEventListener('scroll',onScroll);document.removeEventListener('onupdate',onUpdate);window.clearTimeout(scrollTimer);scrollTimer=null;}
function setResolver(pResolver){loadImage=pResolver;}
function onScroll(){if(imgsLoading>0){window.stop();imgsLoading=0;}
lastScrollTime=Date.now();if(!scrollTimer){scrollTimer=window.setTimeout(updateFromScroll,scrollLatency);}}
function updateFromScroll(){scrollTimer=null;var deltaLatency=lastScrollTime-Date.now()+scrollLatency;if(deltaLatency>0){scrollTimer=window.setTimeout(updateFromScroll,deltaLatency);}else{update();}}
function defaultLoadImage(item){var image=item.querySelector('span[data-type=img][data-src]');if(!image){image=item.querySelector('img[data-src]');if(!image){image=item.querySelector('span[data-type=img][data-group]');if(image){item.dataset.visited='true';}
return;}}
++imgsLoading;var tmp=new Image();var src=tmp.src=image.dataset.src;tmp.onload=function onload(){--imgsLoading;image.style.backgroundImage='url('+src+')';if(tmp.complete){item.dataset.visited='true';}
tmp=null;};tmp.onabort=tmp.onerror=function onerror(){--imgsLoading;item.dataset.visited='false';tmp=null;};}
function update(){if(total===0){return;}
var viewTop=container.scrollTop;var index=Math.floor(viewTop/itemHeight);var containerHeight=container.offsetHeight;for(var i=index;i>=0;i--){var item=items[i];if(item){if(item.offsetTop+itemHeight<viewTop){break;}
if(item.dataset.visited!=='true'&&item.offsetTop<=viewTop+containerHeight){loadImage(item,self);}}}
for(var j=index+1;j<total;j++){var theItem=items[j];if(!theItem){return;}
if(theItem.offsetTop>viewTop+containerHeight){return;}
if(theItem.dataset.visited!=='true'){loadImage(theItem,self);}}}
function releaseImage(item){var image=item.querySelector('span[data-type=img][data-src]');if(!image){image=item.querySelector('span[data-type=img][data-group]');if(image){item.dataset.visited='false';}
return null;}
image.style.backgroundImage='none';item.dataset.visited='false';return image;}
function destroy(){unload();window.removeEventListener('image-loader-pause',unload);window.removeEventListener('image-loader-resume',resuming);container=items=itemsSelector=lastScrollTime=scrollLatency=null;scrollTimer=itemHeight=total=imgsLoading=loadImage=null;}
this.reload=load;this.unload=unload;this.setResolver=setResolver;this.defaultLoad=defaultLoadImage;this.releaseImage=releaseImage;this.destroy=destroy;};};'use strict';var utils=window.utils||{};utils.status=(function(){var STATUS_TIME=2000;var statusMsg=document.querySelector('#statusMsg');var hidingTimeout;var transitionEndTimeout;var additionalLine;var hideAnimationDone=function(){statusMsg.removeEventListener('transitionend',hideAnimationDone);statusMsg.classList.add('hidden');};var showAnimationDone=function(){if(transitionEndTimeout){clearTimeout(transitionEndTimeout);}
statusMsg.removeEventListener('transitionend',showAnimationDone);hidingTimeout=setTimeout(utils.status.hide,STATUS_TIME);};var hideStatus=function(){statusMsg.addEventListener('transitionend',hideAnimationDone);statusMsg.classList.remove('opening');statusMsg.classList.remove('bannerStart');statusMsg.querySelector('p').removeAttribute('data-l10n-id');if(additionalLine){statusMsg.removeChild(additionalLine);additionalLine=null;}};var setL10nAttributes=function(node,l10n){if(l10n&&l10n.id){navigator.mozL10n.setAttributes(node,l10n.id,l10n.args);return true;}else{console.error('Status arguments must be objects');return false;}};var showStatus=function(mainMessage,extra){statusMsg.removeEventListener('transitionend',showAnimationDone);statusMsg.removeEventListener('transitionend',hideAnimationDone);LazyLoader.load([statusMsg],function _loaded(){if(!setL10nAttributes(statusMsg.querySelector('p'),mainMessage)){return;}
if(extra){additionalLine=document.createElement('p');statusMsg.appendChild(additionalLine);setL10nAttributes(additionalLine,extra);}
if(statusMsg.classList.contains('opening')){clearTimeout(hidingTimeout);hidingTimeout=setTimeout(hideStatus,STATUS_TIME);return;}
statusMsg.classList.remove('hidden');statusMsg.addEventListener('transitionend',showAnimationDone);setTimeout(function displaying(){statusMsg.classList.add('opening');statusMsg.classList.add('bannerStart');transitionEndTimeout=setTimeout(showAnimationDone,STATUS_TIME);},10);});};return{show:showStatus,hide:hideStatus};})();;'use strict';var utils=window.utils||{};if(!utils.dom){(function(document){var Dom=utils.dom={};Dom.removeChildNodes=function(node){while(node.hasChildNodes()){node.removeChild(node.firstChild);}};Dom.addClassToNodes=function(container,selector,clazz){var nodes=container.querySelectorAll(selector);for(var i=0,n=nodes.length;i<n;++i){nodes[i].classList.add(clazz);}};Dom.removeClassFromNodes=function(container,selector,clazz){var nodes=container.querySelectorAll(selector);for(var i=0,n=nodes.length;i<n;++i){nodes[i].classList.remove(clazz);}};Dom.updatePhoto=function(photo,dest){var background='';if(photo!=null){background='url('+URL.createObjectURL(photo)+')';}
dest.style.backgroundImage=background;dest.dataset.photoReady='true';};})(document);};'use strict';var fb=window.fb||{};if(!fb.link){(function(document){var link=fb.link={};var UI=link.ui={};link.CID_PARAM='contactid';var contactid;var cdata;var access_token;var numQueries=0;var friendUidToLink;var SEARCH_QUERY=['SELECT uid, name, email from user ',' WHERE uid IN (SELECT uid1 FROM friend WHERE uid2=me()) ',' AND (',null,')',' ORDER BY name'];var MAIL_COND,CELL_COND;MAIL_COND=['strpos(email, ',"'",null,"'",') >= 0'];CELL_COND=['strpos(cell, ',"'",null,"'",') >= 0'];var ALL_QUERY=['SELECT uid, name, last_name, first_name,',' middle_name, email from user ',' WHERE uid IN (SELECT uid1 FROM friend WHERE uid2=me()) ',' ORDER BY name'];var COUNT_QUERY='SELECT uid FROM user WHERE uid IN '+'(SELECT uid1 FROM friend WHERE uid2=me())';var SEARCH_ACCENTS_FIELDS={'last_name':'familyName','first_name':'givenName','middle_name':'givenName'};var friendsList;var viewButton,mainSection;var currentRecommendation=null;var allFriends=null;var currentNetworkRequest=null;var state;var imgLoader;var completedCb;function notifyParent(message){parent.postMessage({type:message.type||'',data:message.data||''},fb.CONTACTS_APP_ORIGIN);}
function buildQuery(contact){var filter=[];if(contact.tel&&contact.tel.length>0){contact.tel.forEach(function(tel){CELL_COND[2]=tel.value.trim();filter.push(CELL_COND.join(''));filter.push(' OR ');});}
if(contact.email&&contact.email.length>0){contact.email.forEach(function(email){MAIL_COND[2]=email.value.trim();filter.push(MAIL_COND.join(''));filter.push(' OR ');});}
if(filter.length>0){filter[filter.length-1]=null;}
var filterStr=filter.join('');var out;if(filterStr){SEARCH_QUERY[3]=filterStr;out=SEARCH_QUERY.join('');}
else{out=null;}
return out;}
link.getRemoteProposal=function(acc_tk,contid){var cid=contid||contactid;var req=fb.utils.getContactData(cid);req.onsuccess=function(){if(req.result){cdata=req.result;numQueries=1;currentRecommendation=null;var query=buildQuery(cdata);if(query===null){getRemoteProposalAll(acc_tk);return;}
doGetRemoteProposal(acc_tk,cdata,query);}
else{throw new Error('FB: Contact to be linked not found in mozContacts: '+cid);}};req.onerror=function(e){window.console.error('FB: Error while retrieving contact data: ',cid);throw e;};};function getRemoteProposalAll(acc_tk){numQueries++;doGetRemoteProposal(acc_tk,null,ALL_QUERY.join(''),true);}
function proposalReadyMultiple(done,error,response){if(response.error){done(response);return;}else if(!Array.isArray(response.data)){error({name:'QueryResponseError'});return;}
var friendList,totalFriends;if(response.data.length>0&&Array.isArray(response.data[0].fql_result_set)){friendList=response.data[0].fql_result_set;if(response.data[1]&&Array.isArray(response.data[1].fql_result_set)){totalFriends=response.data[1].fql_result_set.length;}}
else{friendList=response.data;totalFriends=friendList.length;}
if(typeof totalFriends!=='undefined'){fb.utils.setCachedNumFriends(totalFriends);}
done({data:friendList});}
function doGetRemoteProposal(acc_tk,contactData,query,isAll){var theQuery=query;var callback=proposalReadyMultiple.bind(null,fb.link.proposalReady,fb.link.errorHandler);if(!isAll){theQuery=JSON.stringify({query1:query,query2:COUNT_QUERY});}
state='proposal';currentNetworkRequest=fb.utils.runQuery(theQuery,{success:callback,error:fb.link.errorHandler,timeout:fb.link.timeoutHandler},acc_tk);}
function cancelCb(shouldNotifyParent){if(currentNetworkRequest){currentNetworkRequest.cancel();currentNetworkRequest=null;}
Curtain.hide(shouldNotifyParent?notifyParent.bind(null,{type:'abort'}):null);}
function getRemoteAll(){Curtain.oncancel=cancelCb;Curtain.show('wait','friends');state='friends';currentNetworkRequest=fb.utils.runQuery(ALL_QUERY.join(''),{success:fb.link.friendsReady,error:fb.link.errorHandler,timeout:fb.link.timeoutHandler},access_token);}
link.getProposal=function(cid,acc_tk){link.getRemoteProposal(acc_tk,cid);};link.proposalReady=function(response){if(typeof response.error!=='undefined'){window.console.error('FB: Error while retrieving link data',response.error.code,response.error.message);if(response.error.code!=190){setCurtainHandlersErrorProposal();Curtain.show('error','proposal');}
else{handleTokenError();}
return;}
if(response.data.length===0&&numQueries===1){getRemoteProposalAll(access_token);}
else{var data=response.data;currentRecommendation=data;var sortedByName=[];var numFriendsProposed=data.length;var searchAccentsArrays={};var index=0;data.forEach(function(item){if(!item.email){item.email='';}
var box=utils.misc.getPreferredPictureBox();item.picwidth=box.width;item.picheight=box.height;if(numQueries===2){sortedByName.push(item);Object.keys(SEARCH_ACCENTS_FIELDS).forEach(function(field){searchAccentsArrays[field]=searchAccentsArrays[field]||[];if(item[field]){var words=item[field].split(/[ ]+/);words.push(item[field]);words.forEach(function(word){var obj={originalIndex:index};obj[field]=Normalizer.toAscii(word).toLowerCase();searchAccentsArrays[field].push(obj);});}});index++;}});if(numQueries===2){var accentsProposal=searchAccents(searchAccentsArrays,cdata);if(accentsProposal.length===0){data=sortedByName;mainSection.classList.add('no-proposal');numFriendsProposed=0;}
else{currentRecommendation=[];accentsProposal.forEach(function(proposalIndex){currentRecommendation.push(sortedByName[proposalIndex]);});numFriendsProposed=currentRecommendation.length;}}else{viewButton.setAttribute('data-l10n-id','viewAll');viewButton.onclick=UI.viewAllFriends;}
utils.templates.append('#friends-list',currentRecommendation);imgLoader.reload();if(typeof completedCb==='function'){completedCb();}
Curtain.hide(function onCurtainHide(){sendReadyEvent();window.addEventListener('message',function linkOnViewPort(e){if(e.origin!==fb.CONTACTS_APP_ORIGIN){return;}
var data=e.data;if(data&&data.type==='dom_transition_end'){window.removeEventListener('message',linkOnViewPort);utils.status.show({id:'linkProposal',args:{numFriends:numFriendsProposed}});}});});}};function searchAccents(searchArrays,contactData){var out=[];var searchFields=Object.keys(SEARCH_ACCENTS_FIELDS);function compareItems(target,item){var out;if(typeof target==='string'){out=target.localeCompare(item);if(out!==0){if(item.indexOf(target)===0){out=0;}}}
return out;}
searchFields.forEach(function(searchField){var searchArray=searchArrays[searchField].sort(function(a,b){return a[searchField].localeCompare(b[searchField]);});var fieldToSearch=contactData[SEARCH_ACCENTS_FIELDS[searchField]];if(fieldToSearch&&fieldToSearch[0]){var dataToSearch=fieldToSearch[0].trim().split(/[ ]+/);dataToSearch.forEach(function(aData){var targetString=Normalizer.toAscii(aData).toLowerCase();var searchResult=utils.binarySearch(targetString,searchArray,{arrayField:searchField,compareFunction:compareItems});searchResult.forEach(function(aResult){var candidate=searchArray[aResult].originalIndex;if(out.indexOf(candidate)===-1){out.push(candidate);}});});}});return out;}
function sendReadyEvent(){parent.postMessage({type:'ready',data:''},fb.CONTACTS_APP_ORIGIN);}
function setCurtainHandlersErrorProposal(){Curtain.oncancel=function(){cancelCb(true);};Curtain.onretry=function getRemoteProposal(){Curtain.oncancel=function(){cancelCb(true);};Curtain.show('wait',state);link.getRemoteProposal(access_token,contactid);};}
link.baseHandler=function(type){if(state==='friends'){Curtain.onretry=getRemoteAll;Curtain.oncancel=Curtain.hide;}else if(state==='proposal'){setCurtainHandlersErrorProposal();}
else if(state==='linking'){Curtain.onretry=function retry_link(){Curtain.show('message','linking');UI.selected({target:{dataset:{uuid:friendUidToLink}}});};Curtain.oncancel=Curtain.hide;}
Curtain.show(type,state);};link.timeoutHandler=function(){link.baseHandler('timeout');};link.errorHandler=function(){link.baseHandler('error');};function clearList(){if(!friendsList){friendsList=document.querySelector('#friends-list');}
var template=friendsList.querySelector('[data-template]');utils.dom.removeChildNodes(friendsList);friendsList.appendChild(template);}
link.friendsReady=function(response){if(typeof response.error==='undefined'&&response.data){viewButton.setAttribute('data-l10n-id','viewRecommend');viewButton.onclick=UI.viewRecommended;allFriends=response;fb.utils.setCachedNumFriends(response.data.length);response.data.forEach(function(item){if(!item.email){item.email='';}});clearList();var fragment=document.createDocumentFragment();utils.templates.append(friendsList,response.data,fragment);friendsList.appendChild(fragment);imgLoader.reload();Curtain.hide();}
else{window.console.error('FB: Error while retrieving friends',response.error.code,response.error.message);if(response.error.code!==190){Curtain.onretry=getRemoteAll;Curtain.oncancel=Curtain.hide;}
else{Curtain.onretry=handleTokenError;}
Curtain.show('error','friends');}};function setCurtainHandlers(){Curtain.oncancel=function curtain_cancel(){cancelCb(true);};}
link.start=function(contactId,acc_tk,endCb){completedCb=endCb;access_token=acc_tk;contactid=contactId;setCurtainHandlers();clearList();imgLoader=new ImageLoader('#mainContent','li:not([data-uuid="#uid#"])');if(!acc_tk){oauth2.getAccessToken(function proposal_new_token(new_acc_tk){access_token=new_acc_tk;link.getProposal(contactId,new_acc_tk);},'proposal','facebook');}
else{link.getProposal(contactId,acc_tk);}};link.init=function(){mainSection=document.querySelector('#main');viewButton=document.querySelector('#view-all');};function retryOnErrorCb(){UI.selected({target:{dataset:{uuid:friendUidToLink}}});}
function handleTokenError(){Curtain.hide(notifyParent.bind(null,{type:'token_error'}));var cb=function(){allFriends=null;link.start(contactid);};ImportStatusData.remove(fb.utils.TOKEN_DATA_KEY).then(cb);}
UI.selected=function(event){Curtain.show('message','linking');var element=event.target.parentNode;friendUidToLink=element.dataset.uuid;var req=fb.contacts.get(friendUidToLink);req.onsuccess=function(){if(req.result){window.setTimeout(function delay(){Curtain.hide(notifyParent.bind(null,{type:'item_selected',data:{uid:friendUidToLink}}));},1000);}
else{state='linking';var callbacks={};callbacks.success=function(data){Curtain.hide(notifyParent.bind(null,{type:'item_selected',data:data}));};callbacks.error=function(e){var error=e.target.error;window.console.error('FB: Error while importing friend data ',JSON.stringify(error));Curtain.oncancel=Curtain.hide;if(error.code!=190){Curtain.onretry=retryOnErrorCb;}
else{Curtain.onretry=handleTokenError;}
Curtain.show('error','linking');};callbacks.timeout=function(){link.baseHandler('timeout');};imgLoader.unload();FacebookConnector.importContact(friendUidToLink,access_token,callbacks,'not_match');}};req.onerror=function(){window.console.error('FB: Error while importing friend data');Curtain.oncancel=Curtain.hide;Curtain.onretry=retryOnErrorCb;Curtain.show('error','linking');};};UI.end=function(event){var msg={type:'window_close',data:''};parent.postMessage(msg,fb.CONTACTS_APP_ORIGIN);};UI.viewAllFriends=function(event){if(!allFriends){getRemoteAll();}
else{link.friendsReady(allFriends);}
return false;};UI.viewRecommended=function(event){event.target.onclick=UI.viewAllFriends;event.target.setAttribute('data-l10n-id','viewAll');clearList();utils.templates.append(friendsList,currentRecommendation);imgLoader.reload();return false;};})(document);};'use strict';(function(document){var cid=window.location.search.substring(fb.link.CID_PARAM.length+2);if(parent.fb){fb.operationsTimeout=parent.fb.operationsTimeout;}
utils.listeners.add({'#link-header':[{event:'action',handler:fb.link.ui.end}],'#friends-list':fb.link.ui.selected});fb.link.init();document.querySelector('#view-all').onclick=fb.link.ui.viewAllFriends;fb.contacts.init(function fb_init(){window.addEventListener('message',function getAccessToken(e){if(e.origin!==fb.CONTACTS_APP_ORIGIN){return;}
window.removeEventListener('message',getAccessToken);fb.link.start(cid,e.data.data);});parent.postMessage({type:'messaging_ready',data:''},fb.CONTACTS_APP_ORIGIN);});})(document);;;;;;;window.COMPONENTS_BASE_URL='/shared/elements/';;;;;;(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.GaiaHeader=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){;(function(define){define(function(require,exports,module){'use strict';var debug=0?console.log.bind(console):function(){};var cache={};var MIN=16;var MAX=24;var BUFFER=3;module.exports=function(config){debug('font fit',config);var space=config.space-BUFFER;var min=config.min||MIN;var max=config.max||MAX;var text=trim(config.text);var fontSize=max;var textWidth;var font;do{font=config.font.replace(/\d+px/,fontSize+'px');textWidth=getTextWidth(text,font);}while(textWidth>space&&fontSize!==min&&fontSize--);return{textWidth:textWidth,fontSize:fontSize,overflowing:textWidth>space};};function getTextWidth(text,font){var ctx=getCanvasContext(font);var width=ctx.measureText(text).width;debug('got text width',width);return width;}
function getCanvasContext(font){debug('get canvas context',font);var cached=cache[font];if(cached){return cached;}
var canvas=document.createElement('canvas');canvas.setAttribute('moz-opaque','true');canvas.setAttribute('width','1px');canvas.setAttribute('height','1px');debug('created canvas',canvas);var ctx=canvas.getContext('2d',{willReadFrequently:true});ctx.font=font;return cache[font]=ctx;}
function trim(text){return text.replace(/\s+/g,' ').trim();}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('font-fit',this));},{}],2:[function(require,module,exports){;(function(define){'use strict';define(function(require,exports,module){var textContent=Object.getOwnPropertyDescriptor(Node.prototype,'textContent');var innerHTML=Object.getOwnPropertyDescriptor(Element.prototype,'innerHTML');var removeAttribute=Element.prototype.removeAttribute;var setAttribute=Element.prototype.setAttribute;var noop=function(){};exports.register=function(name,props){var baseProto=getBaseProto(props.extends);var template=props.template||baseProto.templateString;var extensible=props.extensible=props.hasOwnProperty('extensible')?props.extensible:true;delete props.extends;if(template){if(extensible&&props.template){props.templateString=props.template;}
var output=processCss(template,name);props.template=document.createElement('template');props.template.innerHTML=output.template;props.lightCss=output.lightCss;props.globalCss=props.globalCss||'';props.globalCss+=output.globalCss;}
injectGlobalCss(props.globalCss);delete props.globalCss;var descriptors=mixin(props.attrs||{},base.descriptors);props._attrs=props.attrs;delete props.attrs;var proto=createProto(baseProto,props);Object.defineProperties(proto,descriptors);try{return document.registerElement(name,{prototype:proto});}catch(e){if(e.name!=='NotSupportedError'){throw e;}}};var base={properties:{GaiaComponent:true,attributeChanged:noop,attached:noop,detached:noop,created:noop,createdCallback:function(){if(this.dirObserver){addDirObserver();}
injectLightCss(this);this.created();},attributeChangedCallback:function(name,from,to){var prop=toCamelCase(name);if(this._attrs&&this._attrs[prop]){this[prop]=to;}
this.attributeChanged(name,from,to);},attachedCallback:function(){if(this.dirObserver){this.setInnerDirAttributes=setInnerDirAttributes.bind(null,this);document.addEventListener('dirchanged',this.setInnerDirAttributes);}
this.attached();},detachedCallback:function(){if(this.dirObserver){document.removeEventListener('dirchanged',this.setInnerDirAttributes);}
this.detached();},setupShadowRoot:function(){if(!this.template){return;}
var node=document.importNode(this.template.content,true);this.createShadowRoot().appendChild(node);if(this.dirObserver){setInnerDirAttributes(this);}
return this.shadowRoot;},setAttr:function(name,value){var internal=this.shadowRoot.firstElementChild;setAttribute.call(internal,name,value);setAttribute.call(this,name,value);},removeAttr:function(name){var internal=this.shadowRoot.firstElementChild;removeAttribute.call(internal,name);removeAttribute.call(this,name);}},descriptors:{textContent:{set:function(value){textContent.set.call(this,value);if(this.lightStyle){this.appendChild(this.lightStyle);}},get:function(){return textContent.get();}},innerHTML:{set:function(value){innerHTML.set.call(this,value);if(this.lightStyle){this.appendChild(this.lightStyle);}},get:innerHTML.get}}};var defaultPrototype=createProto(HTMLElement.prototype,base.properties);function getBaseProto(proto){if(!proto){return defaultPrototype;}
proto=proto.prototype||proto;return!proto.GaiaComponent?createProto(proto,base.properties):proto;}
function createProto(proto,props){return mixin(Object.create(proto),props);}
var hasShadowCSS=(function(){var div=document.createElement('div');try{div.querySelector(':host');div.querySelector('::content');return true;}
catch(e){return false;}})();var regex={shadowCss:/(?:\:host|\:\:content)[^{]*\{[^}]*\}/g,':host':/(?:\:host)/g,':host()':/\:host\((.+)\)(?: \:\:content)?/g,':host-context':/\:host-context\((.+)\)([^{,]+)?/g,'::content':/(?:\:\:content)/g};function processCss(template,name){var globalCss='';var lightCss='';if(!hasShadowCSS){template=template.replace(regex.shadowCss,function(match){var hostContext=regex[':host-context'].exec(match);if(hostContext){globalCss+=match.replace(regex['::content'],'').replace(regex[':host-context'],'$1 '+name+'$2').replace(/ +/g,' ');}else{lightCss+=match.replace(regex[':host()'],name+'$1').replace(regex[':host'],name).replace(regex['::content'],name);}
return'';});}
return{template:template,lightCss:lightCss,globalCss:globalCss};}
function injectGlobalCss(css){if(!css){return;}
var style=document.createElement('style');style.innerHTML=css.trim();headReady().then(function(){document.head.appendChild(style);});}
function headReady(){return new Promise(function(resolve){if(document.head){return resolve();}
window.addEventListener('load',function fn(){window.removeEventListener('load',fn);resolve();});});}
function injectLightCss(el){if(hasShadowCSS){return;}
var stylesheet=el.querySelector('style');if(!stylesheet){stylesheet=document.createElement('style');stylesheet.setAttribute('scoped','');stylesheet.appendChild(document.createTextNode(el.lightCss));el.appendChild(stylesheet);}
el.lightStyle=stylesheet;}
function toCamelCase(string){return string.replace(/-(.)/g,function replacer(string,p1){return p1.toUpperCase();});}
var dirObserver;function setInnerDirAttributes(component){var dir=component.dir||document.dir;Array.from(component.shadowRoot.children).forEach(element=>{if(element.nodeName!=='STYLE'){element.dir=dir;}});}
function addDirObserver(){if(dirObserver){return;}
dirObserver=new MutationObserver(onChanged);dirObserver.observe(document.documentElement,{attributeFilter:['dir'],attributes:true});function onChanged(mutations){document.dispatchEvent(new Event('dirchanged'));}}
function mixin(target,source){for(var key in source){target[key]=source[key];}
return target;}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-component',this));},{}],3:[function(require,module,exports){(function(define){'use strict';define(function(require,exports,module){var base=window.GAIA_ICONS_BASE_URL||window.COMPONENTS_BASE_URL||'bower_components/';if(!document.documentElement){window.addEventListener('load',load);}else{load();}
function load(){if(isLoaded()){return;}
var link=document.createElement('link');link.rel='stylesheet';link.type='text/css';link.href=base+'gaia-icons/gaia-icons.css';document.head.appendChild(link);exports.loaded=true;}
function isLoaded(){return exports.loaded||document.querySelector('link[href*=gaia-icons]')||document.documentElement.classList.contains('gaia-icons-loaded');}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-icons',this));},{}],4:[function(require,module,exports){;(function(define){'use strict';define(function(require,exports,module){var component=require('gaia-component');var fontFit=require('font-fit');require('gaia-icons');var debug=0?console.log.bind(console):function(){};const KNOWN_ACTIONS={menu:'menu',back:'back',close:'close'};const TITLE_FONT='italic 300 24px FiraSans';const TITLE_PADDING=10;const MINIMUM_FONT_SIZE_CENTERED=20;const MINIMUM_FONT_SIZE_UNCENTERED=16;const MAXIMUM_FONT_SIZE=23;module.exports=component.register('gaia-header',{extensible:false,dirObserver:true,created:function(){debug('created');this.setupShadowRoot();this.els={actionButton:this.shadowRoot.querySelector('.action-button'),titles:this.getElementsByTagName('h1')};this.els.actionButton.addEventListener('click',e=>this.onActionButtonClick(e));this.observer=new MutationObserver(this.onMutation.bind(this));this.ignoreDir=this.hasAttribute('ignore-dir');this.titleEnd=this.getAttribute('title-end');this.titleStart=this.getAttribute('title-start');this.noFontFit=this.getAttribute('no-font-fit');this.notFlush=this.hasAttribute('not-flush');this.action=this.getAttribute('action');this.unresolved={};this.pending={};this._resizeThrottlingId=null;this.onResize=this.onResize.bind(this);},attached:function(){debug('attached');this.runFontFitSoon();this.observerStart();window.addEventListener('resize',this.onResize);},detached:function(){debug('detached');window.removeEventListener('resize',this.onResize);this.observerStop();this.clearPending();},clearPending:function(){for(var key in this.pending){this.pending[key].clear();delete this.pending[key];}
window.cancelAnimationFrame(this._resizeThrottlingId);this._resizeThrottlingId=null;},runFontFit:function(){debug('run font-fit');if(this.noFontFit){return Promise.resolve();}
var titles=this.els.titles;var space=this.getTitleSpace();var styles=[].map.call(titles,el=>this.getTitleStyle(el,space));return this.setTitleStylesSoon(styles);},runFontFitSoon:function(){debug('run font-fit soon');if(this.pending.runFontFitSoon){return;}
this.pending.runFontFitSoon=this.nextTick(()=>{delete this.pending.runFontFitSoon;this.runFontFit();});},getTitleStyle:function(el,space){debug('get el style',el,space);var text=el.textContent;var styleId=space.start+text+space.end+'#'+space.value;if(!text||!text.trim()){return debug('exit: no text');}
if(getStyleId(el)===styleId){return debug('exit: no change');}
var marginStart=this.getTitleMarginStart();var textSpace=space.value-Math.abs(marginStart);var fontFitResult=this.fontFit(text,textSpace,{min:MINIMUM_FONT_SIZE_CENTERED});var overflowing=fontFitResult.overflowing;var padding={start:0,end:0};if(overflowing){debug('title overflowing');padding.start=!space.start?TITLE_PADDING:0;padding.end=!space.end?TITLE_PADDING:0;textSpace=space.value-padding.start-padding.end;fontFitResult=this.fontFit(text,textSpace);marginStart=0;}
return{id:styleId,fontSize:fontFitResult.fontSize,marginStart:marginStart,overflowing:overflowing,padding:padding};},setTitleStylesSoon:function(styles){debug('set title styles soon',styles);var key='setStyleTitlesSoon';this._titleStyles=styles;if(this.unresolved[key]){return this.unresolved[key];}
this.unresolved[key]=new Promise((resolve)=>{this.pending[key]=this.nextTick(()=>{var styles=this._titleStyles;var els=this.els.titles;[].forEach.call(els,(el,i)=>{if(!styles[i]){return debug('exit');}
this.setTitleStyle(el,styles[i]);});delete this._titleStyles;delete this.unresolved[key];delete this.pending[key];resolve();});});},setTitleStyle:function(el,style){debug('set title style',style);this.observerStop();if(this.ignoreDir){el.style.marginLeft=style.marginStart+'px';el.style.paddingLeft=style.padding.start+'px';el.style.paddingRight=style.padding.end+'px';}else{el.style.marginInlineStart=style.marginStart+'px';el.style.paddingInlineStart=style.padding.start+'px';el.style.paddingInlineEnd=style.padding.end+'px';}
el.style.fontSize=style.fontSize+'px';setStyleId(el,style.id);this.observerStart();},fontFit:function(text,space,opts={}){debug('font fit:',text,space,opts);var fontFitArgs={font:TITLE_FONT,min:opts.min||MINIMUM_FONT_SIZE_UNCENTERED,max:MAXIMUM_FONT_SIZE,text:text,space:space};return fontFit(fontFitArgs);},observerStart:function(){if(this.observing){return;}
this.observer.observe(this,{childList:true,attributes:true,subtree:true});this.observing=true;debug('observer started');},observerStop:function(){if(!this.observing){return;}
this.observer.disconnect();this.observing=false;debug('observer stopped');},onResize:function(e){debug('onResize',this._resizeThrottlingId);if(this._resizeThrottlingId!==null){return;}
this._resizeThrottlingId=window.requestAnimationFrame(()=>{this._resizeThrottlingId=null;this.runFontFitSoon();});},onMutation:function(mutations){debug('on mutation',mutations);if(!this.pending.runFontFitSoon){this.runFontFit();}},getTitleSpace:function(){var start=this.titleStart;var end=this.titleEnd;var space=this.getWidth()-start-end;var result={value:space,start:start,end:end};debug('get title space',result);return result;},getWidth:function(){var value=this.notFlush?this.clientWidth:window.innerWidth;debug('get width',value);return value;},triggerAction:function(){if(this.action){this.els.actionButton.click();}},onActionButtonClick:function(){debug('action button click');var config={detail:{type:this.action}};var e=new CustomEvent('action',config);setTimeout(()=>this.dispatchEvent(e));},getTitleMarginStart:function(){var start=this.titleStart;var end=this.titleEnd;var marginStart=end-start;debug('get title margin start',marginStart);return marginStart;},getButtonsBeforeTitle:function(){var children=this.children;var l=children.length;var els=[];for(var i=0;i<l;i++){var el=children[i];if(el.tagName==='H1'){break;}
if(!contributesToLayout(el)){continue;}
els.push(el);}
if(this.action){els.push(this.els.actionButton);}
return els;},getButtonsAfterTitle:function(){var children=this.children;var els=[];for(var i=children.length-1;i>=0;i--){var el=children[i];if(el.tagName==='H1'){break;}
if(!contributesToLayout(el)){continue;}
els.push(el);}
return els;},sumButtonWidths:function(buttons){var defaultWidth=50;var sum=buttons.reduce((prev,button)=>{var isStandardButton=button===this.els.actionButton;var width=isStandardButton?defaultWidth:button.clientWidth;return prev+width;},0);debug('sum button widths',buttons,sum);return sum;},attrs:{action:{get:function(){return this._action;},set:function(value){var action=KNOWN_ACTIONS[value];if(action===this._action){return;}
this.setAttr('action',action);this._action=action;}},titleStart:{get:function(){debug('get title-start');if('_titleStart'in this){return this._titleStart;}
var buttons=this.getButtonsBeforeTitle();var value=this.sumButtonWidths(buttons);debug('get title-start',buttons,value);return value;},set:function(value){debug('set title-start',value);value=parseInt(value,10);if(value===this._titleStart||isNaN(value)){return;}
this.setAttr('title-start',value);this._titleStart=value;debug('set');}},titleEnd:{get:function(){debug('get title-end');if('_titleEnd'in this){return this._titleEnd;}
var buttons=this.getButtonsAfterTitle();return this.sumButtonWidths(buttons);},set:function(value){debug('set title-end',value);value=parseInt(value,10);if(value===this._titleEnd||isNaN(value)){return;}
this.setAttr('title-end',value);this._titleEnd=value;}},noFontFit:{get:function(){return this._noFontFit||false;},set:function(value){debug('set no-font-fit',value);value=!!(value||value==='');if(value===this.noFontFit){return;}
this._noFontFit=value;if(value){this.setAttr('no-font-fit','');}
else{this.removeAttr('no-font-fit');}}},ignoreDir:{get:function(){return this._ignoreDir||false;},set:function(value){debug('set ignore-dir',value);value=!!(value||value==='');if(value===this.ignoreDir){return;}
this._ignoreDir=value;this.dirObserver=!value;if(value){this.setAttr('ignore-dir','');}
else{this.removeAttr('ignore-dir');}}}},template:`<div class="inner">
<button class="action-button">
<content select="[l10n-action]"></content>
</button>
<content></content>
</div>
<style>
:host {
display: block;
-moz-user-select: none;
--gaia-header-button-color:
var(--header-button-color,
var(--header-color,
var(--link-color,
inherit)));
}
/**
* [hidden]
*/
:host[hidden] {
display: none;
}
/** Reset
---------------------------------------------------------*/
::-moz-focus-inner { border: 0; }
/** Inner
---------------------------------------------------------*/
.inner {
display: flex;
min-height: 50px;
-moz-user-select: none;
background:
var(--header-background,
var(--background,
#fff));
}
/** Action Button
---------------------------------------------------------*/
/**
* 1. Hidden by default
*/
.action-button {
position: relative;
display: none; /* 1 */
width: 50px;
font-size: 30px;
margin: 0;
padding: 0;
border: 0;
outline: 0;
align-items: center;
background: none;
cursor: pointer;
transition: opacity 200ms 280ms;
color:
var(--header-action-button-color,
var(--header-icon-color,
var(--gaia-header-button-color)));
}
/**
* [action=back]
* [action=menu]
* [action=close]
*
* 1. For icon vertical-alignment
*/
[action=back] .action-button,
[action=menu] .action-button,
[action=close] .action-button {
display: flex; /* 1 */
}
/**
* :active
*/
.action-button:active {
transition: none;
opacity: 0.2;
}
/** Action Button Icon
---------------------------------------------------------*/
.action-button:before {
font-family: 'gaia-icons';
font-style: normal;
text-rendering: optimizeLegibility;
font-weight: 500;
}
[action=close] .action-button:before { content: 'close' }
[action=menu] .action-button:before { content: 'menu' }
[action=back]:-moz-dir(ltr) .action-button:before { content: 'left' }
[action=back]:-moz-dir(rtl) .action-button:before { content: 'right' }
/** Action Button Icon
---------------------------------------------------------*/
/**
* 1. To enable vertical alignment.
*/
.action-button:before {
display: block;
}
/** Action Button Text
---------------------------------------------------------*/
/**
* To provide custom localized content for
* the action-button, we allow the user
* to provide an element with the class
* .l10n-action. This node is then
* pulled inside the real action-button.
*
* Example:
*
* <gaia-header action="back">
* <span l10n-action aria-label="Back">Localized text</span>
* <h1>title</h1>
* </gaia-header>
*/
::content [l10n-action] {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
font-size: 0;
}
/** Title
---------------------------------------------------------*/
/**
* 1. Vertically center text. We can't use flexbox
* here as it breaks text-overflow ellipsis
* without an inner div.
*/
::content h1 {
flex: 1;
margin: 0;
padding: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
line-height: 50px; /* 1 */
font-weight: 300;
font-style: italic;
font-size: 24px;
color:
var(--header-title-color,
var(--header-color,
var(--title-color,
var(--text-color,
inherit))));
}
/**
* [ignore-dir]
*
* When the <gaia-header> component has an [ignore-dir] attribute, header
* direction is forced to LTR but we still want the <h1> text to be reversed
* so that strings like '1 selected' become 'selected 1'.
*
* When we're happy for <gaia-header> to be fully RTL responsive we won't need
* these rules anymore, but this depends on all Gaia apps being ready.
*
* This should be safe to remove when bug 1179459 lands.
*/
:host[ignore-dir] {
direction: ltr;
}
:host[ignore-dir]:-moz-dir(rtl) h1 {
direction: rtl;
}
/** Buttons
---------------------------------------------------------*/
::content a,
::content button {
position: relative;
z-index: 1;
box-sizing: border-box;
display: flex;
width: auto;
height: auto;
min-width: 50px;
margin: 0;
padding: 0 10px;
outline: 0;
border: 0;
font-size: 14px;
line-height: 1;
align-items: center;
justify-content: center;
text-decoration: none;
text-align: center;
background: none;
border-radius: 0;
font-style: italic;
cursor: pointer;
transition: opacity 200ms 280ms;
color: var(--gaia-header-button-color);
}
/**
* :active
*/
::content a:active,
::content button:active {
transition: none;
opacity: 0.2;
}
/**
* [hidden]
*/
::content a[hidden],
::content button[hidden] {
display: none;
}
/**
* [disabled]
*/
::content a[disabled],
::content button[disabled] {
pointer-events: none;
color: var(--header-disabled-button-color);
}
/** Icon Buttons
---------------------------------------------------------*/
/**
* Icons are a different color to text
*/
::content .icon,
::content [data-icon] {
color:
var(--header-icon-color,
var(--gaia-header-button-color));
}
/**
* If users want their action button
* to be in the component's light-dom
* they can add an .action class
* to make it look like the
* shadow action button.
*/
::content .action {
color:
var(--header-action-button-color,
var(--header-icon-color,
var(--gaia-header-button-color)));
}
/**
* [data-icon]:empty
*
* Icon buttons with no textContent,
* should always be 50px.
*
* This is to prevent buttons being
* larger than they should be before
* icon-font has loaded.
*/
::content [data-icon]:empty {
width: 50px;
}
</style>`,nextTick:nextTick});function contributesToLayout(el){return el.localName!=='style'&&!el.hasAttribute('l10n-action');}
function setStyleId(el,id){el._styleId=id;}
function getStyleId(el){return el._styleId;}
function nextTick(fn){var cleared;Promise.resolve().then(()=>{if(!cleared){fn();}});return{clear:function(){cleared=true;}};}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-header',this));},{"font-fit":1,"gaia-component":2,"gaia-icons":3}]},{},[4])(4)});

View file

@ -0,0 +1,440 @@
;window.COMPONENTS_BASE_URL='/shared/elements/';;(function(exports){'use strict';exports.ComponentUtils={style:function(baseUrl){if(window.VulpesCompat){window.VulpesCompat.componentStyle(this,baseUrl).catch(console.error);return;}var style=document.createElement('style');var url=baseUrl+'style.css';var self=this;style.setAttribute('scoped','');style.innerHTML='@import url('+url+');';this.appendChild(style);this.style.visibility='hidden';style.addEventListener('load',function(){if(self.shadowRoot){self.shadowRoot.appendChild(style.cloneNode(true));}
self.style.visibility='';});}};}(window));;'use strict';window.GaiaSubheader=(function(win){var proto=Object.create(HTMLElement.prototype);var baseurl=window.GaiaSubheaderBaseurl||'/shared/elements/gaia_subheader/';proto.createdCallback=function(){ComponentUtils.style.call(this,baseurl);};return document.registerElement('gaia-subheader',{prototype:proto});})(window);;(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.GaiaHeader=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){;(function(define){define(function(require,exports,module){'use strict';var debug=0?console.log.bind(console):function(){};var cache={};var MIN=16;var MAX=24;var BUFFER=3;module.exports=function(config){debug('font fit',config);var space=config.space-BUFFER;var min=config.min||MIN;var max=config.max||MAX;var text=trim(config.text);var fontSize=max;var textWidth;var font;do{font=config.font.replace(/\d+px/,fontSize+'px');textWidth=getTextWidth(text,font);}while(textWidth>space&&fontSize!==min&&fontSize--);return{textWidth:textWidth,fontSize:fontSize,overflowing:textWidth>space};};function getTextWidth(text,font){var ctx=getCanvasContext(font);var width=ctx.measureText(text).width;debug('got text width',width);return width;}
function getCanvasContext(font){debug('get canvas context',font);var cached=cache[font];if(cached){return cached;}
var canvas=document.createElement('canvas');canvas.setAttribute('moz-opaque','true');canvas.setAttribute('width','1px');canvas.setAttribute('height','1px');debug('created canvas',canvas);var ctx=canvas.getContext('2d',{willReadFrequently:true});ctx.font=font;return cache[font]=ctx;}
function trim(text){return text.replace(/\s+/g,' ').trim();}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('font-fit',this));},{}],2:[function(require,module,exports){;(function(define){'use strict';define(function(require,exports,module){var textContent=Object.getOwnPropertyDescriptor(Node.prototype,'textContent');var innerHTML=Object.getOwnPropertyDescriptor(Element.prototype,'innerHTML');var removeAttribute=Element.prototype.removeAttribute;var setAttribute=Element.prototype.setAttribute;var noop=function(){};exports.register=function(name,props){var baseProto=getBaseProto(props.extends);var template=props.template||baseProto.templateString;var extensible=props.extensible=props.hasOwnProperty('extensible')?props.extensible:true;delete props.extends;if(template){if(extensible&&props.template){props.templateString=props.template;}
var output=processCss(template,name);props.template=document.createElement('template');props.template.innerHTML=output.template;props.lightCss=output.lightCss;props.globalCss=props.globalCss||'';props.globalCss+=output.globalCss;}
injectGlobalCss(props.globalCss);delete props.globalCss;var descriptors=mixin(props.attrs||{},base.descriptors);props._attrs=props.attrs;delete props.attrs;var proto=createProto(baseProto,props);Object.defineProperties(proto,descriptors);try{return document.registerElement(name,{prototype:proto});}catch(e){if(e.name!=='NotSupportedError'){throw e;}}};var base={properties:{GaiaComponent:true,attributeChanged:noop,attached:noop,detached:noop,created:noop,createdCallback:function(){if(this.dirObserver){addDirObserver();}
injectLightCss(this);this.created();},attributeChangedCallback:function(name,from,to){var prop=toCamelCase(name);if(this._attrs&&this._attrs[prop]){this[prop]=to;}
this.attributeChanged(name,from,to);},attachedCallback:function(){if(this.dirObserver){this.setInnerDirAttributes=setInnerDirAttributes.bind(null,this);document.addEventListener('dirchanged',this.setInnerDirAttributes);}
this.attached();},detachedCallback:function(){if(this.dirObserver){document.removeEventListener('dirchanged',this.setInnerDirAttributes);}
this.detached();},setupShadowRoot:function(){if(!this.template){return;}
var node=document.importNode(this.template.content,true);this.createShadowRoot().appendChild(node);if(this.dirObserver){setInnerDirAttributes(this);}
return this.shadowRoot;},setAttr:function(name,value){var internal=this.shadowRoot.firstElementChild;setAttribute.call(internal,name,value);setAttribute.call(this,name,value);},removeAttr:function(name){var internal=this.shadowRoot.firstElementChild;removeAttribute.call(internal,name);removeAttribute.call(this,name);}},descriptors:{textContent:{set:function(value){textContent.set.call(this,value);if(this.lightStyle){this.appendChild(this.lightStyle);}},get:function(){return textContent.get();}},innerHTML:{set:function(value){innerHTML.set.call(this,value);if(this.lightStyle){this.appendChild(this.lightStyle);}},get:innerHTML.get}}};var defaultPrototype=createProto(HTMLElement.prototype,base.properties);function getBaseProto(proto){if(!proto){return defaultPrototype;}
proto=proto.prototype||proto;return!proto.GaiaComponent?createProto(proto,base.properties):proto;}
function createProto(proto,props){return mixin(Object.create(proto),props);}
var hasShadowCSS=(function(){var div=document.createElement('div');try{div.querySelector(':host');div.querySelector('::content');return true;}
catch(e){return false;}})();var regex={shadowCss:/(?:\:host|\:\:content)[^{]*\{[^}]*\}/g,':host':/(?:\:host)/g,':host()':/\:host\((.+)\)(?: \:\:content)?/g,':host-context':/\:host-context\((.+)\)([^{,]+)?/g,'::content':/(?:\:\:content)/g};function processCss(template,name){var globalCss='';var lightCss='';if(!hasShadowCSS){template=template.replace(regex.shadowCss,function(match){var hostContext=regex[':host-context'].exec(match);if(hostContext){globalCss+=match.replace(regex['::content'],'').replace(regex[':host-context'],'$1 '+name+'$2').replace(/ +/g,' ');}else{lightCss+=match.replace(regex[':host()'],name+'$1').replace(regex[':host'],name).replace(regex['::content'],name);}
return'';});}
return{template:template,lightCss:lightCss,globalCss:globalCss};}
function injectGlobalCss(css){if(!css){return;}
var style=document.createElement('style');style.innerHTML=css.trim();headReady().then(function(){document.head.appendChild(style);});}
function headReady(){return new Promise(function(resolve){if(document.head){return resolve();}
window.addEventListener('load',function fn(){window.removeEventListener('load',fn);resolve();});});}
function injectLightCss(el){if(hasShadowCSS){return;}
var stylesheet=el.querySelector('style');if(!stylesheet){stylesheet=document.createElement('style');stylesheet.setAttribute('scoped','');stylesheet.appendChild(document.createTextNode(el.lightCss));el.appendChild(stylesheet);}
el.lightStyle=stylesheet;}
function toCamelCase(string){return string.replace(/-(.)/g,function replacer(string,p1){return p1.toUpperCase();});}
var dirObserver;function setInnerDirAttributes(component){var dir=component.dir||document.dir;Array.from(component.shadowRoot.children).forEach(element=>{if(element.nodeName!=='STYLE'){element.dir=dir;}});}
function addDirObserver(){if(dirObserver){return;}
dirObserver=new MutationObserver(onChanged);dirObserver.observe(document.documentElement,{attributeFilter:['dir'],attributes:true});function onChanged(mutations){document.dispatchEvent(new Event('dirchanged'));}}
function mixin(target,source){for(var key in source){target[key]=source[key];}
return target;}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-component',this));},{}],3:[function(require,module,exports){(function(define){'use strict';define(function(require,exports,module){var base=window.GAIA_ICONS_BASE_URL||window.COMPONENTS_BASE_URL||'bower_components/';if(!document.documentElement){window.addEventListener('load',load);}else{load();}
function load(){if(isLoaded()){return;}
var link=document.createElement('link');link.rel='stylesheet';link.type='text/css';link.href=base+'gaia-icons/gaia-icons.css';document.head.appendChild(link);exports.loaded=true;}
function isLoaded(){return exports.loaded||document.querySelector('link[href*=gaia-icons]')||document.documentElement.classList.contains('gaia-icons-loaded');}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-icons',this));},{}],4:[function(require,module,exports){;(function(define){'use strict';define(function(require,exports,module){var component=require('gaia-component');var fontFit=require('font-fit');require('gaia-icons');var debug=0?console.log.bind(console):function(){};const KNOWN_ACTIONS={menu:'menu',back:'back',close:'close'};const TITLE_FONT='italic 300 24px FiraSans';const TITLE_PADDING=10;const MINIMUM_FONT_SIZE_CENTERED=20;const MINIMUM_FONT_SIZE_UNCENTERED=16;const MAXIMUM_FONT_SIZE=23;module.exports=component.register('gaia-header',{extensible:false,dirObserver:true,created:function(){debug('created');this.setupShadowRoot();this.els={actionButton:this.shadowRoot.querySelector('.action-button'),titles:this.getElementsByTagName('h1')};this.els.actionButton.addEventListener('click',e=>this.onActionButtonClick(e));this.observer=new MutationObserver(this.onMutation.bind(this));this.ignoreDir=this.hasAttribute('ignore-dir');this.titleEnd=this.getAttribute('title-end');this.titleStart=this.getAttribute('title-start');this.noFontFit=this.getAttribute('no-font-fit');this.notFlush=this.hasAttribute('not-flush');this.action=this.getAttribute('action');this.unresolved={};this.pending={};this._resizeThrottlingId=null;this.onResize=this.onResize.bind(this);},attached:function(){debug('attached');this.runFontFitSoon();this.observerStart();window.addEventListener('resize',this.onResize);},detached:function(){debug('detached');window.removeEventListener('resize',this.onResize);this.observerStop();this.clearPending();},clearPending:function(){for(var key in this.pending){this.pending[key].clear();delete this.pending[key];}
window.cancelAnimationFrame(this._resizeThrottlingId);this._resizeThrottlingId=null;},runFontFit:function(){debug('run font-fit');if(this.noFontFit){return Promise.resolve();}
var titles=this.els.titles;var space=this.getTitleSpace();var styles=[].map.call(titles,el=>this.getTitleStyle(el,space));return this.setTitleStylesSoon(styles);},runFontFitSoon:function(){debug('run font-fit soon');if(this.pending.runFontFitSoon){return;}
this.pending.runFontFitSoon=this.nextTick(()=>{delete this.pending.runFontFitSoon;this.runFontFit();});},getTitleStyle:function(el,space){debug('get el style',el,space);var text=el.textContent;var styleId=space.start+text+space.end+'#'+space.value;if(!text||!text.trim()){return debug('exit: no text');}
if(getStyleId(el)===styleId){return debug('exit: no change');}
var marginStart=this.getTitleMarginStart();var textSpace=space.value-Math.abs(marginStart);var fontFitResult=this.fontFit(text,textSpace,{min:MINIMUM_FONT_SIZE_CENTERED});var overflowing=fontFitResult.overflowing;var padding={start:0,end:0};if(overflowing){debug('title overflowing');padding.start=!space.start?TITLE_PADDING:0;padding.end=!space.end?TITLE_PADDING:0;textSpace=space.value-padding.start-padding.end;fontFitResult=this.fontFit(text,textSpace);marginStart=0;}
return{id:styleId,fontSize:fontFitResult.fontSize,marginStart:marginStart,overflowing:overflowing,padding:padding};},setTitleStylesSoon:function(styles){debug('set title styles soon',styles);var key='setStyleTitlesSoon';this._titleStyles=styles;if(this.unresolved[key]){return this.unresolved[key];}
this.unresolved[key]=new Promise((resolve)=>{this.pending[key]=this.nextTick(()=>{var styles=this._titleStyles;var els=this.els.titles;[].forEach.call(els,(el,i)=>{if(!styles[i]){return debug('exit');}
this.setTitleStyle(el,styles[i]);});delete this._titleStyles;delete this.unresolved[key];delete this.pending[key];resolve();});});},setTitleStyle:function(el,style){debug('set title style',style);this.observerStop();if(this.ignoreDir){el.style.marginLeft=style.marginStart+'px';el.style.paddingLeft=style.padding.start+'px';el.style.paddingRight=style.padding.end+'px';}else{el.style.marginInlineStart=style.marginStart+'px';el.style.paddingInlineStart=style.padding.start+'px';el.style.paddingInlineEnd=style.padding.end+'px';}
el.style.fontSize=style.fontSize+'px';setStyleId(el,style.id);this.observerStart();},fontFit:function(text,space,opts={}){debug('font fit:',text,space,opts);var fontFitArgs={font:TITLE_FONT,min:opts.min||MINIMUM_FONT_SIZE_UNCENTERED,max:MAXIMUM_FONT_SIZE,text:text,space:space};return fontFit(fontFitArgs);},observerStart:function(){if(this.observing){return;}
this.observer.observe(this,{childList:true,attributes:true,subtree:true});this.observing=true;debug('observer started');},observerStop:function(){if(!this.observing){return;}
this.observer.disconnect();this.observing=false;debug('observer stopped');},onResize:function(e){debug('onResize',this._resizeThrottlingId);if(this._resizeThrottlingId!==null){return;}
this._resizeThrottlingId=window.requestAnimationFrame(()=>{this._resizeThrottlingId=null;this.runFontFitSoon();});},onMutation:function(mutations){debug('on mutation',mutations);if(!this.pending.runFontFitSoon){this.runFontFit();}},getTitleSpace:function(){var start=this.titleStart;var end=this.titleEnd;var space=this.getWidth()-start-end;var result={value:space,start:start,end:end};debug('get title space',result);return result;},getWidth:function(){var value=this.notFlush?this.clientWidth:window.innerWidth;debug('get width',value);return value;},triggerAction:function(){if(this.action){this.els.actionButton.click();}},onActionButtonClick:function(){debug('action button click');var config={detail:{type:this.action}};var e=new CustomEvent('action',config);setTimeout(()=>this.dispatchEvent(e));},getTitleMarginStart:function(){var start=this.titleStart;var end=this.titleEnd;var marginStart=end-start;debug('get title margin start',marginStart);return marginStart;},getButtonsBeforeTitle:function(){var children=this.children;var l=children.length;var els=[];for(var i=0;i<l;i++){var el=children[i];if(el.tagName==='H1'){break;}
if(!contributesToLayout(el)){continue;}
els.push(el);}
if(this.action){els.push(this.els.actionButton);}
return els;},getButtonsAfterTitle:function(){var children=this.children;var els=[];for(var i=children.length-1;i>=0;i--){var el=children[i];if(el.tagName==='H1'){break;}
if(!contributesToLayout(el)){continue;}
els.push(el);}
return els;},sumButtonWidths:function(buttons){var defaultWidth=50;var sum=buttons.reduce((prev,button)=>{var isStandardButton=button===this.els.actionButton;var width=isStandardButton?defaultWidth:button.clientWidth;return prev+width;},0);debug('sum button widths',buttons,sum);return sum;},attrs:{action:{get:function(){return this._action;},set:function(value){var action=KNOWN_ACTIONS[value];if(action===this._action){return;}
this.setAttr('action',action);this._action=action;}},titleStart:{get:function(){debug('get title-start');if('_titleStart'in this){return this._titleStart;}
var buttons=this.getButtonsBeforeTitle();var value=this.sumButtonWidths(buttons);debug('get title-start',buttons,value);return value;},set:function(value){debug('set title-start',value);value=parseInt(value,10);if(value===this._titleStart||isNaN(value)){return;}
this.setAttr('title-start',value);this._titleStart=value;debug('set');}},titleEnd:{get:function(){debug('get title-end');if('_titleEnd'in this){return this._titleEnd;}
var buttons=this.getButtonsAfterTitle();return this.sumButtonWidths(buttons);},set:function(value){debug('set title-end',value);value=parseInt(value,10);if(value===this._titleEnd||isNaN(value)){return;}
this.setAttr('title-end',value);this._titleEnd=value;}},noFontFit:{get:function(){return this._noFontFit||false;},set:function(value){debug('set no-font-fit',value);value=!!(value||value==='');if(value===this.noFontFit){return;}
this._noFontFit=value;if(value){this.setAttr('no-font-fit','');}
else{this.removeAttr('no-font-fit');}}},ignoreDir:{get:function(){return this._ignoreDir||false;},set:function(value){debug('set ignore-dir',value);value=!!(value||value==='');if(value===this.ignoreDir){return;}
this._ignoreDir=value;this.dirObserver=!value;if(value){this.setAttr('ignore-dir','');}
else{this.removeAttr('ignore-dir');}}}},template:`<div class="inner">
<button class="action-button">
<content select="[l10n-action]"></content>
</button>
<content></content>
</div>
<style>
:host {
display: block;
-moz-user-select: none;
--gaia-header-button-color:
var(--header-button-color,
var(--header-color,
var(--link-color,
inherit)));
}
/**
* [hidden]
*/
:host[hidden] {
display: none;
}
/** Reset
---------------------------------------------------------*/
::-moz-focus-inner { border: 0; }
/** Inner
---------------------------------------------------------*/
.inner {
display: flex;
min-height: 50px;
-moz-user-select: none;
background:
var(--header-background,
var(--background,
#fff));
}
/** Action Button
---------------------------------------------------------*/
/**
* 1. Hidden by default
*/
.action-button {
position: relative;
display: none; /* 1 */
width: 50px;
font-size: 30px;
margin: 0;
padding: 0;
border: 0;
outline: 0;
align-items: center;
background: none;
cursor: pointer;
transition: opacity 200ms 280ms;
color:
var(--header-action-button-color,
var(--header-icon-color,
var(--gaia-header-button-color)));
}
/**
* [action=back]
* [action=menu]
* [action=close]
*
* 1. For icon vertical-alignment
*/
[action=back] .action-button,
[action=menu] .action-button,
[action=close] .action-button {
display: flex; /* 1 */
}
/**
* :active
*/
.action-button:active {
transition: none;
opacity: 0.2;
}
/** Action Button Icon
---------------------------------------------------------*/
.action-button:before {
font-family: 'gaia-icons';
font-style: normal;
text-rendering: optimizeLegibility;
font-weight: 500;
}
[action=close] .action-button:before { content: 'close' }
[action=menu] .action-button:before { content: 'menu' }
[action=back]:-moz-dir(ltr) .action-button:before { content: 'left' }
[action=back]:-moz-dir(rtl) .action-button:before { content: 'right' }
/** Action Button Icon
---------------------------------------------------------*/
/**
* 1. To enable vertical alignment.
*/
.action-button:before {
display: block;
}
/** Action Button Text
---------------------------------------------------------*/
/**
* To provide custom localized content for
* the action-button, we allow the user
* to provide an element with the class
* .l10n-action. This node is then
* pulled inside the real action-button.
*
* Example:
*
* <gaia-header action="back">
* <span l10n-action aria-label="Back">Localized text</span>
* <h1>title</h1>
* </gaia-header>
*/
::content [l10n-action] {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
font-size: 0;
}
/** Title
---------------------------------------------------------*/
/**
* 1. Vertically center text. We can't use flexbox
* here as it breaks text-overflow ellipsis
* without an inner div.
*/
::content h1 {
flex: 1;
margin: 0;
padding: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
line-height: 50px; /* 1 */
font-weight: 300;
font-style: italic;
font-size: 24px;
color:
var(--header-title-color,
var(--header-color,
var(--title-color,
var(--text-color,
inherit))));
}
/**
* [ignore-dir]
*
* When the <gaia-header> component has an [ignore-dir] attribute, header
* direction is forced to LTR but we still want the <h1> text to be reversed
* so that strings like '1 selected' become 'selected 1'.
*
* When we're happy for <gaia-header> to be fully RTL responsive we won't need
* these rules anymore, but this depends on all Gaia apps being ready.
*
* This should be safe to remove when bug 1179459 lands.
*/
:host[ignore-dir] {
direction: ltr;
}
:host[ignore-dir]:-moz-dir(rtl) h1 {
direction: rtl;
}
/** Buttons
---------------------------------------------------------*/
::content a,
::content button {
position: relative;
z-index: 1;
box-sizing: border-box;
display: flex;
width: auto;
height: auto;
min-width: 50px;
margin: 0;
padding: 0 10px;
outline: 0;
border: 0;
font-size: 14px;
line-height: 1;
align-items: center;
justify-content: center;
text-decoration: none;
text-align: center;
background: none;
border-radius: 0;
font-style: italic;
cursor: pointer;
transition: opacity 200ms 280ms;
color: var(--gaia-header-button-color);
}
/**
* :active
*/
::content a:active,
::content button:active {
transition: none;
opacity: 0.2;
}
/**
* [hidden]
*/
::content a[hidden],
::content button[hidden] {
display: none;
}
/**
* [disabled]
*/
::content a[disabled],
::content button[disabled] {
pointer-events: none;
color: var(--header-disabled-button-color);
}
/** Icon Buttons
---------------------------------------------------------*/
/**
* Icons are a different color to text
*/
::content .icon,
::content [data-icon] {
color:
var(--header-icon-color,
var(--gaia-header-button-color));
}
/**
* If users want their action button
* to be in the component's light-dom
* they can add an .action class
* to make it look like the
* shadow action button.
*/
::content .action {
color:
var(--header-action-button-color,
var(--header-icon-color,
var(--gaia-header-button-color)));
}
/**
* [data-icon]:empty
*
* Icon buttons with no textContent,
* should always be 50px.
*
* This is to prevent buttons being
* larger than they should be before
* icon-font has loaded.
*/
::content [data-icon]:empty {
width: 50px;
}
</style>`,nextTick:nextTick});function contributesToLayout(el){return el.localName!=='style'&&!el.hasAttribute('l10n-action');}
function setStyleId(el,id){el._styleId=id;}
function getStyleId(el){return el._styleId;}
function nextTick(fn){var cleared;Promise.resolve().then(()=>{if(!cleared){fn();}});return{clear:function(){cleared=true;}};}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-header',this));},{"font-fit":1,"gaia-component":2,"gaia-icons":3}]},{},[4])(4)});;;'use strict';var LazyLoader=(function(){function LazyLoader(){this._loaded={};this._isLoading={};}
LazyLoader.prototype={_js:function(file,callback){var script=document.createElement('script');script.src=file;script.async=false;script.addEventListener('load',callback);document.head.appendChild(script);this._isLoading[file]=script;},_css:function(file,callback){var style=document.createElement('link');style.type='text/css';style.rel='stylesheet';style.href=file;document.head.appendChild(style);callback();},_html:function(domNode,callback){if(domNode.getAttribute('is')){this.load(['/shared/js/html_imports.js'],function(){HtmlImports.populate(callback);}.bind(this));return;}
for(var i=0;i<domNode.childNodes.length;i++){if(domNode.childNodes[i].nodeType==document.COMMENT_NODE){domNode.innerHTML=domNode.childNodes[i].nodeValue;break;}}
window.dispatchEvent(new CustomEvent('lazyload',{detail:domNode}));callback();},getJSON:function(file,mozSystem){return new Promise(function(resolve,reject){var xhr;if(mozSystem){xhr=new XMLHttpRequest({mozSystem:true});}else{xhr=new XMLHttpRequest();}
xhr.open('GET',file,true);xhr.responseType='json';xhr.onerror=function(error){reject(error);};xhr.onload=function(){if(xhr.response!==null){resolve(xhr.response);}else{reject(new Error('No valid JSON object was found ('+
xhr.status+' '+xhr.statusText+')'));}};xhr.send();});},load:function(files,callback){var deferred={};deferred.promise=new Promise(resolve=>{deferred.resolve=resolve;});if(!Array.isArray(files)){files=[files];}
var loadsRemaining=files.length,self=this;function perFileCallback(file){if(self._isLoading[file]){delete self._isLoading[file];}
self._loaded[file]=true;if(--loadsRemaining===0){deferred.resolve();if(callback){callback();}}}
for(var i=0;i<files.length;i++){var file=files[i];if(this._loaded[file.id||file]){perFileCallback(file);}else if(this._isLoading[file]){this._isLoading[file].addEventListener('load',perFileCallback.bind(null,file));}else{var method,idx;if(typeof file==='string'){method=file.match(/\.([^.]+)$/)[1];idx=file;}else{method='html';idx=file.id;}
this['_'+method](file,perFileCallback.bind(null,idx));}}
return deferred.promise;}};return new LazyLoader();}());;'use strict';(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;})();;'use strict';(function(){const CACHE_ENABLED=true;const FIRST_CHUNK='firstChunk';const l10nStrings={'title':{query:'gaia-header h1#app-title[data-l10n-id="contacts"]',container:'textContent'},'search':{query:'input[data-l10n-id="search-contact"]',container:'placeholder'}};var _observers={};function addObserver(aMessage,aCallback){if(typeof aCallback!=='function'){return;}
if(!_observers[aMessage]){_observers[aMessage]=[];}
_observers[aMessage].push(aCallback);}
function notifyObservers(aMessage,aData){var observers=_observers[aMessage];if(!observers){return;}
for(var callback in observers){if(observers[callback]){observers[callback](aData);}}}
var _caches=new Map();_caches.set(FIRST_CHUNK,{containerId:'groups-list',active:false});var _cachedContacts;var _cachedFavorites;var _cachedHeaders;var _cacheEvictionTimer;function setCache(aCache){if(!CACHE_ENABLED||!aCache.id||!aCache.content||!aCache.lastOrderString||!_caches.has(aCache.id)){return;}
var l10n={};Object.keys(l10nStrings).forEach(key=>{var string=l10nStrings[key];var selector=document.querySelector(string.query);if(!selector||!selector[string.container]){return;}
l10n[key]=selector[string.container];});localStorage.setItem(aCache.id,JSON.stringify({content:aCache.content,languageDirection:navigator.mozL10n.language.direction,languageCode:navigator.mozL10n.language.code,lastOrderString:aCache.lastOrderString,updated:Date.now(),l10n:l10n}));}
function deleteCache(aId){if(!CACHE_ENABLED){return;}
localStorage.removeItem(aId);}
function getCachedContacts(aNodeList){if(!CACHE_ENABLED||!aNodeList){return;}
if(!_cachedContacts){_cachedContacts=new Map();}
if(!_cachedFavorites){_cachedFavorites=new Map();}
for(var i=0;i<aNodeList.length;i++){var cacheEntry=aNodeList[i];if(cacheEntry.parentNode.id=='contact-list-ice'){continue;}
if(cacheEntry.parentNode.id=='contacts-list-favorites'){_cachedFavorites.set(cacheEntry.dataset.uuid,cacheEntry.innerHTML);continue;}
_cachedContacts.set(cacheEntry.dataset.uuid,cacheEntry.innerHTML);}}
function appendNodesToContainer(aContainer,aNodeList){if(!CACHE_ENABLED||!aNodeList.length){return Promise.resolve();}
return new Promise((resolve)=>{if(!_cachedHeaders){_cachedHeaders={};}
var resolved=false;var fragment=document.createDocumentFragment();aNodeList.forEach((node)=>{if(!node.elementName){return;}
var child=document.createElement(node.elementName);node.attributes&&node.attributes.forEach((attribute)=>{if(!attribute.name||!attribute.value){return;}
child.setAttribute(attribute.name,attribute.value);});if(node.innerHTML){child.innerHTML=node.innerHTML;}
fragment.appendChild(child);var headerName=child.id.split('section-group-')[1];_cachedHeaders[headerName]=fragment.querySelector('ol#contacts-list-'+headerName);if(!resolved){resolve();}});getCachedContacts(fragment.querySelectorAll('li[data-cache=true]'));aContainer.appendChild(fragment);});}
function removeCacheFromContainer(aContainer){if(!CACHE_ENABLED||!_cachedContacts){return;}
for(var id of _cachedContacts.keys()){var items=aContainer.querySelectorAll('li[data-uuid=\"'+id+'\"]');Array.prototype.forEach.call(items,(item)=>{var ol=item.parentNode;ol.removeChild(item);if(ol.children.length<1){var header=_cachedHeaders[ol.dataset.group];if(header){var groupTitle=header.parentNode.children[0];groupTitle.classList.add('hide');}}});}}
function undoCache(aCacheId){if(!CACHE_ENABLED||!_caches.has(aCacheId)){return;}
var _cache=_caches.get(aCacheId);if(!_cache.containerId){return;}
var container=document.getElementById(_cache.containerId);if(!container){console.warning('Could not remove cached content from '+
_cache.containerId);return;}
removeCacheFromContainer(container);}
var Cache={get active(){return CACHE_ENABLED&&_caches.get(FIRST_CHUNK).active;},get enabled(){return CACHE_ENABLED;},get contacts(){return(CACHE_ENABLED&&_cachedContacts)?_cachedContacts.keys():null;},get favorites(){return(CACHE_ENABLED&&_cachedFavorites)?_cachedFavorites.keys():null;},get length(){return(CACHE_ENABLED&&_cachedContacts)?_cachedContacts.size+_cachedFavorites.size:0;},get headers(){return(CACHE_ENABLED&&_cachedHeaders)?_cachedHeaders:{};},get lastOrderString(){return(CACHE_ENABLED&&_caches.get(FIRST_CHUNK).lastOrderString);},get updated(){return(CACHE_ENABLED&&_caches.get(FIRST_CHUNK).updated);},get _rawContent(){return _caches.get(FIRST_CHUNK).content;},set firstChunk(aFirstChunk){if(!CACHE_ENABLED||!aFirstChunk||!aFirstChunk.cache||!aFirstChunk.lastOrderString){return;}
setCache({id:FIRST_CHUNK,content:aFirstChunk.cache,lastOrderString:aFirstChunk.lastOrderString});},set oneviction(aCallback){if(!CACHE_ENABLED){return;}
addObserver('oneviction',aCallback);},hasContact:function(aUuid){if(!CACHE_ENABLED){return;}
return _cachedContacts&&_cachedContacts.has(aUuid);},hasFavorite:function(aUuid){if(!CACHE_ENABLED){return;}
return _cachedFavorites&&_cachedFavorites.has(aUuid);},getContact:function(aUuid){if(!CACHE_ENABLED||!this.hasContact(aUuid)){return;}
var contact=_cachedContacts.get(aUuid);_cachedContacts.delete(aUuid);return contact;},getFavorite:function(aUuid){if(!CACHE_ENABLED||!this.hasFavorite(aUuid)){return;}
var contact=_cachedFavorites.get(aUuid);_cachedFavorites.delete(aUuid);return contact;},removeContact:function(aUuid){if(!CACHE_ENABLED||!this.hasContact(aUuid)||!this.hasFavorite(aUuid)){return;}
if(_cachedContacts.has(aUuid)){_cachedContacts.delete(aUuid);}
if(_cachedFavorites.has(aUuid)){_cachedFavorites.delete(aUuid);}},_doEvict:function(aUndo){_cacheEvictionTimer=null;if(aUndo){undoCache(FIRST_CHUNK);}
deleteCache(FIRST_CHUNK);this.cleanup();notifyObservers('oneviction');},evict:function(aUndo,aInstant){if(!CACHE_ENABLED){return;}
if(aInstant){this._doEvict(aUndo);return;}
if(_cacheEvictionTimer){clearTimeout(_cacheEvictionTimer);}
_cacheEvictionTimer=setTimeout(()=>{this._doEvict(aUndo);},1000);},maybeEvict:function(){if(!CACHE_ENABLED||!this.active){return;}
if((_caches.get(FIRST_CHUNK).languageDirection!=navigator.mozL10n.language.direction)||(_caches.get(FIRST_CHUNK).languageCode!=navigator.mozL10n.language.code)){this.evict(true);}},cleanup:function(){if(!CACHE_ENABLED){return;}
_caches.get(FIRST_CHUNK).active=false;_caches.get(FIRST_CHUNK).content=null;_cachedContacts=null;_cachedFavorites=null;_cachedHeaders=null;},apply:function(aCacheId){if(!CACHE_ENABLED||!_caches.has(aCacheId)){return Promise.reject();}
var _cache=_caches.get(aCacheId);var cache=localStorage.getItem(aCacheId);if(!cache){return Promise.reject();}
try{cache=JSON.parse(cache);_cache.content=cache.content;_cache.languageDirection=cache.languageDirection;_cache.languageCode=cache.languageCode;_cache.lastOrderString=cache.lastOrderString;_cache.updated=new Date(cache.updated);}catch(e){console.error(e);return Promise.reject();}
if(!_cache.containerId){return Promise.reject();}
if(cache.l10n){Object.keys(cache.l10n).forEach(key=>{if(!l10nStrings[key]||cache.l10n[key]==='undefined'){return;}
var string=l10nStrings[key];var selector=document.querySelector(string.query);if(!selector){return;}
selector[string.container]=cache.l10n[key];});}
if(cache.languageDirection){var html=document.querySelector('html');html.setAttribute('dir',cache.languageDirection);}
var container=document.getElementById(_cache.containerId);if(!container){console.warning('Could not apply cached content to '+
_cache.containerId);return Promise.reject();}
return appendNodesToContainer(container,_cache.content).then(()=>{_cache.content=null;_cache.active=true;_caches.set(aCacheId,_cache);return Promise.resolve();});}};window.Cache=Cache;})();;'use strict';(function(exports){function loadScripts(){var dependencies=['/contacts/services/contacts.js','/contacts/js/activities.js','/shared/js/contacts/utilities/event_listeners.js','/contacts/js/navigation.js','/contacts/js/main_navigation.js','/contacts/js/views/list.js','/contacts/js/header_ui.js'];if(!Cache.active){dependencies.push('/shared/js/l10n.js');}
LazyLoader.load(dependencies,()=>{['/shared/js/async_storage.js','/shared/js/contacts/import/utilities/config.js','/contacts/js/utilities/extract_params.js','/contacts/js/utilities/cookie.js','/shared/js/contact_photo_helper.js'].forEach((src)=>{var scriptNode=document.createElement('script');scriptNode.src=src;scriptNode.setAttribute('defer',true);document.head.appendChild(scriptNode);});return LazyLoader.load('/contacts/js/contacts.js');});}
window.addEventListener('DOMContentLoaded',function ondomloaded(){utils.PerformanceHelper.domLoaded();window.removeEventListener('DOMContentLoaded',ondomloaded);var firstPaintFired=false;Cache.apply('firstChunk').then(()=>{document.getElementById('view-contacts-list').classList.add('current');utils.PerformanceHelper.visuallyComplete();firstPaintFired=true;});window.onload=()=>{if(!firstPaintFired){utils.PerformanceHelper.visuallyComplete();}
loadScripts();};});})(window);

View file

@ -0,0 +1,284 @@
;;this.asyncStorage=(function(){'use strict';var DBNAME='asyncStorage';var DBVERSION=1;var STORENAME='keyvaluepairs';var db=null;function withDatabase(f){if(db){f();}else{var openreq=indexedDB.open(DBNAME,DBVERSION);openreq.onerror=function withStoreOnError(){console.error('asyncStorage: can\'t open database:',openreq.error.name);};openreq.onupgradeneeded=function withStoreOnUpgradeNeeded(){openreq.result.createObjectStore(STORENAME);};openreq.onsuccess=function withStoreOnSuccess(){db=openreq.result;f();};}}
function withStore(type,callback,oncomplete){withDatabase(function(){var transaction=db.transaction(STORENAME,type);if(oncomplete){transaction.oncomplete=oncomplete;}
callback(transaction.objectStore(STORENAME));});}
function getItem(key,callback){var req;withStore('readonly',function getItemBody(store){req=store.get(key);req.onerror=function getItemOnError(){console.error('Error in asyncStorage.getItem(): ',req.error.name);};},function onComplete(){var value=req.result;if(value===undefined){value=null;}
callback(value);});}
function setItem(key,value,callback){withStore('readwrite',function setItemBody(store){var req=store.put(value,key);req.onerror=function setItemOnError(){console.error('Error in asyncStorage.setItem(): ',req.error.name);};},callback);}
function removeItem(key,callback){withStore('readwrite',function removeItemBody(store){var req=store.delete(key);req.onerror=function removeItemOnError(){console.error('Error in asyncStorage.removeItem(): ',req.error.name);};},callback);}
function clear(callback){withStore('readwrite',function clearBody(store){var req=store.clear();req.onerror=function clearOnError(){console.error('Error in asyncStorage.clear(): ',req.error.name);};},callback);}
function length(callback){var req;withStore('readonly',function lengthBody(store){req=store.count();req.onerror=function lengthOnError(){console.error('Error in asyncStorage.length(): ',req.error.name);};},function onComplete(){callback(req.result);});}
function key(n,callback){if(n<0){callback(null);return;}
var req;withStore('readonly',function keyBody(store){var advanced=false;req=store.openCursor();req.onsuccess=function keyOnSuccess(){var cursor=req.result;if(!cursor){return;}
if(n===0||advanced){return;}
advanced=true;cursor.advance(n);};req.onerror=function keyOnError(){console.error('Error in asyncStorage.key(): ',req.error.name);};},function onComplete(){var cursor=req.result;callback(cursor?cursor.key:null);});}
return{getItem:getItem,setItem:setItem,removeItem:removeItem,clear:clear,length:length,key:key};}());;'use strict';if(!window.ImportStatusData){var ImportStatusData=(function(){var store=null;var DS_NAME='Import_Status_Data';var datastoreLoading=false;var datastoreLoaded=false;var DS_LOADED_EVENT='datastore_loaded';var getDatastore=function getDatastore(){return new Promise(function(resolve,reject){if(!navigator.getDataStores){reject({name:'DatastoreNotEnabled'});return;}
if(datastoreLoaded){resolve(store);return;}
if(datastoreLoading){document.addEventListener(DS_LOADED_EVENT,function loadedHandler(){document.removeEventListener(DS_LOADED_EVENT,loadedHandler);resolve(store);});}
datastoreLoading=true;navigator.getDataStores(DS_NAME).then(function(stores){store=stores[0];},reject).then(function(){datastoreLoading=false;datastoreLoaded=true;document.dispatchEvent(new CustomEvent(DS_LOADED_EVENT));resolve(store);},reject);});};var put=function put(key,obj){return new Promise(function(resolve,reject){getDatastore().then(function success(store){store.put(obj,key).then(resolve,reject);},reject);});};var remove=function remove(key){return new Promise(function(resolve,reject){getDatastore().then(function success(store){store.remove(key).then(resolve,reject);},reject);});};var get=function get(key){return new Promise(function(resolve,reject){getDatastore().then(function success(store){store.get(key).then(resolve,reject);},reject);});};var clear=function clear(){return new Promise(function(resolve,reject){getDatastore().then(function success(store){store.clear().then(resolve,reject);},reject);});};return{put:put,remove:remove,get:get,clear:clear};})();};'use strict';var Normalizer={initAsciiNormalizer:function normalizer_init(){var equivalentChars={'a':'áăǎâäȧạȁàảȃāąåḁⱥãǽǣæ','A':'ÁĂǍÂÄȦẠȀÀẢȂĀĄÅḀȺÃǼǢÆ','b':'ḃḅɓḇƀƃ','B':'ḂḄƁḆɃƂ','c':'ćčçĉċƈȼ','C':'ĆČÇĈĊƇȻ','d':'ďḑḓḋḍɗḏđƌð','D':'ĎḐḒḊḌƊḎĐƋ','e':'éĕěȩêḙëėẹȅèẻȇēę','E':'ÉĔĚȨÊḘËĖẸȄÈẺȆĒĘ','f':'ḟƒ','F':'ḞƑ','g':'ǵğǧģĝġɠḡǥ','G':'ǴĞǦĢĜĠƓḠǤ','h':'ḫȟḩĥⱨḧḣḥħ','H':'ḪȞḨĤⱧḦḢḤĦ','i':'íĭǐîïịȉìỉȋīįɨĩḭı','I':'ÍĬǏÎÏỊȈÌỈȊĪĮƗĨḬ','j':'ĵɉ','J':'ĴɈ','k':'ḱǩķⱪꝃḳƙḵꝁ','K':'ḰǨĶⱩꝂḲƘḴꝀ','l':'ĺƚľļḽḷⱡꝉḻŀɫł','L':'ĹȽĽĻḼḶⱠꝈḺĿⱢŁ','m':'ḿṁṃɱ','M':'ḾṀṂⱮ','n':'ńňņṋṅṇǹɲṉƞñ','N':'ŃŇŅṊṄṆǸƝṈȠÑ','o':'óŏǒôöȯọőȍòỏơȏꝋꝍōǫøõœ','O':'ÓŎǑÔÖȮỌŐȌÒỎƠȎꝊꝌŌǪØÕŒ','p':'ṕṗꝓƥᵽꝑ','P':'ṔṖꝒƤⱣꝐ','q':'ꝗ','Q':'Ꝗ','r':'ŕřŗṙṛȑȓṟɍɽ','R':'ŔŘŖṘṚȐȒṞɌⱤ','s':'śšşŝșṡṣß','S':'ŚŠŞŜȘṠṢ','t':'ťţṱțⱦṫṭƭṯʈŧ','T':'ŤŢṰȚȾṪṬƬṮƮŦ','u':'úŭǔûṷüṳụűȕùủưȗūųůũṵ','U':'ÚŬǓÛṶÜṲỤŰȔÙỦƯȖŪŲŮŨṴ','v':'ṿʋṽ','V':'ṾƲṼ','w':'ẃŵẅẇẉẁⱳ','W':'ẂŴẄẆẈẀⱲ','x':'ẍẋ','X':'ẌẊ','y':'ýŷÿẏỵỳƴỷỿȳɏỹ','Y':'ÝŶŸẎỴỲƳỶỾȲɎỸ','z':'źžẑⱬżẓȥẕƶ','Z':'ŹŽẐⱫŻẒȤẔƵ'};this._toAsciiForm={};for(var letter in equivalentChars){var accentedForms=equivalentChars[letter];for(var i=accentedForms.length-1;i>=0;i--){this._toAsciiForm[accentedForms[i]]=letter;}}},toAscii:function normalizer_toAscii(str){if(!str||typeof str!='string'){return'';}
if(!this._toAsciiForm){Normalizer.initAsciiNormalizer();}
var result='';for(var i=0,len=str.length;i<len;i++){result+=this._toAsciiForm[str.charAt(i)]||str.charAt(i);}
return result;},escapeHTML:function normalizer_escapeHTML(str,escapeQuotes){if(Array.isArray(str)){return Normalizer.escapeHTML(str.join(' '),escapeQuotes);}
if(!str||typeof str!='string'){return'';}
var escaped=str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');if(escapeQuotes){return escaped.replace(/"/g,'&quot;').replace(/'/g,'&#x27;');}
return escaped;},unescapeHTML:function normalizer_unescapeHTML(text){return text.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&#x27;/g,'\'');},escapeRegExp:function normalizer_escapeRegExp(str){return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,'\\$&');}};;'use strict';var utils=window.utils||{};utils.binarySearch=function(target,array,options){var arrayField=options.arrayField,transformFunction=options.transformFunction,compareFunction=options.compareFunction;function getItem(array,index){var item=array[index];if(arrayField){item=item[arrayField];if(typeof transformFunction==='function'){item=transformFunction(item);}}
return item;}
function compare(target,item){var out;if(typeof compareFunction==='function'){out=compareFunction(target,item);}
else{if(typeof target==='string'){out=target.localeCompare(item);}
else{out=target.toString().localeCompare(item);}}
return out;}
var from=options.from;if(typeof from==='undefined'){from=0;}
var to=options.to;if(typeof to==='undefined'){to=array.length-1;}
if(to<from){return[];}
var middleIndex=Math.floor((to-from)/2);var item=getItem(array,from+middleIndex);var compareResult=compare(target,item);if(compareResult===0){var results=[from+middleIndex];var next=from+middleIndex+1;var finish=false;while(next<=(array.length-1)&&!finish){item=getItem(array,next);if(compare(target,item)===0){results.push(next);}
else{finish=true;}
next++;}
finish=false;next=from+middleIndex-1;while(next>=0&&!finish){item=getItem(array,next);if(compare(target,item)===0){results.push(next);}
else{finish=true;}
next--;}
return results;}
else if(compareResult<0){return utils.binarySearch(target,array,{from:from,to:to-middleIndex-1,arrayField:arrayField,transformFunction:transformFunction,compareFunction:compareFunction});}
else{return utils.binarySearch(target,array,{from:from+middleIndex+1,to:to,arrayField:arrayField,transformFunction:transformFunction,compareFunction:compareFunction});}};;;;(function(window,undefined){'use strict';function L10nError(message,id,loc){this.name='L10nError';this.message=message;this.id=id;this.loc=loc;}
L10nError.prototype=Object.create(Error.prototype);L10nError.prototype.constructor=L10nError;var io={_load:function(type,url,callback,sync){var xhr=new XMLHttpRequest();var needParse;if(xhr.overrideMimeType){xhr.overrideMimeType(type);}
xhr.open('GET',url,!sync);if(type==='application/json'){if(sync){needParse=true;}else{xhr.responseType='json';}}
xhr.addEventListener('load',function io_onload(e){if(e.target.status===200||e.target.status===0){var res=e.target.response||e.target.responseText;callback(null,needParse?JSON.parse(res):res);}else{callback(new L10nError('Not found: '+url));}});xhr.addEventListener('error',callback);xhr.addEventListener('timeout',callback);try{xhr.send(null);}catch(e){if(e.name==='NS_ERROR_FILE_NOT_FOUND'){callback(new L10nError('Not found: '+url));}else{throw e;}}},load:function(url,callback,sync){return io._load('text/plain',url,callback,sync);},loadJSON:function(url,callback,sync){return io._load('application/json',url,callback,sync);}};function EventEmitter(){}
EventEmitter.prototype.emit=function ee_emit(){if(!this._listeners){return;}
var args=Array.prototype.slice.call(arguments);var type=args.shift();if(!this._listeners[type]){return;}
var typeListeners=this._listeners[type].slice();for(var i=0;i<typeListeners.length;i++){typeListeners[i].apply(this,args);}};EventEmitter.prototype.addEventListener=function ee_add(type,listener){if(!this._listeners){this._listeners={};}
if(!(type in this._listeners)){this._listeners[type]=[];}
this._listeners[type].push(listener);};EventEmitter.prototype.removeEventListener=function ee_rm(type,listener){if(!this._listeners){return;}
var typeListeners=this._listeners[type];var pos=typeListeners.indexOf(listener);if(pos===-1){return;}
typeListeners.splice(pos,1);};function getPluralRule(lang){var locales2rules={'af':3,'ak':4,'am':4,'ar':1,'asa':3,'az':0,'be':11,'bem':3,'bez':3,'bg':3,'bh':4,'bm':0,'bn':3,'bo':0,'br':20,'brx':3,'bs':11,'ca':3,'cgg':3,'chr':3,'cs':12,'cy':17,'da':3,'de':3,'dv':3,'dz':0,'ee':3,'el':3,'en':3,'eo':3,'es':3,'et':3,'eu':3,'fa':0,'ff':5,'fi':3,'fil':4,'fo':3,'fr':5,'fur':3,'fy':3,'ga':8,'gd':24,'gl':3,'gsw':3,'gu':3,'guw':4,'gv':23,'ha':3,'haw':3,'he':2,'hi':4,'hr':11,'hu':0,'id':0,'ig':0,'ii':0,'is':3,'it':3,'iu':7,'ja':0,'jmc':3,'jv':0,'ka':0,'kab':5,'kaj':3,'kcg':3,'kde':0,'kea':0,'kk':3,'kl':3,'km':0,'kn':0,'ko':0,'ksb':3,'ksh':21,'ku':3,'kw':7,'lag':18,'lb':3,'lg':3,'ln':4,'lo':0,'lt':10,'lv':6,'mas':3,'mg':4,'mk':16,'ml':3,'mn':3,'mo':9,'mr':3,'ms':0,'mt':15,'my':0,'nah':3,'naq':7,'nb':3,'nd':3,'ne':3,'nl':3,'nn':3,'no':3,'nr':3,'nso':4,'ny':3,'nyn':3,'om':3,'or':3,'pa':3,'pap':3,'pl':13,'ps':3,'pt':3,'rm':3,'ro':9,'rof':3,'ru':11,'rwk':3,'sah':0,'saq':3,'se':7,'seh':3,'ses':0,'sg':0,'sh':11,'shi':19,'sk':12,'sl':14,'sma':7,'smi':7,'smj':7,'smn':7,'sms':7,'sn':3,'so':3,'sq':3,'sr':11,'ss':3,'ssy':3,'st':3,'sv':3,'sw':3,'syr':3,'ta':3,'te':3,'teo':3,'th':0,'ti':4,'tig':3,'tk':3,'tl':4,'tn':3,'to':0,'tr':0,'ts':3,'tzm':22,'uk':11,'ur':3,'ve':3,'vi':0,'vun':3,'wa':4,'wae':3,'wo':0,'xh':3,'xog':3,'yo':0,'zh':0,'zu':3};function isIn(n,list){return list.indexOf(n)!==-1;}
function isBetween(n,start,end){return typeof n===typeof start&&start<=n&&n<=end;}
var pluralRules={'0':function(){return'other';},'1':function(n){if((isBetween((n%100),3,10))){return'few';}
if(n===0){return'zero';}
if((isBetween((n%100),11,99))){return'many';}
if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'2':function(n){if(n!==0&&(n%10)===0){return'many';}
if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'3':function(n){if(n===1){return'one';}
return'other';},'4':function(n){if((isBetween(n,0,1))){return'one';}
return'other';},'5':function(n){if((isBetween(n,0,2))&&n!==2){return'one';}
return'other';},'6':function(n){if(n===0){return'zero';}
if((n%10)===1&&(n%100)!==11){return'one';}
return'other';},'7':function(n){if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'8':function(n){if((isBetween(n,3,6))){return'few';}
if((isBetween(n,7,10))){return'many';}
if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'9':function(n){if(n===0||n!==1&&(isBetween((n%100),1,19))){return'few';}
if(n===1){return'one';}
return'other';},'10':function(n){if((isBetween((n%10),2,9))&&!(isBetween((n%100),11,19))){return'few';}
if((n%10)===1&&!(isBetween((n%100),11,19))){return'one';}
return'other';},'11':function(n){if((isBetween((n%10),2,4))&&!(isBetween((n%100),12,14))){return'few';}
if((n%10)===0||(isBetween((n%10),5,9))||(isBetween((n%100),11,14))){return'many';}
if((n%10)===1&&(n%100)!==11){return'one';}
return'other';},'12':function(n){if((isBetween(n,2,4))){return'few';}
if(n===1){return'one';}
return'other';},'13':function(n){if((isBetween((n%10),2,4))&&!(isBetween((n%100),12,14))){return'few';}
if(n!==1&&(isBetween((n%10),0,1))||(isBetween((n%10),5,9))||(isBetween((n%100),12,14))){return'many';}
if(n===1){return'one';}
return'other';},'14':function(n){if((isBetween((n%100),3,4))){return'few';}
if((n%100)===2){return'two';}
if((n%100)===1){return'one';}
return'other';},'15':function(n){if(n===0||(isBetween((n%100),2,10))){return'few';}
if((isBetween((n%100),11,19))){return'many';}
if(n===1){return'one';}
return'other';},'16':function(n){if((n%10)===1&&n!==11){return'one';}
return'other';},'17':function(n){if(n===3){return'few';}
if(n===0){return'zero';}
if(n===6){return'many';}
if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'18':function(n){if(n===0){return'zero';}
if((isBetween(n,0,2))&&n!==0&&n!==2){return'one';}
return'other';},'19':function(n){if((isBetween(n,2,10))){return'few';}
if((isBetween(n,0,1))){return'one';}
return'other';},'20':function(n){if((isBetween((n%10),3,4)||((n%10)===9))&&!(isBetween((n%100),10,19)||isBetween((n%100),70,79)||isBetween((n%100),90,99))){return'few';}
if((n%1000000)===0&&n!==0){return'many';}
if((n%10)===2&&!isIn((n%100),[12,72,92])){return'two';}
if((n%10)===1&&!isIn((n%100),[11,71,91])){return'one';}
return'other';},'21':function(n){if(n===0){return'zero';}
if(n===1){return'one';}
return'other';},'22':function(n){if((isBetween(n,0,1))||(isBetween(n,11,99))){return'one';}
return'other';},'23':function(n){if((isBetween((n%10),1,2))||(n%20)===0){return'one';}
return'other';},'24':function(n){if((isBetween(n,3,10)||isBetween(n,13,19))){return'few';}
if(isIn(n,[2,12])){return'two';}
if(isIn(n,[1,11])){return'one';}
return'other';}};var index=locales2rules[lang.replace(/-.*$/,'')];if(!(index in pluralRules)){return function(){return'other';};}
return pluralRules[index];}
var MAX_PLACEABLES=100;var PropertiesParser={patterns:null,entryIds:null,init:function(){this.patterns={comment:/^\s*#|^\s*$/,entity:/^([^=\s]+)\s*=\s*(.*)$/,multiline:/[^\\]\\$/,index:/\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,unicode:/\\u([0-9a-fA-F]{1,4})/g,entries:/[^\r\n]+/g,controlChars:/\\([\\\n\r\t\b\f\{\}\"\'])/g,placeables:/\{\{\s*([^\s]*?)\s*\}\}/,};},parse:function(ctx,source){if(!this.patterns){this.init();}
var ast=[];this.entryIds=Object.create(null);var entries=source.match(this.patterns.entries);if(!entries){return ast;}
for(var i=0;i<entries.length;i++){var line=entries[i];if(this.patterns.comment.test(line)){continue;}
while(this.patterns.multiline.test(line)&&i<entries.length){line=line.slice(0,-1)+entries[++i].trim();}
var entityMatch=line.match(this.patterns.entity);if(entityMatch){try{this.parseEntity(entityMatch[1],entityMatch[2],ast);}catch(e){if(ctx){ctx._emitter.emit('parseerror',e);}else{throw e;}}}}
return ast;},parseEntity:function(id,value,ast){var name,key;var pos=id.indexOf('[');if(pos!==-1){name=id.substr(0,pos);key=id.substring(pos+1,id.length-1);}else{name=id;key=null;}
var nameElements=name.split('.');if(nameElements.length>2){throw new L10nError('Error in ID: "'+name+'".'+' Nested attributes are not supported.');}
var attr;if(nameElements.length>1){name=nameElements[0];attr=nameElements[1];if(attr[0]==='$'){throw new L10nError('Attribute can\'t start with "$"',id);}}else{attr=null;}
this.setEntityValue(name,attr,key,this.unescapeString(value),ast);},setEntityValue:function(id,attr,key,rawValue,ast){var pos,v;var value=rawValue.indexOf('{{')>-1?this.parseString(rawValue):rawValue;if(attr){pos=this.entryIds[id];if(pos===undefined){v={$i:id};if(key){v[attr]={};v[attr][key]=value;}else{v[attr]=value;}
ast.push(v);this.entryIds[id]=ast.length-1;return;}
if(key){if(typeof(ast[pos][attr])==='string'){ast[pos][attr]={$x:this.parseIndex(ast[pos][attr]),$v:{}};}
ast[pos][attr].$v[key]=value;return;}
ast[pos][attr]=value;return;}
if(key){pos=this.entryIds[id];if(pos===undefined){v={};v[key]=value;ast.push({$i:id,$v:v});this.entryIds[id]=ast.length-1;return;}
if(typeof(ast[pos].$v)==='string'){ast[pos].$x=this.parseIndex(ast[pos].$v);ast[pos].$v={};}
ast[pos].$v[key]=value;return;}
ast.push({$i:id,$v:value});this.entryIds[id]=ast.length-1;},parseString:function(str){var chunks=str.split(this.patterns.placeables);var complexStr=[];var len=chunks.length;var placeablesCount=(len-1)/2;if(placeablesCount>=MAX_PLACEABLES){throw new L10nError('Too many placeables ('+placeablesCount+', max allowed is '+MAX_PLACEABLES+')');}
for(var i=0;i<chunks.length;i++){if(chunks[i].length===0){continue;}
if(i%2===1){complexStr.push({t:'idOrVar',v:chunks[i]});}else{complexStr.push(chunks[i]);}}
return complexStr;},unescapeString:function(str){if(str.lastIndexOf('\\')!==-1){str=str.replace(this.patterns.controlChars,'$1');}
return str.replace(this.patterns.unicode,function(match,token){return unescape('%u'+'0000'.slice(token.length)+token);});},parseIndex:function(str){var match=str.match(this.patterns.index);if(!match){throw new L10nError('Malformed index');}
if(match[2]){return[{t:'idOrVar',v:match[1]},match[2]];}else{return[{t:'idOrVar',v:match[1]}];}}};var KNOWN_MACROS=['plural'];var MAX_PLACEABLE_LENGTH=2500;var rePlaceables=/\{\{\s*(.+?)\s*\}\}/g;var nonLatin1=/[^\x01-\xFF]/;var FSI='\u2068';var PDI='\u2069';function createEntry(node,env){var keys=Object.keys(node);if(typeof node.$v==='string'&&keys.length===2){return node.$v;}
var attrs;for(var i=0,key;key=keys[i];i++){if(key[0]==='$'){continue;}
if(!attrs){attrs=Object.create(null);}
attrs[key]=createAttribute(node[key],env,node.$i+'.'+key);}
return{id:node.$i,value:node.$v!==undefined?node.$v:null,index:node.$x||null,attrs:attrs||null,env:env,dirty:false};}
function createAttribute(node,env,id){if(typeof node==='string'){return node;}
return{id:id,value:node.$v||(node!==undefined?node:null),index:node.$x||null,env:env,dirty:false};}
function format(args,entity){if(typeof entity==='string'){return[{},entity];}
if(entity.dirty){throw new L10nError('Cyclic reference detected: '+entity.id);}
entity.dirty=true;var rv;try{rv=resolveValue({},args,entity.env,entity.value,entity.index);}finally{entity.dirty=false;}
return rv;}
function resolveIdentifier(args,env,id){if(KNOWN_MACROS.indexOf(id)>-1){return[{},env['__'+id]];}
if(args&&args.hasOwnProperty(id)){if(typeof args[id]==='string'||(typeof args[id]==='number'&&!isNaN(args[id]))){return[{},args[id]];}else{throw new L10nError('Arg must be a string or a number: '+id);}}
if(id in env&&id!=='__proto__'){return format(args,env[id]);}
throw new L10nError('Unknown reference: '+id);}
function subPlaceable(locals,args,env,id){var res;try{res=resolveIdentifier(args,env,id);}catch(err){return[{error:err},'{{ '+id+' }}'];}
var value=res[1];if(typeof value==='number'){return res;}
if(typeof value==='string'){if(value.length>=MAX_PLACEABLE_LENGTH){throw new L10nError('Too many characters in placeable ('+
value.length+', max allowed is '+
MAX_PLACEABLE_LENGTH+')');}
if(locals.contextIsNonLatin1||value.match(nonLatin1)){res[1]=FSI+value+PDI;}
return res;}
return[{},'{{ '+id+' }}'];}
function interpolate(locals,args,env,arr){return arr.reduce(function(prev,cur){if(typeof cur==='string'){return[prev[0],prev[1]+cur];}else if(cur.t==='idOrVar'){var placeable=subPlaceable(locals,args,env,cur.v);return[prev[0],prev[1]+placeable[1]];}},[locals,'']);}
function resolveSelector(args,env,expr,index){var selectorName=index[0].v;var selector=resolveIdentifier(args,env,selectorName)[1];if(typeof selector!=='function'){return selector;}
var argValue=index[1]?resolveIdentifier(args,env,index[1])[1]:undefined;if(selector===env.__plural){if(argValue===0&&'zero'in expr){return'zero';}
if(argValue===1&&'one'in expr){return'one';}
if(argValue===2&&'two'in expr){return'two';}}
return selector(argValue);}
function resolveValue(locals,args,env,expr,index){if(!expr){return[locals,expr];}
if(typeof expr==='string'||typeof expr==='boolean'||typeof expr==='number'){return[locals,expr];}
if(Array.isArray(expr)){locals.contextIsNonLatin1=expr.some(function($_){return typeof($_)==='string'&&$_.match(nonLatin1);});return interpolate(locals,args,env,expr);}
if(index){var selector=resolveSelector(args,env,expr,index);if(expr.hasOwnProperty(selector)){return resolveValue(locals,args,env,expr[selector]);}}
if('other'in expr){return resolveValue(locals,args,env,expr.other);}
throw new L10nError('Unresolvable value');}
var Resolver={createEntry:createEntry,format:format,rePlaceables:rePlaceables};function walkContent(node,fn){if(typeof node==='string'){return fn(node);}
if(node.t==='idOrVar'){return node;}
var rv=Array.isArray(node)?[]:{};var keys=Object.keys(node);for(var i=0,key;(key=keys[i]);i++){if(key==='$i'||key==='$x'){rv[key]=node[key];}else{rv[key]=walkContent(node[key],fn);}}
return rv;}
var reAlphas=/[a-zA-Z]/g;var reVowels=/[aeiouAEIOU]/g;var ACCENTED_MAP='\u0226\u0181\u0187\u1E12\u1E16\u0191\u0193\u0126\u012A'+'\u0134\u0136\u013F\u1E3E\u0220\u01FE\u01A4\u024A\u0158'+'\u015E\u0166\u016C\u1E7C\u1E86\u1E8A\u1E8E\u1E90'+'[\\]^_`'+'\u0227\u0180\u0188\u1E13\u1E17\u0192\u0260\u0127\u012B'+'\u0135\u0137\u0140\u1E3F\u019E\u01FF\u01A5\u024B\u0159'+'\u015F\u0167\u016D\u1E7D\u1E87\u1E8B\u1E8F\u1E91';var FLIPPED_MAP='\u2200\u0510\u2183p\u018E\u025F\u05E4HI\u017F'+'\u04FC\u02E5WNO\u0500\xD2\u1D1AS\u22A5\u2229\u0245'+'\uFF2DX\u028EZ'+'[\\]\u1D65_,'+'\u0250q\u0254p\u01DD\u025F\u0183\u0265\u0131\u027E'+'\u029E\u0285\u026Fuodb\u0279s\u0287n\u028C\u028Dx\u028Ez';function makeLonger(val){return val.replace(reVowels,function(match){return match+match.toLowerCase();});}
function replaceChars(map,val){return val.replace(reAlphas,function(match){return map.charAt(match.charCodeAt(0)-65);});}
var reWords=/[^\W0-9_]+/g;function makeRTL(val){return val.replace(reWords,function(match){return'\u202e'+match+'\u202c';});}
var reExcluded=/(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;function mapContent(fn,val){if(!val){return val;}
var parts=val.split(reExcluded);var modified=parts.map(function(part){if(reExcluded.test(part)){return part;}
return fn(part);});return modified.join('');}
function Pseudo(id,name,charMap,modFn){this.id=id;this.translate=mapContent.bind(null,function(val){return replaceChars(charMap,modFn(val));});this.name=this.translate(name);}
var PSEUDO={'fr-x-psaccent':new Pseudo('fr-x-psaccent','Runtime Accented',ACCENTED_MAP,makeLonger),'ar-x-psbidi':new Pseudo('ar-x-psbidi','Runtime Bidi',FLIPPED_MAP,makeRTL)};function Locale(id,ctx){this.id=id;this.ctx=ctx;this.isReady=false;this.entries=Object.create(null);this.entries.__plural=getPluralRule(this.isPseudo()?this.ctx.defaultLocale:id);}
Locale.prototype.isPseudo=function(){return this.ctx.qps.indexOf(this.id)!==-1;};var bindingsIO={extra:function(id,ver,path,type,callback,errback){if(type==='properties'){type='text';}
navigator.mozApps.getLocalizationResource(id,ver,path,type).then(callback.bind(null,null),errback);},app:function(id,ver,path,type,callback,errback,sync){switch(type){case'properties':io.load(path,callback,sync);break;case'json':io.loadJSON(path,callback,sync);break;}},};Locale.prototype.build=function L_build(callback){var sync=!callback;var ctx=this.ctx;var self=this;var l10nLoads=ctx.resLinks.length;function onL10nLoaded(err){if(err){ctx._emitter.emit('fetcherror',err);}
if(--l10nLoads<=0){self.isReady=true;if(callback){callback();}}}
if(l10nLoads===0){onL10nLoaded();return;}
function onJSONLoaded(err,json){if(!err&&json){self.addAST(json);}
onL10nLoaded(err);}
function onPropLoaded(err,source){if(!err&&source){var ast=PropertiesParser.parse(ctx,source);self.addAST(ast);}
onL10nLoaded(err);}
var idToFetch=this.isPseudo()?ctx.defaultLocale:this.id;var appVersion=null;var source='app';if(typeof(navigator)!=='undefined'){source=navigator.mozL10n._config.localeSources[this.id]||'app';appVersion=navigator.mozL10n._config.appVersion;}
for(var i=0;i<ctx.resLinks.length;i++){var resLink=decodeURI(ctx.resLinks[i]);var path=resLink.replace('{locale}',idToFetch);var type=path.substr(path.lastIndexOf('.')+1);var cb;switch(type){case'json':cb=onJSONLoaded;break;case'properties':cb=onPropLoaded;break;}
bindingsIO[source](this.id,appVersion,path,type,cb,onL10nLoaded,sync);}};function createPseudoEntry(node,entries){return Resolver.createEntry(walkContent(node,PSEUDO[this.id].translate),entries);}
Locale.prototype.addAST=function(ast){var createEntry=this.isPseudo()?createPseudoEntry.bind(this):Resolver.createEntry;for(var i=0;i<ast.length;i++){this.entries[ast[i].$i]=createEntry(ast[i],this.entries);}};function Context(id){this.id=id;this.isReady=false;this.isLoading=false;this.defaultLocale='en-US';this.availableLocales=[];this.supportedLocales=[];this.qps=[];this.resLinks=[];this.locales={};this._emitter=new EventEmitter();this._ready=new Promise(this.once.bind(this));}
function reportMissing(id,err){this._emitter.emit('notfounderror',err);return id;}
function getWithFallback(id){var cur=0;var loc;var locale;while(loc=this.supportedLocales[cur]){locale=this.getLocale(loc);if(!locale.isReady){locale.build(null);}
var entry=locale.entries[id];if(entry===undefined){cur++;reportMissing.call(this,id,new L10nError('"'+id+'"'+' not found in '+loc+' in '+this.id,id,loc));continue;}
return entry;}
throw new L10nError('"'+id+'"'+' missing from all supported locales in '+this.id,id);}
function formatTuple(args,entity){try{return Resolver.format(args,entity);}catch(err){this._emitter.emit('resolveerror',err);var locals={error:err};return[locals,entity.id];}}
function formatValue(args,entity){if(typeof entity==='string'){return entity;}
return formatTuple.call(this,args,entity)[1];}
function formatEntity(args,entity){var entityTuple=formatTuple.call(this,args,entity);var value=entityTuple[1];var formatted={value:value,attrs:null,};if(entity.attrs){formatted.attrs=Object.create(null);}
for(var key in entity.attrs){var attrTuple=formatTuple.call(this,args,entity.attrs[key]);formatted.attrs[key]=attrTuple[1];}
return formatted;}
function formatAsync(fn,id,args){return this._ready.then(getWithFallback.bind(this,id)).then(fn.bind(this,args),reportMissing.bind(this,id));}
Context.prototype.formatValue=function(id,args){return formatAsync.call(this,formatValue,id,args);};Context.prototype.formatEntity=function(id,args){return formatAsync.call(this,formatEntity,id,args);};function legacyGet(fn,id,args){if(!this.isReady){throw new L10nError('Context not ready');}
var entry;try{entry=getWithFallback.call(this,id);}catch(err){if(err.loc){throw err;}
reportMissing.call(this,id,err);return'';}
return fn.call(this,args,entry);}
Context.prototype.get=function(id,args){return legacyGet.call(this,formatValue,id,args);};Context.prototype.getEntity=function(id,args){return legacyGet.call(this,formatEntity,id,args);};Context.prototype.getLocale=function getLocale(code){var locales=this.locales;if(locales[code]){return locales[code];}
return locales[code]=new Locale(code,this);};function negotiate(available,requested,defaultLocale){var supportedLocale;for(var i=0;i<requested.length;i++){var locale=requested[i];if(available.indexOf(locale)!==-1){supportedLocale=locale;break;}}
if(!supportedLocale||supportedLocale===defaultLocale){return[defaultLocale];}
return[supportedLocale,defaultLocale];}
function freeze(supported){var locale=this.getLocale(supported[0]);if(locale.isReady){setReady.call(this,supported);}else{locale.build(setReady.bind(this,supported));}}
function setReady(supported){this.supportedLocales=supported;this.isReady=true;this._emitter.emit('ready');}
Context.prototype.registerLocales=function(defLocale,available){if(defLocale){this.defaultLocale=defLocale;}
this.availableLocales=[this.defaultLocale];this.qps=Object.keys(PSEUDO);if(available){for(var i=0,loc;loc=available[i];i++){if(this.availableLocales.indexOf(loc)===-1){this.availableLocales.push(loc);var pos=this.qps.indexOf(loc);if(pos!==-1){this.qps.splice(pos,1);}}}}};Context.prototype.requestLocales=function requestLocales(){if(this.isLoading&&!this.isReady){throw new L10nError('Context not ready');}
this.isLoading=true;var requested=Array.prototype.slice.call(arguments);if(requested.length===0){throw new L10nError('No locales requested');}
var supported=negotiate(this.availableLocales.concat(this.qps),requested,this.defaultLocale);if(this.supportedLocales[0]!==supported[0]){freeze.call(this,supported);}};Context.prototype.addEventListener=function(type,listener){this._emitter.addEventListener(type,listener);};Context.prototype.removeEventListener=function(type,listener){this._emitter.removeEventListener(type,listener);};Context.prototype.ready=function(callback){if(this.isReady){setTimeout(callback);}
this.addEventListener('ready',callback);};Context.prototype.once=function(callback){if(this.isReady){setTimeout(callback);return;}
var callAndRemove=(function(){this.removeEventListener('ready',callAndRemove);callback();}).bind(this);this.addEventListener('ready',callAndRemove);};var allowed={elements:['a','em','strong','small','s','cite','q','dfn','abbr','data','time','code','var','samp','kbd','sub','sup','i','b','u','mark','ruby','rt','rp','bdi','bdo','span','br','wbr'],attributes:{global:['title','aria-label','aria-valuetext','aria-moz-hint'],a:['download'],area:['download','alt'],input:['alt','placeholder'],menuitem:['label'],menu:['label'],optgroup:['label'],option:['label'],track:['label'],img:['alt'],textarea:['placeholder'],th:['abbr']}};var rtlList=['ar','he','fa','ps','ar-x-psbidi','ur'];var nodeObserver=null;var pendingElements=null;var moConfig={attributes:true,characterData:false,childList:true,subtree:true,attributeFilter:['data-l10n-id','data-l10n-args']};navigator.mozL10n={ctx:null,get:function get(id,ctxdata){return navigator.mozL10n.ctx.get(id,ctxdata);},formatValue:function(id,ctxdata){return navigator.mozL10n.ctx.formatValue(id,ctxdata);},formatEntity:function(id,ctxdata){return navigator.mozL10n.ctx.formatEntity(id,ctxdata);},translateFragment:function(fragment){return translateFragment.call(navigator.mozL10n,fragment);},setAttributes:setL10nAttributes,getAttributes:getL10nAttributes,ready:function ready(callback){return navigator.mozL10n.ctx.ready(callback);},once:function once(callback){return navigator.mozL10n.ctx.once(callback);},get readyState(){return navigator.mozL10n.ctx.isReady?'complete':'loading';},language:{set code(lang){navigator.mozL10n.ctx.requestLocales(lang);},get code(){return navigator.mozL10n.ctx.supportedLocales[0];},get direction(){return getDirection(navigator.mozL10n.ctx.supportedLocales[0]);}},qps:PSEUDO,_config:{appVersion:null,localeSources:Object.create(null),isPretranslated:false,},_getInternalAPI:function(){return{Error:L10nError,Context:Context,Locale:Locale,Resolver:Resolver,getPluralRule:getPluralRule,rePlaceables:rePlaceables,translateDocument:translateDocument,onMetaInjected:onMetaInjected,PropertiesParser:PropertiesParser,walkContent:walkContent,buildLocaleList:buildLocaleList};}};function getDirection(lang){return(rtlList.indexOf(lang)>=0)?'rtl':'ltr';}
var readyStates={loading:0,interactive:1,complete:2};function whenInteractive(callback){if(readyStates[document.readyState]>=readyStates.interactive){callback();return;}
document.addEventListener('readystatechange',function l10n_onrsc(){if(readyStates[document.readyState]>=readyStates.interactive){document.removeEventListener('readystatechange',l10n_onrsc);callback();}});}
function initObserver(){nodeObserver=new MutationObserver(onMutations.bind(navigator.mozL10n));nodeObserver.observe(document,moConfig);}
function init(pretranslate){if(!pretranslate){initObserver();}
initResources.call(navigator.mozL10n);}
function initResources(){var meta={};var nodes=document.head.querySelectorAll('link[rel="localization"],'+'meta[name="availableLanguages"],'+'meta[name="defaultLanguage"],'+'meta[name="appVersion"],'+'script[type="application/l10n"]');for(var i=0,node;node=nodes[i];i++){var type=node.getAttribute('rel')||node.nodeName.toLowerCase();switch(type){case'localization':this.ctx.resLinks.push(node.getAttribute('href'));break;case'meta':onMetaInjected.call(this,node,meta);break;case'script':onScriptInjected.call(this,node);break;}}
var additionalLanguagesPromise;if(navigator.mozApps&&navigator.mozApps.getAdditionalLanguages){additionalLanguagesPromise=navigator.mozApps.getAdditionalLanguages().catch(function(e){console.error('Error while loading getAdditionalLanguages',e);});document.addEventListener('additionallanguageschange',function(evt){registerLocales.call(this,meta,evt.detail);this.ctx.requestLocales.apply(this.ctx,navigator.languages||[navigator.language]);}.bind(this));}else{additionalLanguagesPromise=Promise.resolve();}
additionalLanguagesPromise.then(function(extraLangs){registerLocales.call(this,meta,extraLangs);initLocale.call(this);}.bind(this));}
function registerLocales(meta,extraLangs){var locales=buildLocaleList.call(this,meta,extraLangs);navigator.mozL10n._config.localeSources=locales[1];this.ctx.registerLocales(locales[0],Object.keys(locales[1]));}
function getMatchingLangpack(appVersion,langpacks){for(var i=0,langpack;(langpack=langpacks[i]);i++){if(langpack.target===appVersion){return langpack;}}
return null;}
function buildLocaleList(meta,extraLangs){var loc,lp;var localeSources=Object.create(null);var defaultLocale=meta.defaultLanguage||this.ctx.defaultLocale;if(meta.availableLanguages){for(loc in meta.availableLanguages){localeSources[loc]='app';}}
if(extraLangs){for(loc in extraLangs){lp=getMatchingLangpack(this._config.appVersion,extraLangs[loc]);if(!lp){continue;}
if(!(loc in localeSources)||!meta.availableLanguages[loc]||parseInt(lp.revision)>meta.availableLanguages[loc]){localeSources[loc]='extra';}}}
if(!(defaultLocale in localeSources)){localeSources[defaultLocale]='app';}
return[defaultLocale,localeSources];}
function splitAvailableLanguagesString(str){var langs={};str.split(',').forEach(function(lang){lang=lang.trim().split(':');langs[lang[0]]=parseInt(lang[1]);});return langs;}
function onMetaInjected(node,meta){switch(node.getAttribute('name')){case'availableLanguages':meta.availableLanguages=splitAvailableLanguagesString(node.getAttribute('content'));break;case'defaultLanguage':meta.defaultLanguage=node.getAttribute('content');break;case'appVersion':navigator.mozL10n._config.appVersion=node.getAttribute('content');break;}}
function onScriptInjected(node){var lang=node.getAttribute('lang');var locale=this.ctx.getLocale(lang);locale.addAST(JSON.parse(node.textContent));}
function initLocale(){this.ctx.requestLocales.apply(this.ctx,navigator.languages||[navigator.language]);window.addEventListener('languagechange',function l10n_langchange(){this.ctx.requestLocales.apply(this.ctx,navigator.languages||[navigator.language]);}.bind(this));}
function localizeMutations(mutations){var mutation;var targets=new Set();for(var i=0;i<mutations.length;i++){mutation=mutations[i];if(mutation.type==='childList'){var addedNode;for(var j=0;j<mutation.addedNodes.length;j++){addedNode=mutation.addedNodes[j];if(addedNode.nodeType!==Node.ELEMENT_NODE){continue;}
targets.add(addedNode);}}
if(mutation.type==='attributes'){targets.add(mutation.target);}}
targets.forEach(function(target){if(target.childElementCount){translateFragment.call(this,target);}else if(target.hasAttribute('data-l10n-id')){translateElement.call(this,target);}},this);}
function onMutations(mutations,self){self.disconnect();localizeMutations.call(this,mutations);self.observe(document,moConfig);}
function onReady(){if(!navigator.mozL10n._config.isPretranslated){translateDocument.call(this);}
navigator.mozL10n._config.isPretranslated=false;if(pendingElements){for(var i=0,element;element=pendingElements[i];i++){translateElement.call(this,element);}
pendingElements=null;}
if(!nodeObserver){initObserver();}
fireLocalizedEvent.call(this);}
function fireLocalizedEvent(){var event=new CustomEvent('localized',{'bubbles':false,'cancelable':false,'detail':{'language':this.ctx.supportedLocales[0]}});window.dispatchEvent(event);}
var reOverlay=/<|&#?\w+;/;var reHtml=/[&<>]/g;var htmlEntities={'&':'&amp;','<':'&lt;','>':'&gt;',};function translateDocument(){document.documentElement.lang=this.language.code;document.documentElement.dir=this.language.direction;translateFragment.call(this,document.documentElement);}
function translateFragment(element){if(typeof element.hasAttribute==='function'&&element.hasAttribute('data-l10n-id')){translateElement.call(this,element);}
var nodes=getTranslatableChildren(element);for(var i=0;i<nodes.length;i++){translateElement.call(this,nodes[i]);}}
function setL10nAttributes(element,id,args){element.setAttribute('data-l10n-id',id);if(args){element.setAttribute('data-l10n-args',JSON.stringify(args));}}
function getL10nAttributes(element){return{id:element.getAttribute('data-l10n-id'),args:JSON.parse(element.getAttribute('data-l10n-args'))};}
function getTranslatableChildren(element){return element?element.querySelectorAll('*[data-l10n-id]'):[];}
function camelCaseToDashed(string){if(string==='ariaValueText'){return'aria-valuetext';}
return string.replace(/[A-Z]/g,function(match){return'-'+match.toLowerCase();}).replace(/^-/,'');}
function escapeL10nArgs(match){return htmlEntities[match];}
function translateElement(element){if(!this.ctx.isReady){if(!pendingElements){pendingElements=[];}
pendingElements.push(element);return;}
var l10nId=element.getAttribute('data-l10n-id');if(!l10nId){return false;}
var l10nArgs=element.getAttribute('data-l10n-args');var entity=this.ctx.getEntity(l10nId,l10nArgs?JSON.parse(l10nArgs.replace(reHtml,escapeL10nArgs)):undefined);var value=entity.value;if(typeof value==='string'){if(!reOverlay.test(value)){element.textContent=value;}else{var translation=element.ownerDocument.createElement('template');translation.innerHTML=value;overlayElement(element,translation.content);}}
for(var key in entity.attrs){var attrName=camelCaseToDashed(key);if(isAttrAllowed({name:attrName},element)){element.setAttribute(attrName,entity.attrs[key]);}}}
function overlayElement(sourceElement,translationElement){var result=translationElement.ownerDocument.createDocumentFragment();var k,attr;var childElement;while((childElement=translationElement.childNodes[0])){translationElement.removeChild(childElement);if(childElement.nodeType===Node.TEXT_NODE){result.appendChild(childElement);continue;}
var index=getIndexOfType(childElement);var sourceChild=getNthElementOfType(sourceElement,childElement,index);if(sourceChild){overlayElement(sourceChild,childElement);result.appendChild(sourceChild);continue;}
if(isElementAllowed(childElement)){var sanitizedChild=childElement.ownerDocument.createElement(childElement.nodeName);overlayElement(sanitizedChild,childElement);result.appendChild(sanitizedChild);continue;}
result.appendChild(document.createTextNode(childElement.textContent));}
sourceElement.textContent='';sourceElement.appendChild(result);if(translationElement.attributes){for(k=0,attr;(attr=translationElement.attributes[k]);k++){if(isAttrAllowed(attr,sourceElement)){sourceElement.setAttribute(attr.name,attr.value);}}}}
function isElementAllowed(element){return allowed.elements.indexOf(element.tagName.toLowerCase())!==-1;}
function isAttrAllowed(attr,element){var attrName=attr.name.toLowerCase();var tagName=element.tagName.toLowerCase();if(allowed.attributes.global.indexOf(attrName)!==-1){return true;}
if(!allowed.attributes[tagName]){return false;}
if(allowed.attributes[tagName].indexOf(attrName)!==-1){return true;}
if(tagName==='input'&&attrName==='value'){var type=element.type.toLowerCase();if(type==='submit'||type==='button'||type==='reset'){return true;}}
return false;}
function getNthElementOfType(context,element,index){var nthOfType=0;for(var i=0,child;child=context.children[i];i++){if(child.nodeType===Node.ELEMENT_NODE&&child.tagName===element.tagName){if(nthOfType===index){return child;}
nthOfType++;}}
return null;}
function getIndexOfType(element){var index=0;var child;while((child=element.previousElementSibling)){if(child.tagName===element.tagName){index++;}}
return index;}
var DEBUG=false;navigator.mozL10n.ctx=new Context(window.document?document.URL:null);navigator.mozL10n.ctx.ready(onReady.bind(navigator.mozL10n));navigator.mozL10n.ctx.addEventListener('notfounderror',function reportMissingEntity(e){if(DEBUG||e.loc==='en-US'){console.warn(e.toString());}});if(DEBUG){navigator.mozL10n.ctx.addEventListener('fetcherror',console.error.bind(console));navigator.mozL10n.ctx.addEventListener('parseerror',console.error.bind(console));navigator.mozL10n.ctx.addEventListener('resolveerror',console.error.bind(console));}
if(window.document){navigator.mozL10n._config.isPretranslated=document.documentElement.lang===navigator.language;var forcePretranslate=!navigator.mozL10n._config.isPretranslated;whenInteractive(init.bind(navigator.mozL10n,forcePretranslate));}
document.l10n={setAttributes:navigator.mozL10n.setAttributes,getAttributes:navigator.mozL10n.getAttributes,formatValue:function(id,args){return navigator.mozL10n.formatValue(id,args);},translateFragment:function(frag){return Promise.resolve(navigator.mozL10n.translateFragment(frag));},ready:new Promise(function(resolve){navigator.mozL10n.once(resolve);}),formatValues:function(){var keys=arguments;var resp=keys.map(function(key){if(Array.isArray(key)){return navigator.mozL10n.formatValue(key[0],key[1]);}
return navigator.mozL10n.formatValue(key);});return Promise.all(resp);},requestLanguages:function(langs){navigator.mozL10n.ctx.requestLocales.apply(navigator.mozL10n.ctx,langs);},pseudo:{'fr-x-psaccent':{getName:function(){return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].name);},processString:function(s){return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].translate(s));}},'ar-x-psbidi':{getName:function(){return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].name);},processString:function(s){return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].translate(s));}}},};navigator.mozL10n.ready(function(){document.documentElement.setAttribute('langs',navigator.mozL10n.ctx.supportedLocales.join(' '));});navigator.mozL10n.once(function(){window.addEventListener('localized',function(){document.dispatchEvent(new CustomEvent('DOMRetranslated',{bubbles:false,cancelable:false}));});});})(this);

View file

@ -0,0 +1,778 @@
<!DOCTYPE html>
<html lang="en-US" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<meta name="theme-group" content="theme-communications">
<meta name="theme-color" content="var(--header-background)">
<title> Contacts </title>
<!-- Building Blocks -->
<link href="/shared/style/input_areas.css" rel="stylesheet">
<link href="/shared/style/lists.css" rel="stylesheet">
<link href="/shared/style/scrolling.css" rel="stylesheet">
<link href="/shared/style/buttons.css" rel="stylesheet">
<link href="/shared/style/progress_activity.css" rel="stylesheet">
<link href="/shared/style/status.css" rel="stylesheet">
<link href="/shared/style/switches.css" rel="stylesheet">
<link href="/shared/style/confirm.css" rel="stylesheet">
<link href="/shared/style/action_menu.css" rel="stylesheet">
<link href="/shared/style/edit_mode.css" rel="stylesheet">
<link href="/shared/style/animations.css" rel="stylesheet">
<link href="/contacts/style/app.css" rel="stylesheet">
<link href="/contacts/style/contacts.css" rel="stylesheet">
<link href="/contacts/style/overlay.css" rel="stylesheet">
<link href="/shared/pages/import/style/search.css" rel="stylesheet">
<link href="/shared/pages/import/style/curtain_frame.css" rel="stylesheet">
<link href="/contacts/style/status.css" rel="stylesheet">
<link href="/contacts/style/fb_extensions.css" rel="stylesheet">
<link href="/contacts/style/match_service.css" rel="stylesheet">
<link href="/shared/style/contacts.css" rel="stylesheet">
<link href="/shared/style/contacts/contacts_buttons.css" rel="stylesheet">
<!-- <link rel="stylesheet" type="text/css" href="/shared/elements/gaia-icons/gaia-icons.css" /> -->
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
<!-- For contacts import/export:
<script defer type="text/javascript" src="/shared/js/contacts/import/utilities/sdcard.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/import/utilities/vcard_parser.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/import/utilities/vcard_reader.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/import/utilities/import_from_vcard.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/import/utilities/import_sim_contacts.js"></script>
<script defer type="text/javascript" src="/shared/js/confirm.js"></script>
<script defer type="text/javascript" src="/shared/js/image_utils.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/utilities/image_thumbnail.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/contacts_shortcuts.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/import/utilities/overlay.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/import/utilities/status.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/import/import_status_data.js"></script>
<script defer type="text/javascript" src="/shared/js/date_time_helper.js"></script>
<script defer type="text/javascript" src="/shared/js/contacts/utilities/ice_store.js"></script>
<script defer src="/shared/js/mime_mapper.js"></script>
<script defer src="/shared/js/contacts/import/utilities/misc.js"></script>
<script defer src="/shared/js/l10n.js"></script>
<script defer src="/contacts/js/activities.js"></script>
<script defer src="/shared/js/contact_photo_helper.js"></script>
<script defer src="/shared/js/async_storage.js"></script>
<script defer src="/shared/js/contacts/import/utilities/config.js"></script>
<script defer src="/contacts/js/utilities/cookie.js"></script>
<script defer src="/contacts/js/utilities/extract_params.js"></script>
<script defer src="/contacts/js/utilities/performance_helper.js"></script>
<script defer src="/contacts/js/utilities/mozContact.js"></script>
<script defer src="/contacts/js/fb/fb_init.js"></script>
<script defer src="/shared/js/contacts/utilities/event_listeners.js"></script>
<script defer src="/contacts/js/navigation.js"></script>
<script defer src="/contacts/js/views/list.js"></script>
<script defer src="/contacts/js/contacts.js"></script>
<script defer src="/shared/js/contacts/utilities/image_loader.js"></script>
<script defer src="/contacts/js/fb_loader.js"></script>
<script defer src="/shared/js/advanced_telemetry_helper.js"></script>
<script defer src="/shared/js/settings_listener.js"></script>
-->
<!-- Web Components -->
<!-- <script defer="" src="/shared/elements/config.js"></script> -->
<!-- <script defer="" src="/shared/js/component_utils.js"></script> -->
<!-- <script defer="" src="/shared/elements/gaia_subheader/script.js"></script> -->
<!-- <script defer="" src="/shared/elements/gaia-header/dist/gaia-header.js"></script> -->
<link rel="localization" href="/locales-obj/contacts.index.{locale}.json">
<!--
<link rel="stylesheet" type="text/css" href="/shared/elements/gaia_menu/style.css">
<link rel="stylesheet" type="text/css" href="/shared/elements/gaia_sim_picker/style.css">
<script defer src="/shared/elements/gaia_menu/script.js"></script>
<script defer src="/shared/elements/gaia_sim_picker/script.js"></script>
<script defer src="/shared/elements/gaia_switch/script.js"></script>
-->
<!-- <script defer="" src="/shared/js/lazy_loader.js"></script> -->
<!-- <script defer="" src="/contacts/js/utilities/performance_helper.js"></script> -->
<!-- <script defer="" src="/contacts/js/cache.js"></script> -->
<!-- <script defer="" src="/contacts/js/bootstrap.js"></script> --><script src="./gaia_build_defer_index.js" defer="" type="text/javascript"></script>
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="ar:201601260729, bg:201601260729, bn-BD:201601260729, ca:201601260729, cs:201601260729, da:201601260729, de:201601260729, el:201601260729, en-US:201601260729, es:201601260729, eu:201601260729, fr:201601260729, gl:201601260729, hr:201601260729, hu:201601260729, it:201601260729, ja:201601260729, km:201601260729, ko:201601260729, lt:201601260729, mk:201601260729, ms:201601260729, ne-NP:201601260729, nl:201601260729, pa:201601260729, pl:201601260729, pt-BR:201601260729, ro:201601260729, ru:201601260729, sk:201601260729, sq:201601260729, sr-Cyrl:201601260729, sr-Latn:201601260729, sv-SE:201601260729, tr:201601260729, zh-CN:201601260729, zh-TW:201601260729">
<!-- For the build system to include in zip:
<link href="/shared/js/tag_visibility_monitor.js">
<link href="/shared/js/contact2vcard.js">
<link href="/shared/js/device_storage/get_storage_if_available.js">
<link href="/shared/js/device_storage/get_unused_filename.js">
<link href="/shared/js/fb/fb_request.js">
<link href="/shared/js/fb/fb_data_reader.js">
<link href="/shared/js/setImmediate.js">
<link href="/shared/js/sim_settings_helper.js">
<link href="/shared/js/nfc_utils.js">
<link href="/shared/js/contacts/sms_integration.js">
<link href="/shared/js/contacts/contacts_buttons.js">
<link href="/shared/js/contacts/import/facebook/fb_data.js">
<link href="/shared/js/contacts/import/facebook/fb_utils.js">
<link href="/shared/js/contacts/import/facebook/fb_query.js">
<link href="/shared/js/contacts/import/facebook/fb_contact_utils.js">
<link href="/shared/js/contacts/import/facebook/fb_contact.js">
<link href="/shared/js/dialer/telephony_messages.js">
<link href="/shared/pages/import/js/parameters.js">
<link href="/shared/js/utilities.js">
-->
<!--
<link href="/shared/pages/import/oauth.html">
<link href="/shared/pages/import/curtain.html">
<link href="/shared/pages/import/import.html">
-->
<meta content="2.5" name="appVersion"></head>
<body role="application" class="app-contacts skin-comms theme-communications">
<!-- Contacts List Section -->
<section id="view-contacts-list" role="region" data-state="active" class="view view-noscroll view-contacts-list">
<gaia-header id="contacts-list-header">
<h1 id="app-title" data-l10n-id="contacts">Contacts</h1>
<button aria-label="Add" id="add-contact-button" data-icon="add" data-l10n-id="addButton"></button>
<button aria-label="Settings" id="settings-button" data-icon="settings" data-l10n-id="settingsButton"></button>
</gaia-header>
<article class="view-body">
<div id="no-contacts" class="hide">
<div class="info">
<p data-l10n-id="no-contacts">No contacts</p>
<p data-l10n-id="start-adding">Start adding them now</p>
</div>
</div>
<nav data-type="scrollbar">
<p></p>
<ol>
<li data-anchor="search-container" data-img="magnifier_icon_big.png">
<a href="#"><span class="pack-icon-search">Search</span></a>
</li>
<li class="hide" data-anchor="group-ice" data-img="icon_ice_shortcut.png">
<a href="#"><span class="pack-icon-ice">ICE</span></a>
</li>
<li data-anchor="group-favorites" data-img="star_icon_big.png">
<a href="#"><span class="pack-icon-favorites">Favorites</span></a>
</li>
<li data-anchor="group-A" data-letter="A"><a href="#">A</a></li>
<li data-anchor="group-B" data-letter="B"><a href="#">B</a></li>
<li data-anchor="group-C" data-letter="C"><a href="#">C</a></li>
<li data-anchor="group-D" data-letter="D"><a href="#">D</a></li>
<li data-anchor="group-E" data-letter="E"><a href="#">E</a></li>
<li data-anchor="group-F" data-letter="F"><a href="#">F</a></li>
<li data-anchor="group-G" data-letter="G"><a href="#">G</a></li>
<li data-anchor="group-H" data-letter="H"><a href="#">H</a></li>
<li data-anchor="group-I" data-letter="I"><a href="#">I</a></li>
<li data-anchor="group-J" data-letter="J"><a href="#">J</a></li>
<li data-anchor="group-K" data-letter="K"><a href="#">K</a></li>
<li data-anchor="group-L" data-letter="L"><a href="#">L</a></li>
<li data-anchor="group-M" data-letter="M"><a href="#">M</a></li>
<li data-anchor="group-N" data-letter="N"><a href="#">N</a></li>
<li data-anchor="group-O" data-letter="O"><a href="#">O</a></li>
<li data-anchor="group-P" data-letter="P"><a href="#">P</a></li>
<li data-anchor="group-Q" data-letter="Q"><a href="#">Q</a></li>
<li data-anchor="group-R" data-letter="R"><a href="#">R</a></li>
<li data-anchor="group-S" data-letter="S"><a href="#">S</a></li>
<li data-anchor="group-T" data-letter="T"><a href="#">T</a></li>
<li data-anchor="group-U" data-letter="U"><a href="#">U</a></li>
<li data-anchor="group-V" data-letter="V"><a href="#">V</a></li>
<li data-anchor="group-W" data-letter="W"><a href="#">W</a></li>
<li data-anchor="group-X" data-letter="X"><a href="#">X</a></li>
<li data-anchor="group-Y" data-letter="Y"><a href="#">Y</a></li>
<li data-anchor="group-Z" data-letter="Z"><a href="#">Z</a></li>
<li data-anchor="group-#" data-letter="#"><a href="#">#</a></li>
</ol>
</nav>
<div id="groups-container-box">
<div id="groups-container" class="view-body-inner">
<div id="groups-container-background">
<form id="search-container" class="search" role="search">
<p>
<label id="search-start">
<input name="search" class="textfield" x-inputmode="verbatim" placeholder="Search" data-l10n-id="search-contact" type="search">
</label>
</p>
</form>
<section id="groups-list" data-type="list" class="active">
<!-- Group template here -->
</section>
</div>
</div>
</div>
</article>
</section>
<form id="selectable-form" role="dialog" data-type="edit" class="hide">
<section role="region" class="theme-settings">
<gaia-header id="selectable-form-header" action="close">
<h1 id="edit-title" data-l10n-id="contacts">Contacts</h1>
<button type="button" id="select-action"></button>
</gaia-header>
</section>
<menu id="select-all-wrapper">
<button id="deselect-all" class="edit-button" data-l10n-id="deselectAll" disabled="">Deselect all</button>
<button id="select-all" class="edit-button" data-l10n-id="selectAll">Select all</button>
</menu>
</form>
<section id="search-view" class="view"><!--
<form id="searchview-container" class="search" role="search">
<p>
<label role="presentation">
<input name="search" class="textfield" x-inputmode="verbatim" id="search-contact" placeholder="Search" data-l10n-id="search-contact" type="search">
<button type="reset">Clear</button>
</label>
</p>
<button id="cancel-search" data-l10n-id="cancel" type="submit">Cancel</button>
</form>
<section id="groups-list-search">
<ol id="search-list" data-type="list" class="active" role="listbox">
</ol>
</section>
<section id="search-process">
<p id="no-result" class="hide" data-l10n-id="noResults">No contacts found</p>
<p id="search-progress" class="hidden" role="status">
<progress class="small"></progress>
</p>
</section>
--></section>
<section id="view-screenshot" role="region" class="view hide"><!--
<article id="view-screenshot-body">
</article>
--></section>
<section id="view-contact-details" role="region" class="view view-right view-contact-profile view-noscroll"><!--
<gaia-header id="details-view-header" action="back">
<h1 id="contact-name-title"><bdi class="ellipsis-dir-fix"></bdi></h1>
<div class="favorite-indicator"></div>
<button id="edit-contact-button" data-icon="edit-contact" data-l10n-id="editButton"></button>
</gaia-header>
<article id="contact-detail-wrapper" class="view-body">
<div id="cover-img"></div>
<section id="contact-detail" class="view-body-inner">
<li data-phone="" id="phone-details-template-#i#" data-template="" role="region" aria-label="Phone" data-l10n-id="phoneDetail">
<h2>
<span data-l10n-id="#type_l10n_id#">#type#</span>
<span class="carrier-wrapper" hidden="">
<span data-l10n-id="separator"></span>
<span class="carrier">#carrier#</span>
</span>
</h2>
<div class="item">
<div class="item-body-exp">
<div class="button activity call-action icon icon-call" id="call-or-pick-#i#">
<b aria-label="Call" data-l10n-id="makeCall"><bdi class="ellipsis-dir-fix">#value#</bdi></b>
<span class="js-sim-indication"></span>
</div>
</div>
<div class="item-media pull-right">
<button id="send-sms-button-#i#" class="activity send-sms icon icon-message" data-l10n-id="send-sms-button" aria-label="SMS"></button>
</div>
</div>
</li>
<li data-social="" class="social-actions" id="social-template-#i#" data-template="" role="region">
<h2 data-l10n-id="social-network" id="social-label">Social Network</h2>
<button id="share_button" data-l10n-id="share-contact" class="activity icon icon-share">
Share
</button>
</li>
<li class="duplicate-actions" id="duplicate-contacts-template" data-template="" role="region">
<h2 data-l10n-id="organise-contacts">Organize Contacts</h2>
<button id="find-merge-button" data-l10n-id="find-merge-contacts" class="activity icon icon-find-and-merge">
Find duplicate contacts
</button>
</li>
<li data-address="" id="address-details-template-#i#" data-template="" role="region" aria-label="Address" data-l10n-id="addressDetail">
<h2 data-l10n-id="#type_l10n_id#">#type#</h2>
<div class="item">
<div class="item-body-exp">
<a href="#" class="action action-block">
<b>
<span class="force-align" dir="auto">#streetAddress#</span>
#postalCode#
#locality#
#countryName#
</b>
</a>
</div>
</div>
</li>
<li data-mail="" id="email-details-template-#i#" data-template="" role="region" aria-label="Email" data-l10n-id="emailDetail">
<h2 data-l10n-id="#type_l10n_id#"></h2>
<div class="item">
<div class="item-body-exp">
<button id="email-or-pick-#i#" class="activity icon icon-email">
<b aria-label="Email" data-l10n-id="composeEmail" dir="ltr">#value#</b>
</button>
</div>
<div class="item-media pull-right">
<button id="send-sms-to-email-button-#i#" class="activity send-sms icon icon-message" data-l10n-id="send-sms-button" aria-label="SMS"></button>
</div>
</div>
</li>
<li id="dates-template-#i#" data-template="" role="region" aria-label="Date" data-l10n-id="dateDetail">
<h2 data-l10n-id="#type_l10n_id#">#type#</h2>
<p><strong>#date#</strong></p>
</li>
<p data-comment="" id="note-details-template-#i#" data-template="" class="comment-notes">#note#</p>
<div id="contact-detail-inner">
<h1 id="org-title" class="force-align" dir="auto"></h1>
<ul id="details-list" role="presentation">
</ul>
<ul role="presentation">
<li role="presentation">
<button id="toggle-favorite" class="fillflow-row icon icon-fav">
Add as favorite
</button>
</li>
</ul>
</div>
</section>
</article>
--></section>
<section id="view-contact-form" role="region" class="view view-bottom view-edit-contact"><!--
<gaia-header id="contact-form-header" action="close">
<h1 id="contact-form-title" data-l10n-id="addContact"></h1>
<button id="save-button" data-l10n-id="done">Done</button>
</gaia-header>
<progress id="throbber" value="0" max="100" class="pack-activity hide"></progress>
<div id="add-phone-#i#" role="listitem" data-template="" class="phone-template field-template" data-index="#i#">
<fieldset class="fillflow-row">
<div class="two_fields first-row">
<legend class="action" role="combobox" aria-haspopup="true" aria-labelledby="tel_type_#i#">
<span aria-hidden="true" data-taglist="phone-type" name="tel" data-value="#type_value#" id="tel_type_#i#" data-field="type" data-l10n-id="#type_l10n_id#">
#type#
</span>
</legend>
<p class="setbox-item">
<input placeholder="Carrier" data-field="carrier" data-l10n-id="carrier" name="tel[#i#][carrier]" class="textfield" x-inputmode="latin-prose" value="#carrier#" id="carrier_#i#" dir="auto" type="text">
<button type="reset">Clear</button>
</p>
</div>
<section class="second-row">
<p class="setbox-item">
<bdi>
<input placeholder="Phone" data-field="value" data-l10n-id="tel" name="tel[#i#][value]" class="textfield" value="#value#" id="number_#i#" type="tel">
<button type="reset">Clear</button>
</bdi>
</p>
</section>
</fieldset>
</div>
<div id="add-email-#i#" role="listitem" data-template="" class="email-template field-template" data-index="#i#">
<fieldset class="fillflow-row">
<legend class="action first-row" role="combobox" aria-haspopup="true" aria-labelledby="email_type_#i#">
<span aria-hidden="true" data-taglist="email-type" name="email[#i#][type]" data-value="#type_value#" id="email_type_#i#" data-field="type" data-l10n-id="#type_l10n_id#">#type#</span>
</legend>
<section>
<p class="setbox-item second-row">
<input placeholder="Email" data-field="value" data-l10n-id="email" name="email[#i#][value]" class="textfield" value="#value#" id="email_#i#" type="email">
<button type="reset">Clear</button>
</p>
</section>
</fieldset>
</div>
<div id="add-address-#i#" role="listitem" data-template="" class="address-template field-template" data-index="#i#">
<fieldset class="fillflow-row">
<section>
<p class="setbox-item first-row">
<input placeholder="Street" data-field="streetAddress" data-l10n-id="streetAddress" name="adr[#i#][streetAddress]" class="textfield" x-inputmode="latin-prose" value="#streetAddress#" id="streetAddress_#i#" dir="auto" type="text">
<button type="reset">Clear</button>
</p>
<div class="two_fields second-row">
<legend class="action" role="combobox" aria-haspopup="true" aria-labelledby="address_type_#i#">
<span aria-hidden="true" data-taglist="address-type" name="adr[#i#][type]" data-value="#type_value#" id="address_type_#i#" data-field="type" data-l10n-id="#type_l10n_id#">#type#</span>
</legend>
<p class="setbox-item">
<input placeholder="City" data-field="locality" data-l10n-id="locality" name="adr[#i#][locality]" class="textfield" x-inputmode="latin-prose" value="#locality#" id="locality_#i#" dir="auto" type="text">
<button type="reset">Clear</button>
</p>
</div>
<div class="two_fields third-row">
<p class="setbox-item">
<input placeholder="Country" data-field="countryName" data-l10n-id="countryName" name="adr[#i#][countryName]" class="textfield" x-inputmode="latin-prose" value="#countryName#" id="countryName_#i#" dir="auto" type="text">
<button type="reset">Clear</button>
</p>
<p class="setbox-item">
<input placeholder="Zip Code" data-field="postalCode" data-l10n-id="postalCode" name="adr[#i#][postalCode]" class="textfield" x-inputmode="verbatim" value="#postalCode#" id="postalCode_#i#" dir="auto" type="text">
<button type="reset">Clear</button>
</p>
</div>
</section>
</fieldset>
</div>
<div id="add-date-#i#" role="listitem" data-template="" class="date-template field-template" data-exclusive="true" data-custom="false" data-index="#i#">
<fieldset class="fillflow-row first-row">
<div class="two_fields">
<legend class="action" role="combobox" aria-haspopup="true" aria-labelledby="date_type_#i#">
<span aria-hidden="true" data-taglist="date-type" name="date[#i#][type]" data-value="#type_value#" id="date_type_#i#" data-field="type" data-l10n-id="#type_l10n_id#">#type#</span>
</legend>
<section>
<p class="setbox-item">
<span aria-hidden="true" id="date-text_#i#" class="datetime placeholder">
Date
</span>
<input placeholder="date" data-field="#type#" data-l10n-id="date" name="date[#i#][value]" id="date_#i#" aria-labelledby="date-text_#i#" type="date">
</p>
</section>
</div>
</fieldset>
</div>
<div id="add-note-#i#" role="listitem" data-template="" class="note-template field-template" data-index="#i#">
<dl role="presentation" class="setbox fillflow-row first-row">
<dd class="setbox-body">
<p class="setbox-item">
<textarea placeholder="Comment" data-field="note" data-l10n-id="comment" name="comment[#i#]" class="textfield" type="text" x-inputmode="latin-prose" id="note_#i#"> #note#
</textarea>
<button type="reset">Clear</button>
</p>
</dd>
</dl>
</div>
<article class="view-body">
<div class="view-body-inner">
<form id="contact-form" action="#" class="fill-flow">
<input name="id" id="contact-form-id" value="" type="hidden">
<section class="item first">
<div id="thumbnail-action" href="#" class="item-media fillflow-media">
<div role="button" id="photo-button" class="bb-button">
<p>
<span class="icon icon-addimage"></span>
<em data-l10n-id="addPhoto">Add photo</em>
</p>
<p class="edit-photo">
<em data-l10n-id="edit-picture">Edit Picture</em>
</p>
<img id="thumbnail-photo" alt="">
</div>
</div>
<div class="item-body-exp with-overflow">
<p class="fillflow-row input-container">
<input placeholder="Name" data-field="givenName" data-l10n-id="givenName" name="givenName" class="textfield" x-inputmode="latin-prose" value="" id="givenName" dir="auto" autocorrect="off" autocapitalize="on" spellcheck="false" type="text">
<button type="reset">Clear</button>
</p>
<p class="fillflow-row input-container">
<input placeholder="Last name" data-field="familyName" data-l10n-id="familyName" name="familyName" class="textfield" x-inputmode="latin-prose" value="" id="familyName" dir="auto" autocorrect="off" autocapitalize="on" spellcheck="false" type="text">
<button type="reset">Clear</button>
</p>
</div>
<div class="with-overflow">
<p class="fillflow-row">
<input placeholder="Company Name" data-field="org" data-l10n-id="org" name="org" class="textfield" x-inputmode="verbatim" value="" id="org" dir="auto" type="text">
<button type="reset" id="clear-org">Clear</button>
</p>
</div>
</section>
<ul role="presentation">
<li role="region" aria-label="Phones" data-l10n-id="phones">
<section>
<div id="contacts-form-phones" role="list"></div>
</section>
<button data-field-type="tel" id="add-new-phone" data-l10n-id="addPhone" class="fillflow-row action action-add icon icon-add">
Add phone number
</button>
<hr>
</li>
<li role="region" aria-label="Emails" data-l10n-id="emails">
<section>
<div id="contacts-form-emails" role="list"></div>
</section>
<button data-field-type="email" id="add-new-email" data-l10n-id="addEmail" class="fillflow-row action action-add icon icon-add">
Add email address
</button>
<hr>
</li>
<li role="region" aria-label="Addresses" data-l10n-id="addresses">
<section>
<div id="contacts-form-addresses" role="list"></div>
</section>
<button data-field-type="adr" id="add-new-address" data-l10n-id="addAddress" class="fillflow-row action action-add icon icon-add">
Add new address
</button>
<hr>
</li>
<li role="region" aria-label="Dates" data-l10n-id="dates">
<section>
<div id="contacts-form-dates" role="list"></div>
</section>
<button data-field-type="date" id="add-new-date" data-l10n-id="addDate" class="fillflow-row action action-add icon icon-add">
Add Date
</button>
<hr>
</li>
<li role="region" aria-label="Notes" data-l10n-id="notes">
<section>
<div id="contacts-form-notes" role="list"></div>
</section>
<button data-field-type="note" id="add-new-note" data-l10n-id="addComment" class="fillflow-row action action-add icon icon-add">
Add comment
</button>
<hr>
</li>
</ul>
</form>
<footer class="tail-actions hide">
<button id="delete-contact" class="fillflow-row action action-delete danger">
<b data-l10n-id="deleteContact">Delete contact</b>
</button>
</footer>
</div>
</article>
--></section>
<section id="view-select-tag" role="region" class="view view-right view-select-tag"><!--
<gaia-header id="settings-header" action="back">
<h1 data-l10n-id="label"></h1>
<button id="settings-done" data-l10n-id="done">Done</button>
</gaia-header>
<article class="view-body">
<form class="view-body-inner">
<ul id="tags-list" class="selection-list compact" role="listbox">
</ul>
<p>
<input class="textfield tagItem" placeholder="Custom Tag" value="" id="custom-tag" data-l10n-id="customTag" dir="auto" type="text">
<button id="custom-tag-reset" type="reset">Clear</button>
</p>
</form>
</article>
--></section>
<form id="action-menu" role="dialog" data-type="action" class="hide">
<header id="org-title" data-l10n-id="select_recipient">Select recipient</header>
<menu id="value-menu" type="toolbar">
</menu>
</form>
<article id="settings-wrapper"><!--
<section data-theme="organic" id="view-settings" role="region" class="skin-organic view view-bottom theme-settings">
<gaia-header>
<h1 data-l10n-id="settings"></h1>
<button id="settings-close" data-l10n-id="done">Done</button>
</gaia-header>
<article class="view-body" id="settings-article">
<section role="region" class="view-body-inner">
<ul data-type="list" role="presentation">
<li id="settingsOrder" role="checkbox">
<gaia-switch name="order.lastname">
<label data-l10n-id="contactsOrderBy"></label>
</gaia-switch>
</li>
</ul>
<gaia-subheader skin="organic" id="transfer-contacts-header">
<span data-l10n-id="transferContactsTitle">Transfer Contacts</span>
</gaia-subheader>
<ul data-type="list" id="sources" role="listbox" aria-labelledby="transfer-contacts-header">
<li role="presentation" id="importContacts" data-source="sim">
<button role="option" class="icon icon-import" data-l10n-id="importContactsButton">
Import Contacts
</button>
</li>
<li role="presentation" id="exportContacts" data-source="sd">
<button role="option" class="icon icon-export" data-l10n-id="exportContactsButton">
Export Contacts
</button>
</li>
</ul>
<gaia-subheader skin="organic" id="contacts-management-header">
<span data-l10n-id="ContactManagementTitle">Contacts Management</span>
</gaia-subheader>
<ul data-type="list" id="contactsManagement" role="listbox" aria-labelledby="contacts-management-header">
<li role="presentation">
<button role="option" id="set-ice" class="action" data-l10n-id="setICEButton">
Set ICE Contacts
</button>
</li>
<li role="presentation">
<button role="option" id="bulkDelete" class="action" data-l10n-id="bulkDeleteButton">
Delete Contacts
</button>
</li>
</ul>
</section>
</article>
</section>
<section data-theme="organic" id="import-settings" role="region" class="skin-organic view view-right theme-settings">
<gaia-header id="import-settings-header" action="back">
<h1 id="import-settings-title"></h1>
</gaia-header>
<article class="view-body">
<section role="region" class="view-body-inner">
<ul role="listbox" data-type="list" id="import-options">
<li role="presentation" id="import-sd-option" data-source="sd">
<button role="option" class="icon icon-sd">
<span data-l10n-id="importMemoryCard">Memory card</span>
<p><span></span><time></time></p>
</button>
<p class="error-message"></p>
</li>
<li role="presentation" id="import-gmail-option" class="importService" data-source="gmail">
<button role="option" class="icon icon-gmail">
<span data-l10n-id="importGmail">Gmail</span>
<p><span></span><time></time></p>
</button>
</li>
<li role="presentation" id="import-live-option" class="importService" data-source="live">
<button role="option" class="icon icon-live">
<span data-l10n-id="importOutlook">Outlook</span>
<p><span></span><time></time></p>
</button>
<p class="error-message" data-l10n-id="genericNoConnection"></p>
</li>
</ul>
<ul role="listbox" data-type="list" id="export-options">
<li role="presentation" id="export-sd-option" data-source="sd">
<button role="option" class="icon icon-sd" data-l10n-id="memoryCard">
Memory card
</button>
<p class="error-message"></p>
</li>
<li role="presentation" id="export-bluetooth-option" data-source="bluetooth">
<button role="option" class="icon icon-bluetooth" data-l10n-id="bluetooth">
Bluetooth
</button>
</li>
</ul>
</section>
</article>
</section>
<section data-theme="organic" id="ice-settings" role="region" class="skin-organic view view-right theme-settings">
<gaia-header id="ice-settings-header" action="back">
<h1 id="ice-settings-title" data-l10n-id="ICESettingTitle"></h1>
</gaia-header>
<article class="view-body">
<section role="region" class="view-body-inner">
<p class="description" data-l10n-id="ICEDescription">
Add two contacts for making “In Case of Emergency” calls.
</p>
<ul data-type="list" role="listbox" aria-multiselectable="true">
<li role="option" id="ice-contacts-1-switch" class="ice-contacts-switch">
<gaia-switch name="ice-contact-1-enabled">
<label data-l10n-id="ICEContact1"></label>
</gaia-switch>
</li>
<li role="presentation">
<button role="option" id="select-ice-contact-1" class="icon icon-view" data-l10n-id="ICESelectContact" disabled="">
Select a contact
</button>
</li>
<li role="option" id="ice-contacts-2-switch" class="ice-contacts-switch">
<gaia-switch name="ice-contact-2-enabled">
<label data-l10n-id="ICEContact2"></label>
</gaia-switch>
</li>
<li role="presentation">
<button role="option" id="select-ice-contact-2" class="icon icon-view" data-l10n-id="ICESelectContact" disabled="">
Select a contact
</button>
</li>
</ul>
</section>
</article>
</section>
--></article>
<form data-type="confirm" id="loading-overlay" class="hide no-opacity" role="dialog"><!--
<section id="progress-activity" class="hide">
<h1 id="progress-title"></h1>
<p role="status">
<span id="progress-msg"></span>
<progress id="progress-element" class="pack-activity" value="0" max="100"></progress>
</p>
</section>
<menu>
<button data-l10n-id="cancel" id="cancel-overlay">Cancel</button>
</menu>
--></form>
<section id="statusMsg" role="status" class="hidden"><!--
<p></p>
--></section>
<form id="confirmation-message" class="hide" role="dialog" data-type="confirm"><!--
<section>
<h1>Confirmation title</h1>
<p>Confirmation message</p>
</section>
<menu>
<button>Action 1</button>
<button>Action 2</button>
</menu>
--></form>
<section id="ice-view" role="region" class="view view-right hidden"><!--
<gaia-header id="ice-header" action="back">
<h1 data-l10n-id="ice_contacts"></h1>
</gaia-header>
<arcticle class="view-body">
<section id="ice-list" data-type="list" class="active">
<ol id="ice-group" role="">
</ol>
</section>
</arcticle>
--></section>
<section id="multiple-select-view" role="region" class="view hidden"><!--
<template id="contactToImportTemplate">
<li>
<aside class="pack-end" aria-hidden="true">
<span data-type="img"></span>
</aside>
<p>Name</p>
<p>Org</p>
</li>
</template>
<gaia-header id="multiple-select-view-header" action="close">
<h1 id="multiple-select-view-title">Filename</h1>
<button id="save-button" data-l10n-id="importAll">Import all</button>
</gaia-header>
<article class="view-body">
<ol id="multiple-select-container" class="view-body-inner" data-type="list">
</ol>
</article>
--></section>
<gaia-sim-picker id="sim-picker" tabindex="-1"></gaia-sim-picker>
</body>
</html>

View file

@ -0,0 +1,85 @@
/* exported ActionMenu */
'use strict';
function ActionMenu(title) {
var data;
var contactActionMenu;
var listContainer;
var btnCancel;
var titleId = title;
function init() {
contactActionMenu = document.getElementById('action-menu');
// Prevent submit in the form
contactActionMenu.addEventListener('submit', function(evt) {
evt.preventDefault();
});
if (titleId) {
contactActionMenu.querySelector('#org-title').setAttribute(
'data-l10n-id', titleId);
}
listContainer = document.getElementById('value-menu');
data = {
list: []
};
}
this.show = function() {
render(this);
contactActionMenu.classList.remove('hide');
};
this.hide = function() {
contactActionMenu.classList.add('hide');
emptyList();
listContainer.innerHTML = '';
};
function render(self) {
for (var i = 0, l = data.list.length; i < l; i++) {
var button = document.createElement('button');
navigator.mozL10n.setAttributes(button,
data.list[i].labelL10n.id,
data.list[i].labelL10n.args);
// Set callback function on each button element.
var callback = data.list[i].callback;
if (callback) {
button.addEventListener('click', callback, false);
}
listContainer.appendChild(button);
}
btnCancel = document.createElement('button');
btnCancel.setAttribute('data-l10n-id', 'cancel');
btnCancel.addEventListener('click', function() {
self.hide();
});
var lastButtonWrapper = document.createElement('span');
lastButtonWrapper.className = 'last-button-container';
listContainer.appendChild(lastButtonWrapper);
lastButtonWrapper.appendChild(btnCancel);
}
function emptyList() {
data.list = [];
}
this.addToList = function (labelL10n, callback) {
var self = this;
data.list.push({
labelL10n: labelL10n,
callback: function() {
callback();
self.hide();
}
});
};
init();
}

View file

@ -0,0 +1,370 @@
/* globals ConfirmDialog, Contacts, LazyLoader, utils, ActionMenu, HeaderUI,
ContactToVcardBlob, VcardFilename, VcardActivityHandler, MainNavigation */
/* exported ActivityHandler */
'use strict';
/**
* Per RFC 6350, text/vcard is the canonical MIME media type for vCards, but
* there are also deprecated types as well. Whenever we disambiguate what an
* activity is requesting based on its MIME media type, we need to check if it
* is any of these, and not just text/vcard.
*/
const VCARD_MIME_TYPES = [
'text/vcard',
'text/x-vcard',
'text/directory'
];
var ActivityHandler = {
_currentActivity: null,
_launchedAsInlineActivity: (window.location.search == '?pick'),
mozContactParam: null,
_actionMenu: null,
get currentlyHandling() {
return !!this._currentActivity;
},
get activityName() {
if (!this._currentActivity) {
return null;
}
return this._currentActivity.source.name;
},
get activityDataType() {
if (!this._currentActivity) {
return null;
}
return this._currentActivity.source.data.type;
},
get activityData() {
if (!this._currentActivity) {
return null;
}
return this._currentActivity.source.data;
},
get activityContactProperties() {
if (!this._currentActivity) {
return null;
}
return this._currentActivity.source.data.contactProperties;
},
/* checks first if we are handling an activity, then if it is
* of the same type of any of the items from the list provided.
* @param list Array with types of activities to be checked
*/
currentActivityIs: function(list) {
return this.currentlyHandling && list.indexOf(this.activityName) !== -1;
},
/* checks first if we are handling an activity, then checks that
* it is NOT of the same type of any of the items from the list provided.
* @param list Array with types of activities to be checked
*/
currentActivityIsNot: function(list) {
return this.currentlyHandling && list.indexOf(this.activityName) === -1;
},
isCancelable: function() {
return new Promise(resolve => {
resolve(!!this.currentlyHandling);
});
},
launch_activity: function ah_launch(activity, action) {
if (this._launchedAsInlineActivity) {
return;
}
this._currentActivity = activity;
this.isCancelable().then(isCancelable => {
HeaderUI.updateHeader(isCancelable);
});
var hash = action;
var param, params = [];
if (activity.source &&
activity.source.data &&
activity.source.data.params) {
var originalParams = activity.source.data.params;
for (var i in originalParams) {
param = originalParams[i];
params.push(i + '=' + param);
}
hash += '?' + params.join('&');
}
document.location.hash = hash;
},
handle: function ah_handle(activity) {
const VCARD_DEPS = '/contacts/js/activities_vcard.js';
switch (activity.source.name) {
case 'new':
this.launch_activity(activity, 'view-contact-form');
break;
case 'import':
case 'open':
LazyLoader.load(VCARD_DEPS, () => {
VcardActivityHandler.handle(activity, this);
});
break;
case 'update':
this.launch_activity(activity, 'add-parameters');
break;
case 'pick':
if (!this._launchedAsInlineActivity) {
return;
}
this._currentActivity = activity;
this.isCancelable().then(isCancelable => {
HeaderUI.updateHeader(isCancelable);
});
MainNavigation.home();
break;
}
},
importContactsFromFile: function ah_importContactFromVcard(activity) {
var self = this;
if (activity.source &&
activity.source.data &&
activity.source.data.blob) {
LazyLoader.load([
document.querySelector('#loading-overlay'),
'/shared/js/contacts/import/utilities/import_from_vcard.js',
'/shared/js/contacts/import/utilities/overlay.js'
], function loaded() {
Contacts.loadFacebook(function() {
utils.importFromVcard(activity.source.data.blob,
function imported(numberOfContacts, id) {
if (numberOfContacts === 1) {
activity.source.data.params = {id: id};
self.launch_activity(activity, 'view-contact-details');
} else {
self.launch_activity(activity, 'view-contact-list');
}
}
);
});
});
} else {
this._currentActivity.postError('wrong parameters');
this._currentActivity = null;
}
},
dataPickHandler: function ah_dataPickHandler(theContact) {
var type, dataSet, noDataStr;
var result = {};
var self = this;
// Keeping compatibility with previous implementation. If
// we want to get the full contact, just pass the parameter
// 'fullContact' equal true.
if (this.activityDataType === 'webcontacts/contact' &&
this.activityData.fullContact === true) {
result = utils.misc.toMozContact(theContact);
this.postPickSuccess(result);
return;
}
// Was this a request for vCard export as a blob? Check all supported MIME
// types.
var isVcardDataType = VCARD_MIME_TYPES.some((mime) => {
return this.activityDataType.indexOf(mime) !== -1;
});
if (isVcardDataType) {
// Normalize the type to text/vcard so other places that check MIME types
// (ex: the Facebook guards) experience a consistent MIME type.
this._currentActivity.source.data.type = 'text/vcard';
LazyLoader.load([
'/shared/js/text_normalizer.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js'
], function lvcard() {
ContactToVcardBlob([theContact], function blobReady(vcardBlob) {
VcardFilename(theContact).then(filename => {
self.postPickSuccess({
name: filename,
blob: vcardBlob
});
});
}, {
// Some MMS gateways prefer this MIME type for vcards
type: 'text/x-vcard'
});
});
return;
}
switch (this.activityDataType) {
case 'webcontacts/tel':
type = 'contact';
dataSet = theContact.tel;
noDataStr = 'no_contact_phones';
break;
case 'webcontacts/contact':
type = 'number';
dataSet = theContact.tel;
noDataStr = 'no_contact_phones';
break;
case 'webcontacts/email':
type = 'email';
dataSet = theContact.email;
noDataStr = 'no_contact_email';
break;
case 'webcontacts/select':
type = 'select';
var data = [];
if (this.activityContactProperties.indexOf('tel') !== -1) {
if (theContact.tel && theContact.tel.length) {
data = data.concat(theContact.tel);
}
}
if (this.activityContactProperties.indexOf('email') !== -1) {
if (theContact.email && theContact.email.length) {
data = data.concat(theContact.email);
}
}
dataSet = data;
noDataStr = 'no_contact_data_available';
break;
}
var hasData = dataSet && dataSet.length;
var numOfData = hasData ? dataSet.length : 0;
result.name = theContact.name;
switch (numOfData) {
case 0:
// If no required type of data
var dismiss = {
title: 'ok',
callback: function() {
ConfirmDialog.hide();
}
};
ConfirmDialog.show(null, noDataStr, dismiss);
break;
case 1:
// if one required type of data
if (this.activityDataType == 'webcontacts/tel' ||
this.activityDataType == 'webcontacts/select') {
result = this.pickContactsResult(theContact);
} else {
result[type] = dataSet[0].value;
}
this.postPickSuccess(result);
break;
default:
// if more than one required type of data
LazyLoader.load('/contacts/js/action_menu.js', function() {
if (!self._actionMenu) {
self._actionMenu = new ActionMenu();
} else {
// To be sure that the action menu is empty
self._actionMenu.hide();
}
var itemData;
var capture = function(itemData) {
return function() {
if (self.activityDataType == 'webcontacts/tel' ||
self.activityDataType == 'webcontacts/select') {
result = self.pickContactsResult(theContact, itemData);
} else {
result[type] = itemData;
}
self._actionMenu.hide();
self.postPickSuccess(result);
};
};
for (var i = 0, l = dataSet.length; i < l; i++) {
itemData = dataSet[i].value;
var carrier = dataSet[i].carrier || '';
self._actionMenu.addToList(
{
id: 'pick_destination',
args: {destination: itemData, carrier: carrier}
},
capture(itemData)
);
}
self._actionMenu.show();
});
} // switch
},
pickContactsResult:
function ah_pickContactsResult(theContact, itemData) {
var pickResult = {};
var contact = utils.misc.toMozContact(theContact);
if (this.activityDataType == 'webcontacts/tel') {
pickResult = contact;
if (itemData) {
pickResult.tel = this.filterDestinationForActivity(
itemData, pickResult.tel);
}
} else if (this.activityDataType == 'webcontacts/select') {
pickResult.contact = contact;
if (!itemData) {
pickResult.select = pickResult.contact.tel;
if (!pickResult.select || !pickResult.select.length) {
pickResult.select = pickResult.contact.email;
}
} else {
pickResult.select = this.filterDestinationForActivity(
itemData, pickResult.contact.tel);
if (!pickResult.select || !pickResult.select.length) {
pickResult.select = this.filterDestinationForActivity(
itemData, pickResult.contact.email);
}
}
}
return pickResult;
},
filterDestinationForActivity:
function ah_filterDestinationForActivity(itemData, dataSet) {
return dataSet.filter(function isSamePhone(item) {
return item.value == itemData;
});
},
postNewSuccess: function ah_postNewSuccess(contact) {
this._currentActivity.postResult({ contact: contact });
this._currentActivity = null;
},
postPickSuccess: function ah_postPickSuccess(result) {
this._currentActivity.postResult(result);
this._currentActivity = null;
},
postCancel: function ah_postCancel() {
this._currentActivity.postError('canceled');
this._currentActivity = null;
}
};

View file

@ -0,0 +1,66 @@
'use strict';
/* global LazyLoader, VCardReader, Contacts */
/* exported VcardActivityHandler */
var VcardActivityHandler = (function() {
var handle = function(activity, parentHandler) {
const DEPENDENCIES = '/shared/js/contacts/import/utilities/vcard_reader.js';
LazyLoader.load(DEPENDENCIES, function () {
getvCardReader(activity.source.data.blob).then(readvCard, (err) => {
console.error('Error while getting vCard reader:', err);
Contacts.cancel();
});
});
function getvCardReader(blob) {
return new Promise((resolve, reject) => {
var fileReader = new FileReader();
fileReader.readAsText(blob);
fileReader.onloadend = function() {
resolve(new VCardReader(fileReader.result));
};
fileReader.onerror = function() {
console.error('The blob could not be read as a file.');
reject(fileReader.error);
};
});
}
function readvCard(vCardReader) {
var cursor = vCardReader.getAll();
cursor.onsuccess = function(event) {
var contact = event.target.result;
render([contact], activity, cursor);
};
}
function getFileName(path) {
var barIndex = path.lastIndexOf('/');
if (barIndex > -1) {
path = path.substring(barIndex + 1);
}
return path;
}
function render(contacts, activity, cursor) {
parentHandler.launch_activity(activity, 'multiple-select-view');
const DEPENDENCIES = [
'/contacts/js/views/multiple_select.js'
];
LazyLoader.load(DEPENDENCIES, function() {
var name;
if (activity.source.data.filename) {
name = getFileName(activity.source.data.filename);
} else {
name = activity.source.data.src;
}
Contacts.MultipleSelect.init();
Contacts.MultipleSelect.render(contacts, cursor, name);
});
}
};
return {
handle: handle
};
})();

View file

@ -0,0 +1,4 @@
;'use strict';(function(exports){function loadScripts(){var dependencies=['/contacts/services/contacts.js','/contacts/js/activities.js','/shared/js/contacts/utilities/event_listeners.js','/contacts/js/navigation.js','/contacts/js/main_navigation.js','/contacts/js/views/list.js','/contacts/js/header_ui.js'];if(!Cache.active){dependencies.push('/shared/js/l10n.js');}
LazyLoader.load(dependencies,()=>{['/shared/js/async_storage.js','/shared/js/contacts/import/utilities/config.js','/contacts/js/utilities/extract_params.js','/contacts/js/utilities/cookie.js','/shared/js/contact_photo_helper.js'].forEach((src)=>{var scriptNode=document.createElement('script');scriptNode.src=src;scriptNode.setAttribute('defer',true);document.head.appendChild(scriptNode);});return LazyLoader.load('/contacts/js/contacts.js');});}
window.addEventListener('DOMContentLoaded',function ondomloaded(){utils.PerformanceHelper.domLoaded();window.removeEventListener('DOMContentLoaded',ondomloaded);var firstPaintFired=false;Cache.apply('firstChunk').then(()=>{document.getElementById('view-contacts-list').classList.add('current');utils.PerformanceHelper.visuallyComplete();firstPaintFired=true;});window.onload=()=>{if(!firstPaintFired){utils.PerformanceHelper.visuallyComplete();}
loadScripts();};});})(window);

View file

@ -0,0 +1,54 @@
;;'use strict';(function(){const CACHE_ENABLED=true;const FIRST_CHUNK='firstChunk';const l10nStrings={'title':{query:'gaia-header h1#app-title[data-l10n-id="contacts"]',container:'textContent'},'search':{query:'input[data-l10n-id="search-contact"]',container:'placeholder'}};var _observers={};function addObserver(aMessage,aCallback){if(typeof aCallback!=='function'){return;}
if(!_observers[aMessage]){_observers[aMessage]=[];}
_observers[aMessage].push(aCallback);}
function notifyObservers(aMessage,aData){var observers=_observers[aMessage];if(!observers){return;}
for(var callback in observers){if(observers[callback]){observers[callback](aData);}}}
var _caches=new Map();_caches.set(FIRST_CHUNK,{containerId:'groups-list',active:false});var _cachedContacts;var _cachedFavorites;var _cachedHeaders;var _cacheEvictionTimer;function setCache(aCache){if(!CACHE_ENABLED||!aCache.id||!aCache.content||!aCache.lastOrderString||!_caches.has(aCache.id)){return;}
var l10n={};Object.keys(l10nStrings).forEach(key=>{var string=l10nStrings[key];var selector=document.querySelector(string.query);if(!selector||!selector[string.container]){return;}
l10n[key]=selector[string.container];});localStorage.setItem(aCache.id,JSON.stringify({content:aCache.content,languageDirection:navigator.mozL10n.language.direction,languageCode:navigator.mozL10n.language.code,lastOrderString:aCache.lastOrderString,updated:Date.now(),l10n:l10n}));}
function deleteCache(aId){if(!CACHE_ENABLED){return;}
localStorage.removeItem(aId);}
function getCachedContacts(aNodeList){if(!CACHE_ENABLED||!aNodeList){return;}
if(!_cachedContacts){_cachedContacts=new Map();}
if(!_cachedFavorites){_cachedFavorites=new Map();}
for(var i=0;i<aNodeList.length;i++){var cacheEntry=aNodeList[i];if(cacheEntry.parentNode.id=='contact-list-ice'){continue;}
if(cacheEntry.parentNode.id=='contacts-list-favorites'){_cachedFavorites.set(cacheEntry.dataset.uuid,cacheEntry.innerHTML);continue;}
_cachedContacts.set(cacheEntry.dataset.uuid,cacheEntry.innerHTML);}}
function appendNodesToContainer(aContainer,aNodeList){if(!CACHE_ENABLED||!aNodeList.length){return Promise.resolve();}
return new Promise((resolve)=>{if(!_cachedHeaders){_cachedHeaders={};}
var resolved=false;var fragment=document.createDocumentFragment();aNodeList.forEach((node)=>{if(!node.elementName){return;}
var child=document.createElement(node.elementName);node.attributes&&node.attributes.forEach((attribute)=>{if(!attribute.name||!attribute.value){return;}
child.setAttribute(attribute.name,attribute.value);});if(node.innerHTML){child.innerHTML=node.innerHTML;}
fragment.appendChild(child);var headerName=child.id.split('section-group-')[1];_cachedHeaders[headerName]=fragment.querySelector('ol#contacts-list-'+headerName);if(!resolved){resolve();}});getCachedContacts(fragment.querySelectorAll('li[data-cache=true]'));aContainer.appendChild(fragment);});}
function removeCacheFromContainer(aContainer){if(!CACHE_ENABLED||!_cachedContacts){return;}
for(var id of _cachedContacts.keys()){var items=aContainer.querySelectorAll('li[data-uuid=\"'+id+'\"]');Array.prototype.forEach.call(items,(item)=>{var ol=item.parentNode;ol.removeChild(item);if(ol.children.length<1){var header=_cachedHeaders[ol.dataset.group];if(header){var groupTitle=header.parentNode.children[0];groupTitle.classList.add('hide');}}});}}
function undoCache(aCacheId){if(!CACHE_ENABLED||!_caches.has(aCacheId)){return;}
var _cache=_caches.get(aCacheId);if(!_cache.containerId){return;}
var container=document.getElementById(_cache.containerId);if(!container){console.warning('Could not remove cached content from '+
_cache.containerId);return;}
removeCacheFromContainer(container);}
var Cache={get active(){return CACHE_ENABLED&&_caches.get(FIRST_CHUNK).active;},get enabled(){return CACHE_ENABLED;},get contacts(){return(CACHE_ENABLED&&_cachedContacts)?_cachedContacts.keys():null;},get favorites(){return(CACHE_ENABLED&&_cachedFavorites)?_cachedFavorites.keys():null;},get length(){return(CACHE_ENABLED&&_cachedContacts)?_cachedContacts.size+_cachedFavorites.size:0;},get headers(){return(CACHE_ENABLED&&_cachedHeaders)?_cachedHeaders:{};},get lastOrderString(){return(CACHE_ENABLED&&_caches.get(FIRST_CHUNK).lastOrderString);},get updated(){return(CACHE_ENABLED&&_caches.get(FIRST_CHUNK).updated);},get _rawContent(){return _caches.get(FIRST_CHUNK).content;},set firstChunk(aFirstChunk){if(!CACHE_ENABLED||!aFirstChunk||!aFirstChunk.cache||!aFirstChunk.lastOrderString){return;}
setCache({id:FIRST_CHUNK,content:aFirstChunk.cache,lastOrderString:aFirstChunk.lastOrderString});},set oneviction(aCallback){if(!CACHE_ENABLED){return;}
addObserver('oneviction',aCallback);},hasContact:function(aUuid){if(!CACHE_ENABLED){return;}
return _cachedContacts&&_cachedContacts.has(aUuid);},hasFavorite:function(aUuid){if(!CACHE_ENABLED){return;}
return _cachedFavorites&&_cachedFavorites.has(aUuid);},getContact:function(aUuid){if(!CACHE_ENABLED||!this.hasContact(aUuid)){return;}
var contact=_cachedContacts.get(aUuid);_cachedContacts.delete(aUuid);return contact;},getFavorite:function(aUuid){if(!CACHE_ENABLED||!this.hasFavorite(aUuid)){return;}
var contact=_cachedFavorites.get(aUuid);_cachedFavorites.delete(aUuid);return contact;},removeContact:function(aUuid){if(!CACHE_ENABLED||!this.hasContact(aUuid)||!this.hasFavorite(aUuid)){return;}
if(_cachedContacts.has(aUuid)){_cachedContacts.delete(aUuid);}
if(_cachedFavorites.has(aUuid)){_cachedFavorites.delete(aUuid);}},_doEvict:function(aUndo){_cacheEvictionTimer=null;if(aUndo){undoCache(FIRST_CHUNK);}
deleteCache(FIRST_CHUNK);this.cleanup();notifyObservers('oneviction');},evict:function(aUndo,aInstant){if(!CACHE_ENABLED){return;}
if(aInstant){this._doEvict(aUndo);return;}
if(_cacheEvictionTimer){clearTimeout(_cacheEvictionTimer);}
_cacheEvictionTimer=setTimeout(()=>{this._doEvict(aUndo);},1000);},maybeEvict:function(){if(!CACHE_ENABLED||!this.active){return;}
if((_caches.get(FIRST_CHUNK).languageDirection!=navigator.mozL10n.language.direction)||(_caches.get(FIRST_CHUNK).languageCode!=navigator.mozL10n.language.code)){this.evict(true);}},cleanup:function(){if(!CACHE_ENABLED){return;}
_caches.get(FIRST_CHUNK).active=false;_caches.get(FIRST_CHUNK).content=null;_cachedContacts=null;_cachedFavorites=null;_cachedHeaders=null;},apply:function(aCacheId){if(!CACHE_ENABLED||!_caches.has(aCacheId)){return Promise.reject();}
var _cache=_caches.get(aCacheId);var cache=localStorage.getItem(aCacheId);if(!cache){return Promise.reject();}
try{cache=JSON.parse(cache);_cache.content=cache.content;_cache.languageDirection=cache.languageDirection;_cache.languageCode=cache.languageCode;_cache.lastOrderString=cache.lastOrderString;_cache.updated=new Date(cache.updated);}catch(e){console.error(e);return Promise.reject();}
if(!_cache.containerId){return Promise.reject();}
if(cache.l10n){Object.keys(cache.l10n).forEach(key=>{if(!l10nStrings[key]||cache.l10n[key]==='undefined'){return;}
var string=l10nStrings[key];var selector=document.querySelector(string.query);if(!selector){return;}
selector[string.container]=cache.l10n[key];});}
if(cache.languageDirection){var html=document.querySelector('html');html.setAttribute('dir',cache.languageDirection);}
var container=document.getElementById(_cache.containerId);if(!container){console.warning('Could not apply cached content to '+
_cache.containerId);return Promise.reject();}
return appendNodesToContainer(container,_cache.content).then(()=>{_cache.content=null;_cache.active=true;_caches.set(aCacheId,_cache);return Promise.resolve();});}};window.Cache=Cache;})();

View file

@ -0,0 +1,708 @@
'use strict';
/* global ActivityHandler */
/* global Cache */
/* global contacts */
/* global DeferredActions */
/* global fb */
/* global fbLoader */
/* global LazyLoader */
/* global MainNavigation */
/* global Loader */
/* global TAG_OPTIONS */
/* global utils */
/* global HeaderUI */
/* global Search */
/* global ContactsService */
/* exported COMMS_APP_ORIGIN */
/* exported SCALE_RATIO */
/* jshint nonew: false */
var COMMS_APP_ORIGIN = location.origin;
// Scale ratio for different devices
var SCALE_RATIO = window.devicePixelRatio || 1;
var Contacts = (function() {
var goToForm = function edit() {
var transition = ActivityHandler.currentlyHandling ? 'activity-popup'
: 'fade-in';
MainNavigation.go('view-contact-form', transition);
};
var loadAsyncScriptsDeferred = {};
loadAsyncScriptsDeferred.promise = new Promise((resolve) => {
loadAsyncScriptsDeferred.resolve = resolve;
});
var settingsReady = false;
var detailsReady = false;
var formReady = false;
var currentContact = {},
currentFbContact;
var contactsList;
var contactsDetails;
var contactsForm;
// Shows the edit form for the current contact being in an update activity
// It receives an array of two elements with the facebook data && values
function showEditForm(facebookData, params) {
contactsForm.render(currentContact, goToForm,
facebookData, params.fromUpdateActivity);
}
var checkUrl = function checkUrl() {
var hasParams = window.location.hash.split('?');
var hash = hasParams[0];
var sectionId = hash.substr(1, hash.length) || '';
var params = hasParams.length > 1 ?
utils.extractParams(hasParams[1]) : -1;
switch (sectionId) {
case 'view-contact-list':
initContactsList();
break;
case 'view-contact-details':
initContactsList();
initDetails(function onInitDetails() {
// At this point, a parameter is required
if (params == -1) {
console.error('Param missing');
return;
}
// If the parameter is an id, the corresponding contact is loaded
// from the device.
if ('id' in params) {
var id = params.id;
ContactsService.get(id, function onSuccess(savedContact) {
currentContact = savedContact;
contactsDetails.render(currentContact);
MainNavigation.go(sectionId, 'right-left');
}, function onError() {
console.error('Error retrieving contact');
});
// If mozContactParam is true, we know there is a mozContact
// attached to the activity, so we render it using contacts details'
// read only mode. This is used when we receive an activity to open
// a given contact with allowSave set to false.
} else if (params.mozContactParam) {
var contact = ActivityHandler.mozContactParam;
contactsDetails.render(contact, null, true);
MainNavigation.go(sectionId, 'activity-popup');
}
});
break;
case 'view-contact-form':
initForm(function onInitForm() {
if (params.mozContactParam) {
contactsForm.render(ActivityHandler.mozContactParam, goToForm);
ActivityHandler.mozContactParam = null;
} else if (params == -1 || !(params.id)) {
contactsForm.render(params, goToForm);
} else {
// Editing existing contact
if (params.id) {
var id = params.id;
ContactsService.get(id, function onSuccess(savedContact) {
currentContact = savedContact;
// Check if we have extra parameters to render
if ('extras' in params) {
addExtrasToContact(params.extras);
}
if (fb.isFbContact(savedContact)) {
var fbContact = new fb.Contact(savedContact);
var req = fbContact.getDataAndValues();
req.onsuccess = function() {
showEditForm(req.result, params);
};
req.onerror = function() {
console.error('Error retrieving FB information');
showEditForm(null, params);
};
}
else {
showEditForm(null, params);
}
}, function onError() {
console.error('Error retrieving contact to be edited');
contactsForm.render(null, goToForm);
});
}
}
});
break;
case 'add-parameters':
initContactsList();
initForm(function onInitForm() {
MainNavigation.home();
if (ActivityHandler.currentlyHandling) {
selectList(params, true);
}
});
break;
case 'multiple-select-view':
Loader.view('multiple_select', () => {
MainNavigation.go('multiple-select-view', 'activity-popup');
});
break;
case 'home':
MainNavigation.home();
break;
}
};
var addExtrasToContact = function addExtrasToContact(extrasString) {
try {
var extras = JSON.parse(decodeURIComponent(extrasString));
for (var type in extras) {
var extra = extras[type];
if (currentContact[type]) {
if (Array.isArray(currentContact[type])) {
var joinArray = currentContact[type].concat(extra);
currentContact[type] = joinArray;
} else {
currentContact[type] = extra;
}
} else {
currentContact[type] = Array.isArray(extra) ? extra : [extra];
}
}
} catch (e) {
console.error('Extras malformed');
return null;
}
};
var onLocalized = function onLocalized() {
init();
// We need to return the promise here for testing purposes
return addAsyncScripts().then(() => {
checkUrl();
if (!ActivityHandler.currentlyHandling ||
ActivityHandler.currentActivityIs(['pick', 'update'])) {
initContactsList();
} else {
// Unregister here to avoid un-necessary list operations.
ContactsService.removeListener('contactchange', oncontactchange);
}
if (contactsList) {
contactsList.initAlphaScroll();
}
});
};
var loadDeferredActions = function loadDeferredActions() {
window.removeEventListener('listRendered', loadDeferredActions);
LazyLoader.load([
'js/deferred_actions.js',
'/contacts/js/fb_loader.js',
'/contacts/js/fb/fb_init.js'
], function() {
DeferredActions.execute();
});
};
var init = function init() {
window.addEventListener('hashchange', checkUrl);
window.addEventListener('listRendered', loadDeferredActions);
/* Tell the audio channel manager that we want to adjust the "notification"
* channel when the user presses the volumeup/volumedown buttons. */
if (navigator.mozAudioChannelManager) {
navigator.mozAudioChannelManager.volumeControlChannel = 'notification';
}
};
var initContactsList = function initContactsList() {
if (contactsList) {
return;
}
contactsList = contactsList || contacts.List;
var list = document.getElementById('groups-list');
contactsList.init(list);
getFirstContacts();
contactsList.initAlphaScroll();
contactsList.handleClick(contactListClickHandler);
ActivityHandler.isCancelable().then(isCancelable => {
HeaderUI.updateHeader(isCancelable);
});
};
var contactListClickHandler = function originalHandler(id) {
initDetails(function onDetailsReady() {
ContactsService.get(id, function findCb(contact, fbContact) {
currentContact = contact;
currentFbContact = fbContact;
if (ActivityHandler.currentActivityIsNot(['import'])) {
if (ActivityHandler.currentActivityIs(['pick'])) {
ActivityHandler.dataPickHandler(currentFbContact || currentContact);
}
return;
}
contactsDetails.render(currentContact, currentFbContact);
if (window.Search && Search.isInSearchMode()) {
MainNavigation.go('view-contact-details', 'go-deeper-search');
} else {
MainNavigation.go('view-contact-details', 'go-deeper');
}
});
});
};
var updateContactDetail = function updateContactDetail(id) {
ContactsService.get(id, function findCallback(contact) {
currentContact = contact;
contactsDetails.render(currentContact);
});
};
var selectList = function selectList(params, fromUpdateActivity) {
HeaderUI.hideAddButton();
contactsList.clearClickHandlers();
contactsList.handleClick(function addToContactHandler(id) {
var data = {};
if (params.hasOwnProperty('tel')) {
var phoneNumber = params.tel;
data.tel = [{
'value': phoneNumber,
'carrier': null,
'type': [TAG_OPTIONS['phone-type'][0].type]
}];
}
if (params.hasOwnProperty('email')) {
var email = params.email;
data.email = [{
'value': email,
'type': [TAG_OPTIONS['email-type'][0].type]
}];
}
var hash = '#view-contact-form?extras=' +
encodeURIComponent(JSON.stringify(data)) + '&id=' + id;
if (fromUpdateActivity) {
hash += '&fromUpdateActivity=1';
}
window.location.hash = hash;
});
};
var handleBack = function handleBack(cb) {
MainNavigation.back(cb);
};
var handleCancel = function handleCancel() {
//If in an activity, cancel it
if (ActivityHandler.currentlyHandling) {
ActivityHandler.postCancel();
MainNavigation.home();
} else {
handleBack(function() {
// TODO: remove all interaction with detail.js when it works
// as an independent view
if (MainNavigation.currentView() === 'view-contact-details') {
contactsDetails.startNFC(currentContact);
}
});
}
};
var showAddContact = function showAddContact() {
showForm();
};
var loadFacebook = function loadFacebook(callback) {
LazyLoader.load([
'/contacts/js/fb_loader.js',
'/contacts/js/fb/fb_init.js'
], () => {
if (!fbLoader.loaded) {
fb.init(function onInitFb() {
window.addEventListener('facebookLoaded', function onFbLoaded() {
window.removeEventListener('facebookLoaded', onFbLoaded);
callback();
});
fbLoader.load();
});
} else {
callback();
}
});
};
var initForm = function c_initForm(callback) {
if (formReady) {
callback();
} else {
initDetails(function onDetails() {
LazyLoader.load([
'/shared/js/contacts/import/utilities/misc.js',
'/shared/js/contacts/utilities/image_thumbnail.js',
'/contacts/js/match_service.js'],
function() {
Loader.view('Form', function viewLoaded() {
formReady = true;
contactsForm = contacts.Form;
contactsForm.init(TAG_OPTIONS);
callback();
});
});
});
}
};
var initSettings = function c_initSettings(callback) {
if (settingsReady) {
callback();
} else {
Loader.view('Settings', function viewLoaded() {
LazyLoader.load(['/contacts/js/utilities/sim_dom_generator.js',
'/contacts/js/utilities/normalizer.js',
'/shared/js/contacts/import/utilities/misc.js',
'/shared/js/mime_mapper.js',
'/shared/js/contacts/import/utilities/vcard_parser.js',
'/contacts/js/utilities/icc_handler.js',
'/shared/js/contacts/import/utilities/sdcard.js',
'/shared/elements/gaia_switch/script.js',
'/shared/js/date_time_helper.js',
'/shared/js/contacts/import/import_status_data.js'], function() {
settingsReady = true;
contacts.Settings.init();
callback();
});
});
}
};
var initDetails = function c_initDetails(callback) {
if (detailsReady) {
callback();
} else {
Loader.view('Details', function viewLoaded() {
LazyLoader.load(
['/shared/js/contacts/import/utilities/misc.js',
'/dialer/js/telephony_helper.js',
'/shared/js/contacts/sms_integration.js',
'/shared/js/contacts/contacts_buttons.js',
'/contacts/js/match_service.js',
'/contacts/js/utilities/mozContact.js'],
function() {
detailsReady = true;
contactsDetails = contacts.Details;
contactsDetails.init();
callback();
});
});
}
};
var showForm = function c_showForm(edit, contact) {
currentContact = contact || currentContact;
initForm(function onInit() {
doShowForm(edit);
});
};
var doShowForm = function c_doShowForm(edit) {
var contact = edit ? currentContact : null;
if (contact && fb.isFbContact(contact)) {
var fbContact = new fb.Contact(contact);
var req = fbContact.getDataAndValues();
req.onsuccess = function() {
contactsForm.render(contact, goToForm, req.result);
};
req.onerror = function() {
contactsForm.render(contact, goToForm);
};
}
else {
contactsForm.render(contact, goToForm);
}
};
var setCurrent = function c_setCurrent(contact) {
currentContact = contact;
if (contacts.Details) {
contacts.Details.setContact(contact);
}
};
var showOverlay = function c_showOverlay(messageId, progressClass, textId) {
var out = utils.overlay.show(messageId, progressClass, textId);
// When we are showing the overlay we are often performing other
// significant work, such as importing. While performing this work
// it would be nice to avoid the overhead of any accidental reflows
// due to touching the list DOM. For example, importing incrementally
// adds contacts to the list which triggers many reflows. Therefore,
// minimize this impact by hiding the list while we are showing the
// overlay.
contacts.List.hide();
return out;
};
var hideOverlay = function c_hideOverlay() {
Loader.utility('Overlay', function _loaded() {
contacts.List.show();
utils.overlay.hide();
});
};
var showSettings = function showSettings() {
initSettings(function onSettingsReady() {
// The number of FB Friends has to be recalculated
contacts.Settings.refresh();
MainNavigation.go('view-settings', 'fade-in');
});
};
var stopPropagation = function stopPropagation(evt) {
evt.preventDefault();
};
var enterSearchMode = function enterSearchMode(evt) {
Loader.view('Search', function viewLoaded() {
contacts.List.initSearch(function onInit() {
Search.enterSearchMode(evt);
});
});
};
var initEventListeners = function initEventListener() {
// Definition of elements and handlers
utils.listeners.add({
'#add-contact-button': showAddContact,
'#settings-button': showSettings, // Settings related
'#search-start': [
{
event: 'click',
handler: enterSearchMode
}
],
// For screen reader users
'#search-start > input': [
{
event: 'focus',
handler: enterSearchMode
}
],
'button[type="reset"]': stopPropagation
});
};
var getFirstContacts = function c_getFirstContacts() {
var onerror = function() {
console.error('Error getting first contacts');
};
contactsList = contactsList || contacts.List;
contactsList.getAllContacts(onerror);
};
var addAsyncScripts = function addAsyncScripts() {
var lazyLoadFiles = [
'/shared/js/contacts/utilities/templates.js',
'/shared/js/contacts/contacts_shortcuts.js',
'/contacts/js/contacts_tag.js',
'/contacts/js/tag_options.js',
'/contacts/js/loader.js',
'/shared/js/text_normalizer.js',
'/shared/js/contacts/import/utilities/status.js',
'/shared/js/contacts/utilities/dom.js',
'/shared/js/confirm.js',
document.getElementById('confirmation-message')
];
LazyLoader.load(lazyLoadFiles, function() {
loadAsyncScriptsDeferred.resolve();
});
return loadAsyncScriptsDeferred.promise;
};
var pendingChanges = {};
// This function is called when we finish a oncontactchange operation to
// remove the op of the pending changes and check if we need to apply more
// changes request over the same id.
var checkPendingChanges = function checkPendingChanges(id) {
var changes = pendingChanges[id];
if (!changes) {
return;
}
pendingChanges[id].shift();
if (pendingChanges[id].length >= 1) {
performOnContactChange(pendingChanges[id][0]);
}
};
var oncontactchange = function oncontactchange(event) {
if (typeof pendingChanges[event.contactID] !== 'undefined') {
pendingChanges[event.contactID].push({
contactID: event.contactID,
reason: event.reason
});
} else {
pendingChanges[event.contactID] = [{
contactID: event.contactID,
reason: event.reason
}];
}
// If there is already a pending request, don't do anything,
// just wait to finish it in order
if (pendingChanges[event.contactID].length > 1) {
return;
}
performOnContactChange(event);
};
ContactsService.addListener('contactchange', oncontactchange);
var performOnContactChange = function performOnContactChange(event) {
// To be on the safe side for now we evict the cache everytime a
// contact change event is received. In the future, we may want to check
// if the change affects the cache or not, so we avoid evicting it when
// is not needed.
Cache.evict();
initContactsList();
var currView = MainNavigation.currentView();
switch (event.reason) {
case 'update':
if (currView == 'view-contact-details' && currentContact != null &&
currentContact.id == event.contactID) {
ContactsService.get(event.contactID,
function success(contact, enrichedContact) {
currentContact = contact;
if (contactsDetails) {
contactsDetails.render(currentContact, enrichedContact);
}
if (contactsList) {
contactsList.refresh(enrichedContact || currentContact,
checkPendingChanges, event.reason);
}
notifyContactChanged(event.contactID, event.reason);
});
} else {
refreshContactInList(event.contactID);
}
break;
case 'create':
refreshContactInList(event.contactID);
break;
case 'remove':
if (currentContact != null && currentContact.id == event.contactID &&
(currView == 'view-contact-details' ||
currView == 'view-contact-form')) {
MainNavigation.home();
}
contactsList.remove(event.contactID, event.reason);
currentContact = {};
checkPendingChanges(event.contactID);
notifyContactChanged(event.contactID, event.reason);
break;
}
};
// Refresh a contact in the list, and notifies of contact
// changed to possible listeners.
function refreshContactInList(id) {
contactsList.refresh(id, function() {
notifyContactChanged(id);
checkPendingChanges(id);
});
}
// Send a custom event when we know that a contact changed and
// the contact list was updated.
// Used internally in places where the contact list is a reference
function notifyContactChanged(id, reason) {
document.dispatchEvent(new CustomEvent('contactChanged', {
detail: {
contactID: id,
reason: reason
}
}));
}
var close = function close() {
window.removeEventListener('localized', initContacts);
};
var initContacts = function initContacts(evt) {
initEventListeners();
HeaderUI.setNormalHeader();
utils.PerformanceHelper.contentInteractive();
utils.PerformanceHelper.chromeInteractive();
window.setTimeout(Contacts && Contacts.onLocalized);
if (window.navigator.mozSetMessageHandler && window.self == window.top) {
LazyLoader.load(['/shared/js/contacts/import/utilities/misc.js',
'/shared/js/contacts/import/utilities/vcard_reader.js',
'/shared/js/contacts/import/utilities/vcard_parser.js'],
function() {
var actHandler = ActivityHandler.handle.bind(ActivityHandler);
window.navigator.mozSetMessageHandler('activity', actHandler);
});
}
document.addEventListener('visibilitychange', function visibility(e) {
if (document.hidden === false &&
MainNavigation.currentView() === 'view-settings') {
Loader.view('Settings', function viewLoaded() {
contacts.Settings.updateTimestamps();
});
}
});
};
LazyLoader.load('/shared/js/l10n.js', () => {
navigator.mozL10n.once(() => {
initContacts();
});
navigator.mozL10n.ready(() => {
Cache.maybeEvict();
});
});
window.addEventListener('DOMContentLoaded', function onLoad() {
window.removeEventListener('DOMContentLoaded', onLoad);
});
return {
'goBack' : handleBack,
'cancel': handleCancel,
'showForm': showForm,
'setCurrent': setCurrent,
'onLocalized': onLocalized,
'init': init,
'showOverlay': showOverlay,
'hideOverlay': hideOverlay,
'showContactDetail': contactListClickHandler,
'updateContactDetail': updateContactDetail,
'loadFacebook': loadFacebook,
'close': close,
get asyncScriptsLoaded() {
return loadAsyncScriptsDeferred.promise;
}
};
})();

View file

@ -0,0 +1,108 @@
/* globals Contacts, utils, contactsRemover, Promise,
Search, ConfirmDialog, Loader */
'use strict';
var contacts = window.contacts || {};
(function(exports) {
var cancelled = false;
/**
* Loads the overlay class before showing
*/
function requireOverlay(callback) {
Loader.utility('Overlay', callback);
}
// Shows a dialog to confirm the bulk delete
var showConfirm = function showConfirm(n) {
return new Promise(function doShowConfirm(resolve, reject) {
var cancelObject = {
title: 'cancel',
callback: function onCancel() {
ConfirmDialog.hide();
reject();
}
};
var removeObject = {
title: 'delete',
isDanger: true,
callback: function onRemove() {
ConfirmDialog.hide();
resolve();
}
};
ConfirmDialog.show(null,
{'id': 'ContactConfirmDel', 'args': {n: n}}, cancelObject,
removeObject);
});
};
var doDelete = function doDelete(ids, done) {
cancelled = false;
var progress = utils.overlay.show('DeletingContacts', 'progressBar');
progress.setTotal(ids.length);
utils.overlay.showMenu();
utils.overlay.oncancel = function() {
cancelled = true;
contactsRemoverObj.finish();
};
var contactsRemoverObj = new contactsRemover();
contactsRemoverObj.init(ids, function onInitDone() {
contactsRemoverObj.start();
});
contactsRemoverObj.onDeleted = function onDeleted(currentId) {
if (window.Search && Search.isInSearchMode()) {
Search.invalidateCache();
Search.removeContact(currentId);
}
contacts.List.remove(currentId);
progress.update();
};
contactsRemoverObj.onError = function onError() {
Contacts.hideOverlay();
utils.status.show({
id: 'deleteError-general'
});
contacts.Settings.refresh();
};
contactsRemoverObj.onFinished = function onFinished() {
Contacts.hideOverlay();
utils.status.show({
id: 'DeletedTxt',
args: {n: contactsRemoverObj.getDeletedCount()}
});
contacts.Settings.refresh();
if (typeof done === 'function') {
done();
}
};
contactsRemoverObj.onCancelled = contactsRemoverObj.onFinished;
};
// Start the delete of the contacts
var performDelete = function performDelete(promise, done) {
var self = this;
requireOverlay(function onOverlay() {
promise.onsuccess = function onSuccess(ids) {
showConfirm(ids.length).then(self.doDelete.bind(null, ids, done));
};
});
};
exports.BulkDelete = {
'performDelete': performDelete,
'doDelete': doDelete
};
})(window);

View file

@ -0,0 +1,106 @@
/* globals mozContact, ContactsService */
'use strict';
/*
* Contacts Remover delete the contacts present in phone.
* - start: To initiate the deleting process.
* - finish: To cancel the deleting process.
* - onDeleted: A Contact has been deleted
* - onFinished: All Selected contacts have been deleted;
* - onError: Error while deleting contacts.
*/
function contactsRemover() {
var totalRemoved = 0;
var totalSelected = 0;
var cancelled = false;
/*jshint validthis:true */
var self = this;
var ids;
this.init = function(cIds, cb) {
if (cIds === null || cIds.length === 0) {
return;
}
ids = cIds;
if (cb && typeof cb === 'function') { cb(); }
};
this.start = function() {
totalRemoved = 0;
cancelled = false;
totalSelected = ids.length;
continueCb(ids);
};
this.finish = function() {
cancelled = true;
};
this.getDeletedCount = function() {
return totalRemoved;
};
function continueCb(ids) {
var currentId = ids.shift();
if (!currentId || cancelled) {
if (totalRemoved === totalSelected) {
notifyFinished();
}
else {
notifyCancelled();
}
return;
}
deleteContact(
currentId,
function(e) {
if (e) {
notifyError();
return;
}
totalRemoved++;
notifyDeleted(currentId);
continueCb(ids);
}
);
}
function notifyError() {
if (typeof self.onError === 'function') {
self.onError();
}
}
function notifyCancelled() {
if (typeof self.onCancelled === 'function') {
window.setTimeout(self.onCancelled, 100);
}
}
function notifyFinished() {
if (typeof self.onFinished === 'function') {
window.setTimeout(self.onFinished, 100);
}
}
function notifyDeleted(currentId) {
if (typeof self.onDeleted === 'function') {
self.onDeleted(currentId);
}
}
function deleteContact(currentID, callback) {
var contact = new mozContact();
contact.id = currentID;
ContactsService.remove(
contact,
callback
);
}
}
window.contactsRemover = contactsRemover;

View file

@ -0,0 +1,163 @@
'use strict';
/* global utils */
/* exported ContactsTag */
var ContactsTag = (function() {
var originalTag = null;
var selectedTag = null;
var customTag = null;
var setCustomTag = function setCustomTag(element) {
customTag = element;
};
var setCustomTagVisibility = function setCustomTagVisibility(value) {
if (!customTag) {
return;
}
if (value) {
customTag.classList.remove('hide');
}
else {
customTag.classList.add('hide');
}
};
var unMarkTag = function unMarkTag(tag) {
if (tag) {
tag.classList.remove('icon');
tag.classList.remove('icon-selected');
tag.removeAttribute('aria-selected');
}
};
var markTag = function markTag(tag) {
if (tag) {
tag.classList.add('icon');
tag.classList.add('icon-selected');
tag.setAttribute('aria-selected', true);
}
};
var touchCustomTag = function touchCustomTag(callback) {
unMarkTag(selectedTag);
selectedTag = null;
if (callback !== undefined && typeof callback === 'function') {
callback();
}
};
var fillTagOptions = function fillTagOptions(target, _originalTag, options) {
utils.dom.removeChildNodes(target);
originalTag = _originalTag;
var selectedLink;
/* jshint loopfunc:true */
for (var option in options) {
var tagLink = document.createElement('button');
tagLink.dataset.index = option;
tagLink.setAttribute('data-l10n-id', options[option].type);
tagLink.setAttribute('data-value', options[option].type);
tagLink.setAttribute('role', 'option');
tagLink.classList.add('tagItem');
tagLink.addEventListener('click', function(event) {
var tag = event.target;
selectTag(tag);
event.preventDefault();
});
if (originalTag.dataset.value == options[option].type) {
selectedLink = tagLink;
}
var tagItem = document.createElement('li');
tagItem.setAttribute('role', 'presentation');
tagItem.appendChild(tagLink);
target.appendChild(tagItem);
}
customTag.value = '';
if (!selectedLink && originalTag.textContent) {
customTag.value = originalTag.textContent;
}
selectTag(selectedLink);
};
var selectTag = function selectTag(tag) {
if (tag == null) {
return;
}
//Clean any trace of the custom tag
customTag.value = '';
unMarkTag(selectedTag);
markTag(tag);
selectedTag = tag;
};
var clickDone = function clickDone(callback) {
if (selectedTag) {
originalTag.textContent = selectedTag.textContent;
originalTag.dataset.l10nId = selectedTag.dataset.l10nId;
originalTag.dataset.value = selectedTag.dataset.value;
} else if (customTag.value.length > 0) {
originalTag.textContent = customTag.value;
originalTag.dataset.value = customTag.value;
}
originalTag = null;
if (callback !== undefined && typeof callback === 'function') {
callback();
}
};
// Filter tags to be shown when selecting an item type (work, birthday, etc)
// This is particularly useful for dates as we cannot have multiple instances
// of them (only one birthday, only one anniversary)
function filterTags(type, currentNode, tags) {
var element = document.querySelector(
'[data-template]' + '.' + type + '-' + 'template');
if (!element || !element.dataset.exclusive) {
return tags;
}
// If the type is exclusive the tag options are filtered according to
// the existing ones
var newOptions = tags.slice(0);
var sameType = document.querySelectorAll('.' + type + '-template');
if (sameType.length > 1) {
/* jshint loopfunc:true */
for (var j = 0; j < sameType.length; j++) {
var itemSame = sameType.item(j);
var tagNode = itemSame.querySelector('[data-field="type"]');
if (tagNode !== currentNode &&
!itemSame.classList.contains('facebook')) {
newOptions = newOptions.filter(function(ele) {
return ele.type != tagNode.dataset.value;
});
}
}
}
return newOptions;
}
return {
'setCustomTag': setCustomTag,
'touchCustomTag': touchCustomTag,
'fillTagOptions': fillTagOptions,
'clickDone': clickDone,
'setCustomTagVisibility': setCustomTagVisibility,
'filterTags': filterTags
};
})();

View file

@ -0,0 +1,119 @@
'use strict';
/* global fb */
/* global fbLoader */
/* global ImportStatusData */
/* global LazyLoader */
/* global Migrator */
/* global utils */
/* exported DeferredActions */
// Methods not related with the list rendering that must be executed after
// render to not damage performance.
var DeferredActions = (function() {
var config;
var TOTAL_RETRIES = 4;
var execute = function execute() {
config = utils.cookie.load();
if (!fbLoader.loaded) {
window.addEventListener('facebookLoaded', doExecute);
return;
}
doExecute();
};
var doExecute = function doExecute() {
window.removeEventListener('facebookLoaded', doExecute);
checkFacebookSynchronization(config);
checkVersionMigration(config);
checkFacebookSanity(config);
};
// If FB Cleaning data was ongoing we finish it
// This avoids keeping the contacts database inconsistent. See bug 1073684
function checkFacebookSanity(config) {
if (!config || !config.fbCleaningInProgress ||
config.fbCleaningInProgress > TOTAL_RETRIES) {
return;
}
var req = fb.utils.clearFbData();
req.onsuccess = function() {
var cleaner = req.result;
cleaner.onsuccess = function() {
console.log('Finished clean Fb data');
fb.markFbCleaningInProgress(0);
};
cleaner.onerror = function() {
console.error('FB Cleaner reported an error: ',
req.error && req.error.name);
fb.markFbCleaningInProgress(++config.fbCleaningInProgress);
};
};
req.onerror = function() {
console.error('Unable to start cleaning', req.error && req.error.name);
fb.markFbCleaningInProgress(++config.fbCleaningInProgress);
};
}
function checkFacebookSynchronization(config) {
// Checks if we should set a fb sync alarm when fb sync has been done in ftu
if (config && config.fbScheduleDone) {
return;
}
LazyLoader.load([
'/facebook/js/fb_sync.js',
'/shared/js/contacts/import/import_status_data.js'
], performFbSync);
}
function performFbSync() {
var fbutils = fb.utils;
function neverExecuteAgain() {
ImportStatusData.remove(fbutils.SCHEDULE_SYNC_KEY);
utils.cookie.update({fbScheduleDone: true});
navigator.removeIdleObserver(idleObserver);
idleObserver = null;
}
var idleObserver = {
time: 3,
onidle: function onidle() {
ImportStatusData.get(fbutils.SCHEDULE_SYNC_KEY).then(function(date) {
if (date) {
fbutils.setLastUpdate(date, function() {
var req = fb.sync.scheduleNextSync();
req.onsuccess = neverExecuteAgain;
req.onerror = neverExecuteAgain;
});
} else {
neverExecuteAgain();
}
});
}
};
navigator.addIdleObserver(idleObserver);
}
function checkVersionMigration(config) {
if (!config || !config.fbMigrated || !config.accessTokenMigrated) {
LazyLoader.load('js/migrator.js', function() {
Migrator.start(config);
});
}
}
return {
'execute': execute
};
})();

View file

@ -0,0 +1,200 @@
/* global getStorageIfAvailable, getUnusedFilename, ContactToVcardBlob,
MozActivity, utils */
/* exported ContactsBTExport */
'use strict';
var ContactsBTExport = function ContactsBTExport() {
var contacts;
var progressStep;
var cancelled = false;
var _ = navigator.mozL10n.get;
var _setContactsToExport = function btex_setContactsToExport(cts) {
contacts = cts;
};
var _hasDeterminativeProgress = function btex_hasDeterminativeProgress() {
return false;
};
var _getExportTitle = function btex_getExportTitle() {
return 'btExport-title';
};
var _setProgressStep = function btex_setProgressStep(p) {
progressStep = p;
};
var _getGivenName = function _getGivenName(contact) {
return (Array.isArray(contact.givenName) && contact.givenName[0] &&
contact.givenName[0].trim());
};
var _getLastName = function _getLastName(contact) {
return (Array.isArray(contact.familyName) && contact.familyName[0] &&
contact.familyName[0].trim());
};
var _getFileName = function _getFileName() {
var filename = [];
if (contacts && contacts.length === 1) {
var contact = contacts[0],
givenName = _getGivenName(contact),
lastName = _getLastName(contact);
if (givenName) {
filename.push(givenName);
}
if (lastName) {
filename.push(lastName);
}
if (filename.length === 0) {
if (contact.org && contact.org.length > 0) {
filename.push(contact.org[0]);
} else if (contact.tel && contact.tel.length > 0) {
filename.push(contact.tel[0].value);
} else if (contact.email && contact.email.length > 0) {
filename.push(contact.email[0].value);
} else {
filename.push(_('noName'));
}
}
} else {
var today = new Date();
filename.push(
today.getFullYear(),
today.getMonth() + 1,
today.getDate(),
contacts.length
);
}
return filename.join('_')
.replace(/[^a-z0-9]/gi, '_')
.toLowerCase() +
'.vcf';
};
var _getStorage = function _getStorage(fileName, blob, callback) {
if (typeof callback !== 'function') {
throw new Error('getStorage requires a callback function');
}
getStorageIfAvailable('sdcard', blob.size,
function onSuccess(storage) {
// Get the final version of the proposed file name
getUnusedFilename(storage, fileName, function onfilename(name) {
callback(null, storage, name);
});
},
callback //onerror
);
};
var _saveToSdcard = function _saveToSdcard(storage, name, blob, callback) {
if (typeof callback !== 'function') {
throw new Error('saveToSDcard requires a callback function');
}
var request = storage.addNamed(blob, name);
request.onsuccess = function(evt) {
callback(null, evt.target.result); // returns the full filepath
};
request.onerror = callback;
};
var _getFile = function _getFile(storage, filename, callback) {
if (typeof callback !== 'function') {
throw new Error('getFile requires a callback function');
}
var request = storage.get(filename);
request.onsuccess = function() {
callback(null, request.result); // returns a File object
};
request.onerror = callback;
};
var cancelExport = function cancelExport() {
cancelled = true;
};
var _doExport = function btex_doExport(finishCallback) {
if (typeof finishCallback !== 'function') {
throw new Error('BT export requires a callback function');
}
var checkError = function checkError(error) {
if (error === null) {
return false;
}
var reason = error;
// numeric error means not enough space available
if (parseInt(error, 10) >= 0) {
reason = 'noSpace';
}
finishCallback({ 'reason': reason }, 0, false);
return true;
};
ContactToVcardBlob(contacts, function onContacts(blob) {
if (cancelled) {
finishCallback(null, 0, false);
return;
}
_getStorage(_getFileName(), blob,
function onStorage(error, storage, filename) {
if (checkError(error)) {
return;
}
if (cancelled) {
finishCallback(null, 0, false);
return;
}
utils.overlay.hideMenu();
_saveToSdcard(storage, filename, blob,
function onVcardSaved(error, filepath) {
if (checkError(error)) {
return;
}
_getFile(storage, filepath, function onFileRetrieved(error, file) {
if (checkError(error)) {
return;
}
var a = new MozActivity({
name: 'share-via-bluetooth-only',
data: {
type: 'text/vcard',
number: 1,
blobs: [file],
filenames: [filename],
filepaths: [filepath]
}
});
a.onsuccess = function() {
finishCallback(null, contacts.length);
};
a.onerror = function(e) {
if (a.error.name === 'NO_PROVIDER') {
alert(_('share-noprovider'));
} else {
console.warn('share activity error:', a.error.name);
}
finishCallback({'reason': a.error}, 0, a.error.name);
};
});
});
});
});
};
return {
'setContactsToExport': _setContactsToExport,
'shouldShowProgress': function btex_shouldShowProgress() { return true; },
'hasDeterminativeProgress': _hasDeterminativeProgress,
'getExportTitle': _getExportTitle,
'setProgressStep': _setProgressStep,
'doExport': _doExport,
'cancelExport' : cancelExport,
get name() { return 'BT';} // handling error messages on contacts_exporter
};
};

View file

@ -0,0 +1,197 @@
/* global utils, ConfirmDialog, Contacts, ContactsService, Loader */
'use strict';
//
// Generic module to do contacts export.
//
// Once passed to this module an array of mozContacts objects
// and setup the strategy (mechanism to export), will perform
// the common steps taken place during the process of exporting
// the information.
// Those steps are:
// - Preparation: (optional), may launch extra UI to setup some configuration
// and transform the mozcontacts object to any extra format
// - Export: Here is where the real import happens, could provide three known
// scenarios:
// - External handling of the export (web activity, ect.)
// - Progress of the import:
// - Defined progress activity
// - Undefined progress
// - Result of the export
window.ContactsExporter = function ContactsExporter(theStrategy) {
var contacts;
var strategy = theStrategy;
var hasProgress = false;
var determinativeProgress = false;
var progress;
// XXX: Bug 904623 since we cannot fetch a list of contacts by
// contact id, we will need to fetch them all and filter
var _init = function _init(theContacts, cb) {
if (theContacts === null || theContacts.length === 0) {
return;
}
ContactsService.getAll(function(e, contactsFromAPI) {
if (e) {
cb();
return;
}
contacts = [];
contactsFromAPI.forEach(function onContact(ct) {
if (theContacts.indexOf(ct.id) !== -1) {
contacts.push(ct);
}
});
if (cb) {
cb(contacts);
}
});
};
//
// Checks if the export module has been properly configured
// and start the process of exporting.
//
var _start = function _start() {
if (!contacts || !strategy) {
throw new Error('Not properly configured');
}
strategy.setContactsToExport(contacts);
// Check if we have a 'Preparation step'
if (typeof strategy.prepare === 'function') {
strategy.prepare(_doExport);
} else {
_doExport();
}
};
var _doExport = function _doExport() {
if (typeof strategy.shouldShowProgress === 'function' &&
strategy.shouldShowProgress()) {
hasProgress = true;
_configureProgress();
_displayProgress();
}
strategy.doExport(_doHandleResult);
};
/**
* Callback invoked when the exporting process finished.
*
* @param {Object} error Not null in case an error happened
* @param {Number} exported Number of contacts successfuly exported
* @param {Boolean} isRecoverable In case of error, whether it is recoverable
*/
var _doHandleResult = function _doHandleResult(error, exported,
isRecoverable) {
if (hasProgress && !error) {
utils.overlay.hide();
}
if (error) {
// If it was cancelled by the user we don't show the error screen.
if (error.reason && error.reason.name === 'cancelled') {
utils.overlay.hide();
} else {
var cancel = {
title: 'cancel',
callback: function() {
utils.overlay.hide();
ConfirmDialog.hide();
_showStatus(exported, contacts.length);
}
};
var retry = {
title: 'retry',
isRecommend: true,
callback: function() {
utils.overlay.hide();
ConfirmDialog.hide();
// And now the action is reproduced one more time
window.setTimeout(_doExport, 0);
}
};
if (isRecoverable === false) {
retry = null;
}
var errorString = 'exportError-' + strategy.name + '-';
ConfirmDialog.show('exportErrorTitle',
errorString + error.reason, cancel, retry);
Contacts.hideOverlay();
console.error('An error occurred during the export: ',
error.reason.name || error.reason);
return;
}
}
_showStatus(exported, contacts.length);
};
var _showStatus = function(exported, total) {
// TODO: Better mechanism to show result
var msgId = {
id: 'contactsExported2',
args: {
'exported': exported,
'total': total
}
};
utils.status.show(msgId);
};
//
// Based on the strategy configure the progress display to show a
// determinative or indeterminate ui depending on the strategy
//
var _configureProgress = function _configureProgress() {
determinativeProgress =
strategy.hasDeterminativeProgress !== undefined &&
strategy.hasDeterminativeProgress();
};
//
// Shows the progress dialog based on the
//
var _displayProgress = function _displayProgress() {
var progressClass = determinativeProgress ? 'progressBar' : 'spinner';
Loader.utility('Overlay', function _loaded() {
progress = utils.overlay.show(
strategy.getExportTitle(),
progressClass,
null
);
utils.overlay.showMenu();
utils.overlay.oncancel = function() {
strategy.cancelExport();
utils.overlay.hide();
};
// Allow the strategy to setup the progress bar
if (determinativeProgress) {
progress.setTotal(contacts.length);
strategy.setProgressStep(progress.update);
}
});
};
return {
'init': _init,
'start': _start
};
};

View file

@ -0,0 +1,168 @@
/* globals _, ContactToVcard, getStorageIfAvailable, getUnusedFilename
*/
/* exported ContactsSDExport */
'use strict';
var ContactsSDExport = function ContactsSDExport() {
var contacts;
var progressStep;
var cancelled = false;
var setContactsToExport = function setContactsToExport(cts) {
contacts = cts;
};
var hasDeterminativeProgress = function hasDeterminativeProgress() {
return false;
};
var setProgressStep = function setProgressStep(p) {
progressStep = p;
};
var getExportTitle = function getExportTitle() {
return 'memoryCardExport-title';
};
var hasGivenName = function hasGivenName(contact) {
return (Array.isArray(contact.givenName) && contact.givenName[0] &&
contact.givenName[0].trim());
};
var hasFamilyName = function hasFamilyName(contact) {
return (Array.isArray(contact.familyName) && contact.familyName[0] &&
contact.familyName[0].trim());
};
var getFileName = function getFileName() {
var filename = [];
if (contacts && contacts.length === 1) {
var contact = contacts[0],
hasName = false;
if (hasGivenName(contact)) {
filename.push(contact.givenName[0]);
hasName = true;
}
if (hasFamilyName(contact)) {
filename.push(contact.familyName[0]);
hasName = true;
}
if (!hasName) {
if (contact.org && contact.org.length > 0) {
filename.push(contact.org[0]);
} else if (contact.tel && contact.tel.length > 0) {
filename.push(contact.tel[0].value);
} else if (contact.email && contact.email.length > 0) {
filename.push(contact.email[0].value);
} else {
filename.push(_('noName'));
}
}
} else {
var today = new Date();
filename.push(
today.getFullYear(),
today.getMonth() + 1,
today.getDate(),
contacts.length
);
}
return filename.join('_')
.replace(/[^a-z0-9]/gi, '_')
.toLowerCase() +
'.vcf';
};
var getStorage = function getStorage(fileName, blob, callback) {
getStorageIfAvailable('sdcard', blob.size,
function onSuccess(storage) {
// Get the final version of the proposed file name
getUnusedFilename(storage, fileName, function onFinalName(name) {
callback(null, storage, name);
});
},
function onError(type) {
callback(type);
}
);
};
var cancelExport = function cancelExport() {
cancelled = true;
};
var doExport = function doExport(finishCallback) {
/* XXX: We use a batch-conversion size of 2MiB. This ensures that the
* process won't go out of memory even on low-end devices. This should be
* revisited once append-to-file functionality becomes available. */
var batchSize = 2 * 1024 * 1024;
var count = 0;
var pendingBatches = 0;
var done = false;
var cancelButton = document.querySelector('#cancel-overlay');
if (typeof finishCallback !== 'function') {
throw new Error('SD export requires a callback function');
}
ContactToVcard(contacts, function onContacts(vcards, nCards) {
if (cancelled) {
finishCallback(null, 0);
return;
}
var blob = new Blob([vcards], {'type': 'text/vcard'});
pendingBatches++;
getStorage(getFileName(), blob,
function onStorage(error, storage, finalName) {
if (error !== null) {
var reason = error;
// numeric error means not enough space available
if (parseInt(error, 10) >= 0) {
reason = 'noSpace';
}
finishCallback({
'reason': reason
}, count, true);
return;
}
if (cancelled) {
finishCallback(null, 0);
return;
}
cancelButton.disabled = true;
var request = storage.addNamed(blob, finalName);
request.onsuccess = function onSuccess() {
count += nCards;
pendingBatches--;
if (done && (pendingBatches === 0)) {
finishCallback(null, count, false);
}
};
request.onerror = function onError(e) {
finishCallback({ 'reason': e }, count, true);
};
});
}, function finish() {
done = true;
}, batchSize);
};
return {
'setContactsToExport': setContactsToExport,
'shouldShowProgress': function shouldShowProgress() { return true; },
'hasDeterminativeProgress': hasDeterminativeProgress,
'getExportTitle': getExportTitle,
'doExport': doExport,
'setProgressStep': setProgressStep,
'cancelExport': cancelExport,
get name() { return 'SD'; } // handling error messages on contacts_exporter
};
};

View file

@ -0,0 +1,195 @@
/* exported ContactsSIMExport */
/* global ContactsService */
'use strict';
var ContactsSIMExport = function ContactsSIMExport(icc) {
var contacts;
var progressStep;
var exported;
var notExported;
var cancelled = false;
var ERRORS = {
'NoFreeRecordFound': false,
'CannotAccessPhoneBook': true
};
var setContactsToExport = function setContactsToExport(cts) {
contacts = cts;
};
var hasDeterminativeProgress = function hasDeterminativeProgress() {
return contacts.length > 1;
};
var setProgressStep = function setProgressStep(p) {
progressStep = p;
};
var getExportTitle = function getExportTitle() {
return 'simExport-title';
};
var doExport = function doExport(finishCallback) {
if (typeof finishCallback !== 'function') {
throw new Error('SIM export requires a callback function');
}
exported = [];
notExported = [];
// We should control this state before doing the export
// but a second check is healthy
if (!icc) {
finishCallback({
'reason': 'Unavailable'
}, 0, false);
return;
}
_doExport(0, finishCallback);
};
// Returns the iccContactId to be used for exporting this contact
// undefined if not iccContactId is found
// url content is urn:uuid:<iccid>-icccontactid
var _getIccContactId = function _getIccContactId(theContact) {
var out;
var contactUrl = theContact.url;
if (!Array.isArray(contactUrl)) {
return out;
}
for (var j = 0; j < contactUrl.length; j++) {
var aUrl = contactUrl[j];
if (aUrl.type.indexOf('source') !== -1 &&
aUrl.type.indexOf('sim') !== -1) {
var value = aUrl.value.split(':')[2];
var iccInfo = value.split('-');
if (iccInfo[0] === _getIccId()) {
out = iccInfo[1];
break;
}
}
}
return out;
};
var _generateIccContactUrl = function _generateIccContactUrl(contactid,
iccId) {
var urlValue = 'urn:' + 'uuid:' + (iccId || 'iccId') + '-' + contactid;
return {
type: ['source', 'sim'],
value: urlValue
};
};
var _getIccId = function _getIccId() {
return icc.iccInfo && icc.iccInfo.iccid;
};
var _updateContactSimSource = function _updateContactSimSource(theContact,
iccContact, cb, errorCb) {
if (!iccContact) {
window.console.warn('No Icc Contact provided');
cb();
return;
}
theContact.url = theContact.url || [];
theContact.url.push(_generateIccContactUrl(iccContact.id, _getIccId()));
ContactsService.save(
theContact,
function(e) {
if (e) {
errorCb(e);
return;
}
cb();
}
);
};
var cancelExport = function cancelExport() {
cancelled = true;
};
var _doExport = function _doExport(step, finishCallback) {
if (step == contacts.length || cancelled) {
finishCallback(null, exported.length);
return;
}
var next = function next(success, contact) {
var resultArray = success ? exported : notExported;
resultArray.push(contact);
if (progressStep) {
progressStep();
}
step++;
_doExport(step, finishCallback);
};
var theContact = contacts[step];
var originalContactId = theContact.id;
var idToUpdate = _getIccContactId(theContact);
if (!idToUpdate) {
theContact.id = null;
}
else {
theContact.id = idToUpdate;
}
var request = icc.updateContact('adn', theContact);
request.onsuccess = function onsuccess() {
// Restoring the original contact.id to avoid duplicating it
theContact.id = originalContactId;
if (idToUpdate) {
next(true, theContact);
return;
}
// Now it is needed to save the provided id in case we don't have one
var iccContact = request.result;
_updateContactSimSource(theContact, iccContact,
function export_update_success() {
next(true, theContact);
}, function export_update_error(err) {
window.console.error('Error while updating: ', err.name);
next(true, theContact);
});
};
request.onerror = function onerror(e) {
var error = e.target.error;
window.console.error('Error while exporting: ', originalContactId,
error.name);
var errorName = typeof ERRORS[error.name] === 'boolean' ?
error.name : 'GenericError';
if (errorName !== 'GenericError') {
finishCallback({
'reason': errorName
}, exported.length, ERRORS[error.name]);
}
else {
next(false, theContact);
}
};
};
return {
'setContactsToExport': setContactsToExport,
'shouldShowProgress': function() { return true; },
'hasDeterminativeProgress': hasDeterminativeProgress,
'getExportTitle': getExportTitle,
'doExport': doExport,
'setProgressStep': setProgressStep,
'cancelExport' : cancelExport,
get name() { return 'SIM';} // handling error messages on contacts_exporter
};
};

View file

@ -0,0 +1,314 @@
'use strict';
/* global LazyLoader, fb, contacts, utils */
/* exported DatastoreMigration */
var DatastoreMigration = function(db) {
var active = false;
var ongoingMigration = false;
var migrator;
var idleObserver;
var self = this;
// Only really needed for testing purposes
Object.defineProperty(this, 'db', {
set: function(val) {
if (migrator) {
migrator.db = val;
}
}
});
var IdleObserver = function() {
var self = this;
this._files = [
'/shared/js/fb/fb_request.js',
'/shared/js/contacts/import/facebook/fb_data.js'
];
// 3 seconds idle time
this.time = 3;
this.onidle = function() {
LazyLoader.load(self._files, function() {
onidle();
});
};
this.onactive = function() {
LazyLoader.load(self._files, function() {
onactive();
});
};
};
function DatastoreMigrator(db) {
var IDB_NAME = 'Gaia_Facebook_Friends';
var objStoreName;
var SLICE_SIZE = 5;
var initialized = false;
var database = db;
var self = this;
var existsDatabase = true;
this.saveErrors = false;
// Only really needed for testing purposes
Object.defineProperty(this, 'db', {
set: function(val) {
database = val;
initialized = true;
}
});
function sliceEnd(hasMore) {
if (!hasMore) {
// No more records present
window.console.info('FB Migration: All records have been migrated');
// Close the database
database.close();
if (!self.saveErrors) {
var req = window.indexedDB.deleteDatabase(IDB_NAME);
req.onsuccess = function() {
window.console.info('FB Migration: ',
'Now the indexedDB has been deleted');
};
req.onerror = function() {
window.console.error('FB Migration: ',
'Error while deleting indexedDB: ',
req.error.name);
};
}
else {
window.console.warn('There are save errors. ', 'DB not deleted');
}
}
if (typeof self.onfinished === 'function') {
self.onfinished(hasMore, self.saveErrors);
}
}
this.migrateSlice = function migrateSlice() {
var toBeMigrated = [];
var pendingRecords = true;
var self = this;
openIndexedDB(function() {
var transaction = database.transaction([objStoreName], 'readwrite');
var objectStore = transaction.objectStore(objStoreName);
var req = objectStore.openCursor();
req.onsuccess = function(e) {
var cursor = e.target.result;
if (cursor && toBeMigrated.length < SLICE_SIZE) {
var obj = cursor.value;
toBeMigrated.push(obj);
cursor.continue();
}
else {
if (!cursor) {
pendingRecords = false;
}
migrateRecords(toBeMigrated, function() {
sliceEnd(pendingRecords);
});
}
};
req.onerror = function() {
window.console.error('FB Migration: ',
'Error while requesting a cursor: ',
req.error.name);
if (typeof self.onerror === 'function') {
self.onerror();
}
};
});
};
this.reset = function() {
existsDatabase = true;
initialized = false;
this.saveErrors = false;
database = null;
};
function migrateRecords(list, cb) {
if (list.length === 0) {
cb();
return;
}
var numResponses = 0;
var migrateCb = function migratCb() {
numResponses++;
if (numResponses === list.length) {
cb();
}
};
for (var j = 0; j < list.length; j++) {
migrateRecord(list[j], migrateCb);
}
}
function removeRecord(obj, cb) {
// Now deleting from indexedDB
var trans = database.transaction([objStoreName], 'readwrite');
var objectStore = trans.objectStore(objStoreName);
var req = objectStore.delete(obj.uid);
req.onsuccess = cb;
req.onerror = function() {
window.console.error('FB Migration: Error while deleting indexedDB: ',
obj.uid, ': ', req.error.name);
cb();
};
}
function migrateRecord(obj, cb) {
var req = fb.contacts.save(obj);
req.onsuccess = function() {
window.console.info('FB Migration: indexed DB Record migrated: ',
obj.uid);
contacts.List.refreshFb(obj.uid);
removeRecord(obj, cb);
};
req.onerror = function() {
window.console.error('FB Migration: Error while saving to datastore: ',
obj.uid, req.error.name);
if (req.error.name === 'AlreadyExists') {
// Then that record was already migrated and it is safe to delete it
removeRecord(obj, cb);
}
else {
self.saveErrors = true;
cb();
}
};
}
function openIndexedDB(cb) {
if (database) {
cb();
return;
}
var req = window.indexedDB.open(IDB_NAME);
req.onupgradeneeded = function() {
existsDatabase = false;
};
req.onsuccess = function() {
if (!existsDatabase) {
// Delete it as indexedDB will have created it
window.console.warn('FB Migration: The database does not exist');
window.indexedDB.deleteDatabase(IDB_NAME);
if (typeof self.onfinished === 'function') {
self.onfinished(false);
}
return;
}
database = req.result;
objStoreName = database.objectStoreNames[0];
cb();
};
req.onerror = function() {
window.console.error('Indexed DB cannot be opened: ', req.error.name);
if (typeof self.onerror === 'function') {
self.onerror();
}
};
}
}
var start = function start() {
idleObserver = new IdleObserver();
navigator.addIdleObserver(idleObserver);
};
this.start = start;
var stop = function stop() {
navigator.removeIdleObserver(idleObserver);
if (migrator) {
migrator.reset();
}
if (typeof self.onmigrationdone === 'function') {
self.onmigrationdone();
}
};
this.stop = stop;
function onactive() {
active = true;
}
function sliceFinished(hasMore, saveErrors) {
if (!hasMore && !saveErrors) {
// And annotate in a cookie that migration was done
utils.cookie.update({
fbMigrated: true
});
stop();
}
else if (saveErrors && !hasMore) {
// The process is finished for the moment though there are errors
if (typeof self.onmigrationdone === 'function') {
self.onmigrationdone(true);
}
ongoingMigration = false;
}
else {
if (typeof self.onslicemigrated === 'function') {
self.onslicemigrated();
}
if (!active) {
ongoingMigration = true;
// If still inactive next slice is migrated
migrator.migrateSlice();
}
else {
ongoingMigration = false;
}
}
}
var onidle = function onidle() {
active = false;
if (ongoingMigration) {
return;
}
window.console.info('Idle event!!!. FB Migration about to start');
ongoingMigration = true;
if (!migrator) {
migrator = new DatastoreMigrator(db);
migrator.onfinished = sliceFinished;
migrator.onerror = function() {
ongoingMigration = false;
migrator.reset();
if (typeof self.onerror === 'function') {
self.onerror({
name: 'Migration Error'
});
}
};
}
migrator.saveErrors = false;
migrator.migrateSlice();
};
// Mainly needed for testing purposes
this.onidle = onidle;
};

View file

@ -0,0 +1,4 @@
;'use strict';var fb=window.fb||{};var config=window.config||{};if(typeof fb.init==='undefined'){(function(){var initialized=false;var initializing=false;var EV_FB_INIT='fb_init_initialized';fb.isEnabled=false;fb.init=function(callback){if(initialized){callback();return;}
if(initializing){document.addEventListener(EV_FB_INIT,function handler(e){initializing=false;document.removeEventListener(EV_FB_INIT,handler);callback();});return;}
initializing=true;utils.config.load('/contacts/config.json').then(function cLoaded(configData){if(configData.facebookEnabled===true){fb.isEnabled=true;}
fb.operationsTimeout=config.operationsTimeout=configData.operationsTimeout;fb.logLevel=configData.logLevel||'none';fb.syncPeriod=configData.facebookSyncPeriod||24;fb.testToken=configData.testToken;initialized=true;document.dispatchEvent(new CustomEvent(EV_FB_INIT));callback();},function loadError(err){console.error('Error while loading config.json',err);});};})();}

View file

@ -0,0 +1,51 @@
;'use strict';var fb=window.fb||{};if(!fb.link){(function(document){var link=fb.link={};var UI=link.ui={};link.CID_PARAM='contactid';var contactid;var cdata;var access_token;var numQueries=0;var friendUidToLink;var SEARCH_QUERY=['SELECT uid, name, email from user ',' WHERE uid IN (SELECT uid1 FROM friend WHERE uid2=me()) ',' AND (',null,')',' ORDER BY name'];var MAIL_COND,CELL_COND;MAIL_COND=['strpos(email, ',"'",null,"'",') >= 0'];CELL_COND=['strpos(cell, ',"'",null,"'",') >= 0'];var ALL_QUERY=['SELECT uid, name, last_name, first_name,',' middle_name, email from user ',' WHERE uid IN (SELECT uid1 FROM friend WHERE uid2=me()) ',' ORDER BY name'];var COUNT_QUERY='SELECT uid FROM user WHERE uid IN '+'(SELECT uid1 FROM friend WHERE uid2=me())';var SEARCH_ACCENTS_FIELDS={'last_name':'familyName','first_name':'givenName','middle_name':'givenName'};var friendsList;var viewButton,mainSection;var currentRecommendation=null;var allFriends=null;var currentNetworkRequest=null;var state;var imgLoader;var completedCb;function notifyParent(message){parent.postMessage({type:message.type||'',data:message.data||''},fb.CONTACTS_APP_ORIGIN);}
function buildQuery(contact){var filter=[];if(contact.tel&&contact.tel.length>0){contact.tel.forEach(function(tel){CELL_COND[2]=tel.value.trim();filter.push(CELL_COND.join(''));filter.push(' OR ');});}
if(contact.email&&contact.email.length>0){contact.email.forEach(function(email){MAIL_COND[2]=email.value.trim();filter.push(MAIL_COND.join(''));filter.push(' OR ');});}
if(filter.length>0){filter[filter.length-1]=null;}
var filterStr=filter.join('');var out;if(filterStr){SEARCH_QUERY[3]=filterStr;out=SEARCH_QUERY.join('');}
else{out=null;}
return out;}
link.getRemoteProposal=function(acc_tk,contid){var cid=contid||contactid;var req=fb.utils.getContactData(cid);req.onsuccess=function(){if(req.result){cdata=req.result;numQueries=1;currentRecommendation=null;var query=buildQuery(cdata);if(query===null){getRemoteProposalAll(acc_tk);return;}
doGetRemoteProposal(acc_tk,cdata,query);}
else{throw new Error('FB: Contact to be linked not found in mozContacts: '+cid);}};req.onerror=function(e){window.console.error('FB: Error while retrieving contact data: ',cid);throw e;};};function getRemoteProposalAll(acc_tk){numQueries++;doGetRemoteProposal(acc_tk,null,ALL_QUERY.join(''),true);}
function proposalReadyMultiple(done,error,response){if(response.error){done(response);return;}else if(!Array.isArray(response.data)){error({name:'QueryResponseError'});return;}
var friendList,totalFriends;if(response.data.length>0&&Array.isArray(response.data[0].fql_result_set)){friendList=response.data[0].fql_result_set;if(response.data[1]&&Array.isArray(response.data[1].fql_result_set)){totalFriends=response.data[1].fql_result_set.length;}}
else{friendList=response.data;totalFriends=friendList.length;}
if(typeof totalFriends!=='undefined'){fb.utils.setCachedNumFriends(totalFriends);}
done({data:friendList});}
function doGetRemoteProposal(acc_tk,contactData,query,isAll){var theQuery=query;var callback=proposalReadyMultiple.bind(null,fb.link.proposalReady,fb.link.errorHandler);if(!isAll){theQuery=JSON.stringify({query1:query,query2:COUNT_QUERY});}
state='proposal';currentNetworkRequest=fb.utils.runQuery(theQuery,{success:callback,error:fb.link.errorHandler,timeout:fb.link.timeoutHandler},acc_tk);}
function cancelCb(shouldNotifyParent){if(currentNetworkRequest){currentNetworkRequest.cancel();currentNetworkRequest=null;}
Curtain.hide(shouldNotifyParent?notifyParent.bind(null,{type:'abort'}):null);}
function getRemoteAll(){Curtain.oncancel=cancelCb;Curtain.show('wait','friends');state='friends';currentNetworkRequest=fb.utils.runQuery(ALL_QUERY.join(''),{success:fb.link.friendsReady,error:fb.link.errorHandler,timeout:fb.link.timeoutHandler},access_token);}
link.getProposal=function(cid,acc_tk){link.getRemoteProposal(acc_tk,cid);};link.proposalReady=function(response){if(typeof response.error!=='undefined'){window.console.error('FB: Error while retrieving link data',response.error.code,response.error.message);if(response.error.code!=190){setCurtainHandlersErrorProposal();Curtain.show('error','proposal');}
else{handleTokenError();}
return;}
if(response.data.length===0&&numQueries===1){getRemoteProposalAll(access_token);}
else{var data=response.data;currentRecommendation=data;var sortedByName=[];var numFriendsProposed=data.length;var searchAccentsArrays={};var index=0;data.forEach(function(item){if(!item.email){item.email='';}
var box=utils.misc.getPreferredPictureBox();item.picwidth=box.width;item.picheight=box.height;if(numQueries===2){sortedByName.push(item);Object.keys(SEARCH_ACCENTS_FIELDS).forEach(function(field){searchAccentsArrays[field]=searchAccentsArrays[field]||[];if(item[field]){var words=item[field].split(/[ ]+/);words.push(item[field]);words.forEach(function(word){var obj={originalIndex:index};obj[field]=Normalizer.toAscii(word).toLowerCase();searchAccentsArrays[field].push(obj);});}});index++;}});if(numQueries===2){var accentsProposal=searchAccents(searchAccentsArrays,cdata);if(accentsProposal.length===0){data=sortedByName;mainSection.classList.add('no-proposal');numFriendsProposed=0;}
else{currentRecommendation=[];accentsProposal.forEach(function(proposalIndex){currentRecommendation.push(sortedByName[proposalIndex]);});numFriendsProposed=currentRecommendation.length;}}else{viewButton.setAttribute('data-l10n-id','viewAll');viewButton.onclick=UI.viewAllFriends;}
utils.templates.append('#friends-list',currentRecommendation);imgLoader.reload();if(typeof completedCb==='function'){completedCb();}
Curtain.hide(function onCurtainHide(){sendReadyEvent();window.addEventListener('message',function linkOnViewPort(e){if(e.origin!==fb.CONTACTS_APP_ORIGIN){return;}
var data=e.data;if(data&&data.type==='dom_transition_end'){window.removeEventListener('message',linkOnViewPort);utils.status.show({id:'linkProposal',args:{numFriends:numFriendsProposed}});}});});}};function searchAccents(searchArrays,contactData){var out=[];var searchFields=Object.keys(SEARCH_ACCENTS_FIELDS);function compareItems(target,item){var out;if(typeof target==='string'){out=target.localeCompare(item);if(out!==0){if(item.indexOf(target)===0){out=0;}}}
return out;}
searchFields.forEach(function(searchField){var searchArray=searchArrays[searchField].sort(function(a,b){return a[searchField].localeCompare(b[searchField]);});var fieldToSearch=contactData[SEARCH_ACCENTS_FIELDS[searchField]];if(fieldToSearch&&fieldToSearch[0]){var dataToSearch=fieldToSearch[0].trim().split(/[ ]+/);dataToSearch.forEach(function(aData){var targetString=Normalizer.toAscii(aData).toLowerCase();var searchResult=utils.binarySearch(targetString,searchArray,{arrayField:searchField,compareFunction:compareItems});searchResult.forEach(function(aResult){var candidate=searchArray[aResult].originalIndex;if(out.indexOf(candidate)===-1){out.push(candidate);}});});}});return out;}
function sendReadyEvent(){parent.postMessage({type:'ready',data:''},fb.CONTACTS_APP_ORIGIN);}
function setCurtainHandlersErrorProposal(){Curtain.oncancel=function(){cancelCb(true);};Curtain.onretry=function getRemoteProposal(){Curtain.oncancel=function(){cancelCb(true);};Curtain.show('wait',state);link.getRemoteProposal(access_token,contactid);};}
link.baseHandler=function(type){if(state==='friends'){Curtain.onretry=getRemoteAll;Curtain.oncancel=Curtain.hide;}else if(state==='proposal'){setCurtainHandlersErrorProposal();}
else if(state==='linking'){Curtain.onretry=function retry_link(){Curtain.show('message','linking');UI.selected({target:{dataset:{uuid:friendUidToLink}}});};Curtain.oncancel=Curtain.hide;}
Curtain.show(type,state);};link.timeoutHandler=function(){link.baseHandler('timeout');};link.errorHandler=function(){link.baseHandler('error');};function clearList(){if(!friendsList){friendsList=document.querySelector('#friends-list');}
var template=friendsList.querySelector('[data-template]');utils.dom.removeChildNodes(friendsList);friendsList.appendChild(template);}
link.friendsReady=function(response){if(typeof response.error==='undefined'&&response.data){viewButton.setAttribute('data-l10n-id','viewRecommend');viewButton.onclick=UI.viewRecommended;allFriends=response;fb.utils.setCachedNumFriends(response.data.length);response.data.forEach(function(item){if(!item.email){item.email='';}});clearList();var fragment=document.createDocumentFragment();utils.templates.append(friendsList,response.data,fragment);friendsList.appendChild(fragment);imgLoader.reload();Curtain.hide();}
else{window.console.error('FB: Error while retrieving friends',response.error.code,response.error.message);if(response.error.code!==190){Curtain.onretry=getRemoteAll;Curtain.oncancel=Curtain.hide;}
else{Curtain.onretry=handleTokenError;}
Curtain.show('error','friends');}};function setCurtainHandlers(){Curtain.oncancel=function curtain_cancel(){cancelCb(true);};}
link.start=function(contactId,acc_tk,endCb){completedCb=endCb;access_token=acc_tk;contactid=contactId;setCurtainHandlers();clearList();imgLoader=new ImageLoader('#mainContent','li:not([data-uuid="#uid#"])');if(!acc_tk){oauth2.getAccessToken(function proposal_new_token(new_acc_tk){access_token=new_acc_tk;link.getProposal(contactId,new_acc_tk);},'proposal','facebook');}
else{link.getProposal(contactId,acc_tk);}};link.init=function(){mainSection=document.querySelector('#main');viewButton=document.querySelector('#view-all');};function retryOnErrorCb(){UI.selected({target:{dataset:{uuid:friendUidToLink}}});}
function handleTokenError(){Curtain.hide(notifyParent.bind(null,{type:'token_error'}));var cb=function(){allFriends=null;link.start(contactid);};ImportStatusData.remove(fb.utils.TOKEN_DATA_KEY).then(cb);}
UI.selected=function(event){Curtain.show('message','linking');var element=event.target.parentNode;friendUidToLink=element.dataset.uuid;var req=fb.contacts.get(friendUidToLink);req.onsuccess=function(){if(req.result){window.setTimeout(function delay(){Curtain.hide(notifyParent.bind(null,{type:'item_selected',data:{uid:friendUidToLink}}));},1000);}
else{state='linking';var callbacks={};callbacks.success=function(data){Curtain.hide(notifyParent.bind(null,{type:'item_selected',data:data}));};callbacks.error=function(e){var error=e.target.error;window.console.error('FB: Error while importing friend data ',JSON.stringify(error));Curtain.oncancel=Curtain.hide;if(error.code!=190){Curtain.onretry=retryOnErrorCb;}
else{Curtain.onretry=handleTokenError;}
Curtain.show('error','linking');};callbacks.timeout=function(){link.baseHandler('timeout');};imgLoader.unload();FacebookConnector.importContact(friendUidToLink,access_token,callbacks,'not_match');}};req.onerror=function(){window.console.error('FB: Error while importing friend data');Curtain.oncancel=Curtain.hide;Curtain.onretry=retryOnErrorCb;Curtain.show('error','linking');};};UI.end=function(event){var msg={type:'window_close',data:''};parent.postMessage(msg,fb.CONTACTS_APP_ORIGIN);};UI.viewAllFriends=function(event){if(!allFriends){getRemoteAll();}
else{link.friendsReady(allFriends);}
return false;};UI.viewRecommended=function(event){event.target.onclick=UI.viewAllFriends;event.target.setAttribute('data-l10n-id','viewAll');clearList();utils.templates.append(friendsList,currentRecommendation);imgLoader.reload();return false;};})(document);}

View file

@ -0,0 +1,3 @@
;'use strict';(function(document){var cid=window.location.search.substring(fb.link.CID_PARAM.length+2);if(parent.fb){fb.operationsTimeout=parent.fb.operationsTimeout;}
utils.listeners.add({'#link-header':[{event:'action',handler:fb.link.ui.end}],'#friends-list':fb.link.ui.selected});fb.link.init();document.querySelector('#view-all').onclick=fb.link.ui.viewAllFriends;fb.contacts.init(function fb_init(){window.addEventListener('message',function getAccessToken(e){if(e.origin!==fb.CONTACTS_APP_ORIGIN){return;}
window.removeEventListener('message',getAccessToken);fb.link.start(cid,e.data.data);});parent.postMessage({type:'messaging_ready',data:''},fb.CONTACTS_APP_ORIGIN);});})(document);

View file

@ -0,0 +1,110 @@
'use strict';
/* global LazyLoader */
/* global oauth2 */
/* global oauthflow */
var fb = window.fb || {};
if (typeof fb.msg === 'undefined') {
LazyLoader.load('/shared/pages/import/js/parameters.js', () => {
(function(document) {
var Msg = fb.msg = {};
var to;
var message;
var params = oauthflow.params.facebook;
var appId = params.applicationId;
var redirectURI = params.redirectMsg;
Msg.CID_PARAM = 'contactid';
// Only needed if we decide to craft our own UI
Msg.wallPost = function(uid, msg) {
to = uid || to;
message = msg || message;
oauth2.getAccessToken(doWallPost, 'wallPost', 'facebook');
};
Msg.sendPrivate = function(uid, msg) {
// TODO: To be implemented (if we decide to craft our custom UI)
};
// This code is for posting to user's wall. Only will be necessary if
// we decide to craft our own UI for posting to the wall
function doWallPost(token) {
var msgWallService = 'https://graph.facebook.com/#/feed?method=POST';
msgWallService = msgWallService.replace(/#/, to);
var params = [
'access_token' + '=' + token,
'message' + '=' + message,
'callback' + '=' + 'fb.msg.ui.wallPosted'
];
var q = params.join('&');
var jsonp = document.createElement('script');
jsonp.src = msgWallService + '&' + q;
document.body.appendChild(jsonp);
}
var UI = Msg.ui = {};
function getFbContactUid(contactId, callback) {
var req = fb.utils.getContactData(contactId);
req.onsuccess = function() {
var fbContact = new fb.Contact(req.result);
callback(fbContact.uid);
};
req.onerror = function() {
window.console.error('Contacts: Contact not found!');
callback(null);
};
}
function openMsgDialog(dialogURI, uid) {
var params = [
'app_id' + '=' + appId,
'to' + '=' + uid,
'redirect_uri' + '=' + encodeURIComponent(redirectURI)
];
var target = dialogURI + params.join('&');
window.open(fb.utils.getNonCacheableUrl(target), '', 'dialog');
}
// Use the FB Dialogs functionality for posting to the wall
UI.wallPost = function(contactId) {
getFbContactUid(contactId, function ui_wallPost(uid) {
if (uid) {
var dialogURI = 'https://m.facebook.com/dialog/feed?';
openMsgDialog(dialogURI, uid);
}
});
};
// Use a Web view from Facebook for sending private messages
// TODO: Check with Facebook why the send message dialog seems not to be
// working on mobile
UI.sendPrivateMsg = function(contactId) {
getFbContactUid(contactId, function ui_sendMsg(uid) {
if (uid) {
var url = 'https://m.facebook.com/compose_message/?uid=' + uid;
window.open(fb.utils.getNonCacheableUrl(url),
'', 'dialog');
}
});
};
UI.wallPosted = function(result) {
UI.end();
window.console.log(JSON.stringify(result));
};
})(document);
});
}

View file

@ -0,0 +1,55 @@
'use strict';
/* global LazyLoader */
/* exported fbLoader */
var fbLoader = (function() {
var loaded = false;
var loadFb = function loadFb() {
if (loaded) {
return;
}
loaded = true;
var iframesFragment = document.createDocumentFragment();
var oauth = document.createElement('iframe');
oauth.id = 'iframe_oauth';
oauth.hidden = true;
iframesFragment.appendChild(oauth);
var extensions = document.createElement('iframe');
extensions.id = 'iframe_extensions';
iframesFragment.appendChild(extensions);
document.body.appendChild(iframesFragment);
var scripts = [
'/shared/js/contacts/import/utilities/misc.js',
'/shared/js/contacts/import/import_status_data.js',
'/contacts/js/service_extensions.js',
'/shared/pages/import/js/parameters.js',
'/shared/js/fb/fb_request.js',
'/shared/js/contacts/import/facebook/fb_data.js',
'/shared/js/contacts/import/facebook/fb_utils.js',
'/shared/js/contacts/import/facebook/fb_query.js',
'/shared/js/fb/fb_reader_utils.js',
'/shared/js/contacts/import/facebook/fb_contact_utils.js',
'/shared/js/contacts/import/facebook/fb_contact.js',
'/contacts/js/fb/fb_link.js',
'/contacts/js/fb/fb_messaging.js'
];
LazyLoader.load(scripts, function() {
var event = new CustomEvent('facebookLoaded');
window.dispatchEvent(event);
});
};
return {
load: loadFb,
get loaded() { return loaded; }
};
})();

View file

@ -0,0 +1,67 @@
'use strict';
/* global contacts */
/* global LazyLoader */
var fb = window.fb || {};
fb.resolver = function(item, loader) {
var FB_SCRIPTS_NEEDED = [
'/shared/js/fb/fb_request.js',
'/shared/js/fb/fb_data_reader.js',
'/shared/js/fb/fb_reader_utils.js'
];
var status = item.dataset.status;
var isFbContact = 'fbUid' in item.dataset;
var EMPTY_OBJ = {};
if (isFbContact && status !== 'pending' && status !== 'loaded') {
item.dataset.status = 'pending';
LazyLoader.load(FB_SCRIPTS_NEEDED, function() {
var fbReq = fb.contacts.get(item.dataset.fbUid);
fbReq.onsuccess = function() {
var fbData = fbReq.result;
if (fbData) {
var id = item.dataset.uuid;
var hasPhoto = contacts.List.hasPhoto(id);
var renderPhoto = false;
if (!hasPhoto) {
renderPhoto = contacts.List.updatePhoto(fbData, id);
} else {
renderPhoto = true;
}
if (renderPhoto) {
contacts.List.renderPhoto(item, id);
document.dispatchEvent(new CustomEvent('onupdate'));
}
item.dataset.status = 'loaded';
// The organization is also loaded
var contactObj = EMPTY_OBJ;
// If there is no local org we render the FB org
if (!item.querySelector('span.org')) {
var org = fbData.org;
if (Array.isArray(org)) {
contactObj = {
org: org
};
}
}
contacts.List.renderFbData(contactObj, item);
} else {
window.console.warn('FB Data could not be retrieved by the resolver',
'for UID: ', item.dataset.fbUid);
item.dataset.status = 'loaded';
}
};
fbReq.onerror = function() {
item.dataset.status = 'error';
};
});
} else if (status === 'loaded' || !isFbContact) {
loader.defaultLoad(item);
}
};

View file

@ -0,0 +1,80 @@
/* globals ActivityHandler, MainNavigation */
(function(exports) {
'use strict';
exports.HeaderUI = {
_lastCustomHeaderCallback: null,
settings: document.getElementById('view-settings'),
settingsButton: document.getElementById('settings-button'),
header: document.getElementById('contacts-list-header'),
addButton: document.getElementById('add-contact-button'),
editModeTitleElement: document.getElementById('edit-title'),
appTitleElement: document.getElementById('app-title'),
setupActionableHeader: function() {
this.header.removeAttribute('action');
this.settingsButton.hidden = false;
this.addButton.hidden = false;
this.appTitleElement.setAttribute('data-l10n-id', 'contacts');
},
setCancelableHeader: function(cb, titleId) {
this.setupCancelableHeader(titleId);
this.header.removeEventListener('action', this.handleCancel);
this._lastCustomHeaderCallback = cb;
this.header.addEventListener('action', cb);
},
setNormalHeader: function() {
this.setupActionableHeader();
this.header.removeEventListener('action', this._lastCustomHeaderCallback);
this.header.addEventListener('action', this.handleCancel);
},
setupCancelableHeader: function(alternativeTitle) {
this.header.setAttribute('action', 'close');
this.settingsButton.hidden = true;
this.addButton.hidden = true;
if (alternativeTitle) {
this.appTitleElement.setAttribute('data-l10n-id', alternativeTitle);
}
// Trigger the title to re-run font-fit/centering logic
this.appTitleElement.textContent = this.appTitleElement.textContent;
},
handleCancel: function() {
//If in an activity, cancel it
if (ActivityHandler.currentlyHandling) {
ActivityHandler.postCancel();
MainNavigation.home();
} else {
MainNavigation.back();
}
},
updateSelectCountTitle: function(count) {
navigator.mozL10n.setAttributes(this.editModeTitleElement,
'SelectedTxt',
{n: count});
},
hideAddButton: function() {
this.addButton.classList.add('hide');
},
updateHeader: function(isCancelable) {
if (isCancelable) {
var alternativeTitle = null;
var activityName = ActivityHandler.activityName;
if (activityName === 'pick' || activityName === 'update') {
alternativeTitle = 'selectContact';
}
this.setupCancelableHeader(alternativeTitle);
} else {
this.setupActionableHeader();
}
}
};
})(window);

View file

@ -0,0 +1,122 @@
(function(exports) {
'use strict';
/* global LazyLoader */
/* exported Loader */
// Loader allow to load "views" and "utilities" in a easy form.
// We load with this class the DOM dependencies and the libraries
// needed to use the view/utility.
var SHARED = 'shared';
var SHARED_PATH = '/' + SHARED + '/' + 'js';
var SHARED_UTILS = 'sharedUtilities';
var SHARED_UTILS_PATH = SHARED_PATH + '/contacts/import/utilities';
var SHARED_CONTACTS = 'sharedContacts';
var SHARED_CONTACTS_PATH = SHARED_PATH + '/' + 'contacts';
/**
* Specifies dependencies for resources
* E.g., mapping Facebook as a dependency of views
*/
var dependencies = {
views: {
Search: function(callback) {
LazyLoader.load(SHARED_PATH + '/utilities.js', callback);
}
},
utilities: {},
sharedUtilities: {}
};
// Mapping of view names to element IDs
// TODO: Having a more standardized way of specifying this would be nice.
// Then we could get rid of this mapping entirely
// E.g., #details-view, #list-view, #form-view
var elementMapping = {
details: ['view-contact-details'],
form: ['view-contact-form'],
settings: ['settings-wrapper'],
search: ['search-view', SHARED_CONTACTS],
multiple_select: ['multiple-select-view'],
overlay: ['loading-overlay', SHARED_UTILS],
ice: ['ice-view'],
import_sim_contacts: [null, SHARED_UTILS]
};
function load(type, file, callback) {
/**
* Performs the actual lazy loading
* Called once all dependencies are met
*/
function doLoad() {
var name = file.toLowerCase();
var path = null;
if (elementMapping[name] && elementMapping[name][1]) {
path = elementMapping[name][1];
}
var finalPath = 'js' + '/' + type;
switch (path) {
case SHARED:
finalPath = SHARED_PATH;
break;
case SHARED_UTILS:
finalPath = SHARED_UTILS_PATH;
break;
case SHARED_CONTACTS:
finalPath = SHARED_CONTACTS_PATH;
break;
default:
finalPath = 'js' + '/' + type;
}
var toLoad = [finalPath + '/' + name + '.js'];
if (elementMapping[name] && elementMapping[name][0]) {
var node = document.getElementById(elementMapping[name][0]);
if (node) {
toLoad.unshift(node);
}
}
LazyLoader.load(toLoad, function() {
if (callback) {
callback();
}
});
}
if (dependencies[type][file]) {
return dependencies[type][file](doLoad);
}
doLoad();
}
var Loader = {
/**
* Loads a view from the views/ folder
* @param {String} view name.
* @param {Function} callback.
*/
view: function loadView(view, callback) {
load('views', view, callback);
},
/**
* Loads a utility from the utilities/ folder
* @param {String} utility name.
* @param {Function} callback.
*/
utility: function loadUtility(utility, callback) {
load('utilities', utility, callback);
}
};
exports.Loader = Loader;
}(window));

View file

@ -0,0 +1,7 @@
/* global navigationStack */
/* exported MainNavigation */
(function(exports) {
'use strict';
exports.MainNavigation = new navigationStack('view-contacts-list');
}(window));

View file

@ -0,0 +1,96 @@
'use strict';
/*
* This code is in charge of launching a diferent window
* to handle if one or more contacts have the same info
* that the one we are trying to save.
*/
(function(exports) {
var matchService = {};
const CONTACTS_APP_ORIGIN = location.origin;
var matcherWindow = null;
var currentURL;
function openWindow(url) {
if (!url || matcherWindow !== null) {
return;
}
matcherWindow = window.open(url);
currentURL = url;
init();
}
function closeWindow(messageId, additionalMessageId) {
matcherWindow.close();
unload();
}
matchService.match = function(contactId) {
var url =
'/contacts/views/matching/matching_contacts.html?contactId=' +
contactId;
openWindow(url);
};
matchService.showDuplicateContacts = function() {
var url =
'/contacts/views/matching/matching_contacts.html';
openWindow(url);
};
function init(){
matcherWindow.addEventListener('load', function fn(){
matcherWindow.removeEventListener('load', fn);
matcherWindow.postMessage({
type: 'sync'
}, CONTACTS_APP_ORIGIN);
matcherWindow.onunload = function(){
window.postMessage({
type: 'window_close'
}, CONTACTS_APP_ORIGIN);
};
}, false);
}
// This function can also be executed when other messages arrive
// That's why we cannot call notifySettings outside the switch block
function messageHandler(e) {
if (!currentURL || e.origin !== CONTACTS_APP_ORIGIN) {
return;
}
var data = e.data;
switch (data.type) {
case 'ready':
matcherWindow.postMessage({
type: 'dom_transition_end',
data: ''
}, CONTACTS_APP_ORIGIN);
window.dispatchEvent(new CustomEvent('image-loader-pause'));
break;
case 'window_close':
closeWindow(data.messageId, data.additionalMessageId);
break;
case 'show_duplicate_contacts':
matcherWindow.postMessage(data, CONTACTS_APP_ORIGIN);
break;
case 'duplicate_contacts_merged':
matcherWindow.postMessage(data, CONTACTS_APP_ORIGIN);
break;
}
}
function unload() {
// Attaching again scrolling handlers on the contact list's image loader
matcherWindow = currentURL = null;
window.dispatchEvent(new CustomEvent('image-loader-resume'));
}
window.addEventListener('message', messageHandler);
exports.MatchService = matchService;
}(window));

View file

@ -0,0 +1,49 @@
'use strict';
/* global fb, utils, LazyLoader, DatastoreMigration, ImportStatusData*/
/* exported Migrator */
var Migrator = (function(){
var start = function start(config) {
if (!config || !config.fbMigrated) {
migrateFbContacts();
}
if (!config || !config.accessTokenMigrated) {
migrateFbToken();
}
};
function migrateFbContacts() {
LazyLoader.load('js/fb/datastore_migrator.js', function() {
new DatastoreMigration().start();
});
}
function migrateFbToken() {
LazyLoader.load([
'/shared/js/contacts/import/import_status_data.js',
], function() {
var STORAGE_KEY = fb.utils.TOKEN_DATA_KEY;
ImportStatusData.get(STORAGE_KEY).then(function(datastoreToken) {
if (!datastoreToken) {
utils.cookie.update({accessTokenMigrated: true});
window.asyncStorage.getItem(STORAGE_KEY, function(asyncstorageToken) {
if (asyncstorageToken) {
ImportStatusData.put(STORAGE_KEY, asyncstorageToken).then(
function() {
window.asyncStorage.removeItem(STORAGE_KEY);
});
}
});
}
});
});
}
return {
start: start
};
})();

View file

@ -0,0 +1,290 @@
'use strict';
/* global LazyLoader */
/* exported navigationStack */
function navigationStack(currentView) {
/* jshint validthis:true */
// Each transition entry includes a 'forwards' property including the
// classes which will be added to the 'current' and 'next' view when the
// transition goes forwards, as well as a 'backwards' property including the
// classes which will be added to the 'current' and 'next' view when the
// transition goes backwards.
this.transitions = {
'none': {
forwards: {},
backwards: {}
},
'right-left': {
forwards: {
next: 'app-go-in'
},
backwards: {
current: 'app-go-back-out'
}
},
'popup': {
forwards: {
next: 'app-go-up-in'
},
backwards: {
current: 'app-go-up-back-out'
}
},
'activity-popup': {
forwards: {
next: 'app-go-up-in'
},
backwards: {}
},
'fade-in': {
forwards: {
next: 'fade-in'
},
backwards: {
current: 'fade-out'
}
},
'go-deeper': {
forwards: {
current: 'app-go-deeper-out',
next: 'app-go-deeper-in'
},
backwards: {
current: 'app-go-deeper-back-out',
next: 'app-go-deeper-back-in'
}
},
'go-deeper-search': {
forwards: {
current: 'move-out-from-forward',
next: 'move-in-from-forward'
},
backwards: {
current: 'move-out-from-backward',
next: 'move-in-from-backward'
}
}
};
var COMMS_APP_ORIGIN = location.origin;
var screenshotViewId = 'view-screenshot';
var _currentView = currentView;
document.getElementById(currentView).classList.add('current');
this.stack = [];
navigationStack._zIndex = navigationStack._zIndex || 0;
this.stack.push({view: _currentView, transition: 'popup',
zIndex: ++navigationStack._zIndex});
var waitForAnimation = function ng_waitForAnimation(view, callback) {
if (!callback) {
return;
}
view.addEventListener('animationend', function ng_onAnimationEnd() {
view.removeEventListener('animationend', ng_onAnimationEnd);
setTimeout(callback, 0);
});
};
this.go = function go(nextView, transition, callback) {
if (_currentView === nextView) {
if (callback) {
callback();
}
return;
}
var parent = window.parent;
if (nextView == 'view-contact-form') {
parent.postMessage({type: 'hide-navbar'}, COMMS_APP_ORIGIN);
}
// Remove items that match nextView from the stack to prevent duplicates.
this.stack = this.stack.filter(function(item) {
return item.view != nextView;
});
var current;
var currentClassList;
// Performance is very bad when there are too many contacts so we use
// -moz-element and animate this 'screenshot" element.
if (transition.indexOf('go-deeper') === 0) {
current = document.getElementById(screenshotViewId);
// Load the screenshot dom content
LazyLoader.load([current]);
current.style.zIndex = this.stack[this.stack.length - 1].zIndex;
currentClassList = current.classList;
if (transition.indexOf('search') !== -1) {
currentClassList.add('search');
} else {
currentClassList.add('contact-list');
}
currentClassList.remove('hide');
} else {
current = document.getElementById(_currentView);
currentClassList = current.classList;
}
var forwardsClasses = this.transitions[transition].forwards;
// Add forwards class to current view.
currentClassList.add('block-item');
if (forwardsClasses.current) {
currentClassList.add(forwardsClasses.current);
}
var next = document.getElementById(nextView);
// Add forwards class to next view.
if (forwardsClasses.next) {
next.classList.add(forwardsClasses.next);
next.addEventListener('animationend', function ng_onNextBackwards(ev) {
next.removeEventListener('animationend', ng_onNextBackwards);
next.classList.remove('block-item');
});
}
next.classList.add('current');
var realCurrentView = document.getElementById(_currentView);
var _callbackInner = function _callback() {
next.classList.add('current');
realCurrentView.classList.remove('current');
if (callback) {
callback();
}
};
if (transition === 'none') {
setTimeout(_callbackInner, 0);
} else {
waitForAnimation(next, _callbackInner);
}
var zIndex = ++navigationStack._zIndex;
this.stack.push({ view: nextView, transition: transition,
zIndex: zIndex});
next.style.zIndex = zIndex;
_currentView = nextView;
};
this.back = function back(callback) {
if (this.stack.length < 2) {
if (typeof callback === 'function') {
setTimeout(callback, 0);
}
return;
}
var currentView = this.stack.pop();
var current = document.getElementById(currentView.view);
var currentClassList = current.classList;
var nextView = this.stack[this.stack.length - 1];
var transition = currentView.transition;
var next = document.getElementById(nextView.view);
var nextClassList;
// Performance is very bad when there are too many contacts so we use
// -moz-element and animate this 'screenshot" element.
if (transition.indexOf('go-deeper') === 0) {
next = document.getElementById(screenshotViewId);
} else {
next = document.getElementById(nextView.view);
}
nextClassList = next.classList;
var forwardsClasses = this.transitions[transition].forwards;
var backwardsClasses = this.transitions[transition].backwards;
if (currentView.view == 'view-contact-form') {
parent.postMessage({type: 'show-navbar'}, COMMS_APP_ORIGIN);
}
// Add backwards class to current view.
if (backwardsClasses.current) {
currentClassList.add('block-item');
currentClassList.add(backwardsClasses.current);
current.addEventListener('animationend',
function ng_onCurrentBackwards() {
current.removeEventListener('animationend', ng_onCurrentBackwards);
// Once the backwards animation completes, delete the added classes
// to restore the elements to their initial state.
currentClassList.remove(forwardsClasses.next);
currentClassList.remove(backwardsClasses.current);
current.style.zIndex = null;
currentClassList.remove('block-item');
if (!backwardsClasses.next) {
nextClassList.remove('block-item');
}
}
);
} else {
current.style.zIndex = null;
currentClassList.remove('block-item');
if (!backwardsClasses.next) {
nextClassList.remove('block-item');
}
}
next.style.zIndex = nextView.zIndex;
// Add backwards class to next view.
if (backwardsClasses.next) {
nextClassList.add(backwardsClasses.next);
next.addEventListener('animationend', function ng_onNextBackwards() {
next.removeEventListener('animationend', ng_onNextBackwards);
// Once the backwards animation completes, delete the added classes
// to restore the elements to their initial state.
nextClassList.remove(forwardsClasses.current);
nextClassList.remove(backwardsClasses.next);
if (transition.indexOf('go-deeper') === 0) {
// Hide the -moz-element element once the animation completes.
nextClassList.add('hide');
nextClassList.remove('search');
nextClassList.remove('contact-list');
}
nextClassList.remove('block-item');
});
}
document.getElementById(nextView.view).classList.add('current');
var _callbackInner = function _callbackInner() {
document.getElementById(nextView.view).classList.add('current');
currentClassList.remove('current');
if (callback) {
callback();
}
};
if ((!backwardsClasses.current && !backwardsClasses.next) ||
transition === 'none') {
setTimeout(_callbackInner, 0);
} else {
waitForAnimation(current, _callbackInner);
}
_currentView = nextView.view;
navigationStack._zIndex = nextView.zIndex;
};
this.home = function home(callback) {
if (this.stack.length < 2) {
if (typeof callback === 'function') {
setTimeout(callback, 0);
}
return;
}
while (this.stack.length > 1) {
this.back(callback);
}
};
this.currentView = function currentView() {
return _currentView != null ? _currentView : '';
};
}

View file

@ -0,0 +1,94 @@
'use strict';
/* global LazyLoader, ContactToVcard, MozNDEFRecord, fb, utils,
NDEF, NfcUtils, utils*/
/* exported NFC */
(function(exports) {
var mozNfc = window.navigator.mozNfc;
var currentContact;
var vCardContact;
var mozNfcPeer;
var startListening = function(contact) {
if (!mozNfc) {
return;
}
currentContact = contact;
// We cannot share Facebook data via NFC so we check if the contact
// is an FB contacts. However, if the contact is linked to a regular
// mozContact, we can share linked data
if (fb && !(fb.isFbContact(contact) && !fb.isFbLinked(contact))) {
mozNfc.onpeerready = handlePeerReady;
} else {
mozNfc.onpeerready = handlePeerReadyForFb;
}
};
var stopListening = function() {
if (!mozNfc) {
return;
}
mozNfc.onpeerready = null;
currentContact = null;
vCardContact = null;
mozNfcPeer = null;
};
var handlePeerReady = function(event) {
mozNfcPeer = event.peer;
LazyLoader.load([
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js',
'/shared/js/nfc_utils.js'
], function() {
ContactToVcard(
[currentContact],
function append(vcard) {
vCardContact = vcard;
},
function success() {
sendContact();
},
// use default batch size
null,
// We don't want to share a profile photo via NFC,
// like on Android:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1003767#c5
true
);
});
};
var sendContact = function() {
var nfcUtils = new NfcUtils();
var NDEFRecord = new MozNDEFRecord({
tnf: NDEF.TNF_MIME_MEDIA,
type: nfcUtils.fromUTF8('text/vcard'),
payload: nfcUtils.fromUTF8(vCardContact)
});
var promise = mozNfcPeer.sendNDEF([NDEFRecord]);
promise.then(() => {
console.log('Contact succesfuly sent');
}).catch(e => {
console.log('Something goes wrong : ' + e);
});
};
var handlePeerReadyForFb = function() {
utils.status.show({
id: 'facebook-export-forbidden'
});
};
var nfc_tools = {
startListening: startListening,
stopListening: stopListening
};
exports.NFC = nfc_tools;
}(window));

View file

@ -0,0 +1,185 @@
'use strict';
/* exported extServices*/
/* global fb */
/* global utils */
(function(exports) {
var onContactsImportedCb = null;
var extServices = {
set onContactsImported(cb) {
if (typeof cb !== 'function') {
return;
}
onContactsImportedCb = cb;
}
};
var extensionFrame = document.querySelector('#iframe_extensions');
var oauthFrame = document.querySelector('#iframe_oauth');
oauthFrame.src = '/shared/pages/import/oauth.html';
var currentURI, access_token;
var canClose = true, canCloseLogout = true;
var closeRequested = false;
extServices.importGmail = function(evt) {
loadService('gmail');
};
extServices.importLive = function(evt) {
loadService('live');
};
function loadService(serviceName) {
closeRequested = false;
canClose = false;
canCloseLogout = false;
load('/shared/pages/import/import.html?service=' + serviceName,
'friends', serviceName);
}
function load(uri, from, serviceName) {
oauthFrame.contentWindow.postMessage({
type: 'start',
data: {
from: from,
service: serviceName
}
}, fb.CONTACTS_APP_ORIGIN);
currentURI = uri;
}
function unload() {
// Attaching again scrolling handlers on the contact list's image loader
window.dispatchEvent(new CustomEvent('image-loader-resume'));
extensionFrame.src = currentURI = null;
}
function close(messageId, additionalMessageId) {
// Aborting the close if was closed previously, ie: no contacts
if (!extensionFrame.classList.contains('opening')) {
return;
}
extensionFrame.addEventListener('transitionend', function tclose() {
extensionFrame.removeEventListener('transitionend', tclose);
extensionFrame.classList.add('hidden');
if (canClose === true && canCloseLogout === true) {
unload();
}
else {
closeRequested = true;
}
if (messageId) {
utils.status.show(messageId, additionalMessageId);
}
// Otherwise we do nothing as the sync process will finish sooner or later
});
extensionFrame.classList.remove('opening');
}
function notifySettings(evtype) {
// Notify observers that a change from FB could have happened
var eventType = evtype || 'fb_changed';
var event = new CustomEvent(eventType, {
'detail' : true
});
document.dispatchEvent(event);
}
// This function can also be executed when other messages arrive
// That's why we cannot call notifySettings outside the switch block
function messageHandler(e) {
if (!currentURI || e.origin !== fb.CONTACTS_APP_ORIGIN) {
return;
}
var data = e.data;
switch (data.type) {
case 'ready':
extensionFrame.classList.remove('hidden');
window.setTimeout(function displaying() {
extensionFrame.classList.add('opening');
extensionFrame.addEventListener('transitionend', function topen() {
extensionFrame.removeEventListener('transitionend', topen);
extensionFrame.contentWindow.postMessage({
type: 'dom_transition_end',
data: ''
}, fb.CONTACTS_APP_ORIGIN);
// Stop scrolling listeners on the contact list's image loader to
// prevent images cancelled while friends are being imported
window.dispatchEvent(new CustomEvent('image-loader-pause'));
});
}, 0);
break;
case 'authenticated':
extensionFrame.src = currentURI;
access_token = data.data;
break;
case 'token_stored':
notifySettings('fb_token_ready');
break;
case 'token_error':
notifySettings('fb_token_error');
break;
case 'abort':
unload();
break;
case 'window_close':
close(data.messageId, data.additionalMessageId);
notifySettings();
break;
case 'import_updated':
extensionFrame.contentWindow.postMessage({
type: 'contacts_loaded',
data: ''
}, fb.CONTACTS_APP_ORIGIN);
if (onContactsImportedCb) {
onContactsImportedCb();
onContactsImportedCb = null;
}
break;
case 'sync_finished':
// Sync finished thus the iframe can be safely "removed"
canClose = true;
if (closeRequested && canCloseLogout) {
unload();
}
// Check whether there has been changes or not
if (data.data > 0) {
notifySettings();
}
break;
case 'logout_finished':
canCloseLogout = true;
if (closeRequested && canClose) {
unload();
}
break;
case 'messaging_ready':
extensionFrame.contentWindow.postMessage({
type: 'token',
data: access_token
}, fb.CONTACTS_APP_ORIGIN);
break;
}
}
window.addEventListener('message', messageHandler);
exports.ExtServices = extServices;
}(window));

View file

@ -0,0 +1,31 @@
'use strict';
/* exported TAG_OPTIONS */
var _ = navigator.mozL10n.get;
var TAG_OPTIONS = {
'phone-type' : [
{type: 'mobile', value: _('mobile')},
{type: 'home', value: _('home')},
{type: 'work', value: _('work')},
{type: 'personal', value: _('personal')},
{type: 'faxHome', value: _('faxHome')},
{type: 'faxOffice', value: _('faxOffice')},
{type: 'faxOther', value: _('faxOther')},
{type: 'other', value: _('other')}
],
'email-type' : [
{type: 'personal', value: _('personal')},
{type: 'home', value: _('home')},
{type: 'work', value: _('work')},
{type: 'other', value: _('other')}
],
'address-type' : [
{type: 'current', value: _('current')},
{type: 'home', value: _('home')},
{type: 'work', value: _('work')}
],
'date-type': [
{type: 'birthday', value: _('birthday')},
{type: 'anniversary', value: _('anniversary')}
]
};

View 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;
})();

View 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;
};
}

View 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
};
}();

View 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);

View 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
};
}());

View 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);

View 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;
};
})();
}

View file

@ -0,0 +1 @@
;;;'use strict';(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;})();

View 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]');
};

View 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));

View 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);

View file

@ -0,0 +1,641 @@
'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
};
})();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,108 @@
/* global ICEData */
/* global ImageLoader */
/* global LazyLoader */
/* global MainNavigation */
'use strict';
(function(exports) {
var contactsIds = null;
var rowBuilderFn = null;
var imgLoader = null;
var initialized = false;
var iceListDisplayed = false;
/**
* Initialises the component by passing a list of ICE ids,
* a function that knows how to build contact list rows and
* another function to perform actions when clicking on a
* ICE contact.
* This function should be the same than the contacts list one
* to keep the same behavior (activities, select, etc.)
* @param ids (Array) array of contact ids
* @param rowBuilder (function) function that paints contac list
* rows, shared with the contact list
* @param clickHandler (function) action to perform when clicked
* on a item
*/
function init(ids, rowBuilder, clickHandler) {
rowBuilderFn = rowBuilder;
contactsIds = ids;
imgLoader = new ImageLoader('#ice-list', 'li');
buildICEContactsList();
if (initialized) {
return;
}
initialized = true;
// Initialise common controls
document.getElementById('ice-header').addEventListener(
'action', hideICEList);
document.getElementById('ice-list').addEventListener('click', clickHandler);
LazyLoader.load(['/contacts/js/utilities/ice_data.js'], function() {
listenForChanges();
});
}
// Once the list is built, we need to be aware of possible changes in
// contacts. In that case rebuild the whole list, since ICE list is meant
// to be a small set (2 currently) of contacts.
function listenForChanges() {
ICEData.listenForChanges(function(newList) {
contactsIds = [];
newList.forEach(function(iceContact) {
if (iceContact.id && iceContact.active) {
contactsIds.push(iceContact.id);
}
});
buildICEContactsList();
});
}
// Clone the nodes on the contacts list, also rebuild them with
// the row builder function passed on initialization.
// Due to the nature of the contacts list, despite of having the
// nodes, those may have not being initialized, that's why we need
// the use of this builder function
function buildICEContactsList() {
var iceList = document.getElementById('ice-group');
iceList.innerHTML = '';
contactsIds.forEach(function(id) {
var node = document.querySelector('[data-uuid="' + id + '"]');
if (!node) {
return;
}
node = rowBuilderFn(id, node);
iceList.appendChild(node);
});
imgLoader.reload();
}
function showICEList() {
MainNavigation.go('ice-view', 'right-left');
iceListDisplayed = true;
}
function hideICEList() {
MainNavigation.back();
iceListDisplayed = false;
}
var ICEView = {
init: init,
showICEList: showICEList,
hideICEList: hideICEList,
get iceListDisplayed() {
return iceListDisplayed;
}
};
exports.ICEView = ICEView;
})(window);

View file

@ -0,0 +1,354 @@
/* global Contacts */
/* global ICEData */
/* global ConfirmDialog */
/* global ContactsService */
/* global HeaderUI */
/* global Search */
/**
* ICE Settings view. In charge of selecting
* the contacts for emergency and keeping sync
* the datastore to share those contacts with
* dialer and lockscreen.
*/
'use strict';
var contacts = window.contacts || {};
(function(exports) {
var iceSettingsPanel,
iceSettingsHeader,
iceContactItems = [],
iceContactCheckboxes = [],
iceContactButtons = [],
iceScreenInitialized = false,
currentICETarget;
/**
* Redraws the ICE contact list. This is not an expensive
* operation since we have a maximun of 2 contacts.
* The first time executed will attach listeners for the
* frame ui.
*/
var init = function ice_init() {
if (iceScreenInitialized) {
return;
}
// ICE DOM elements
iceSettingsPanel = document.getElementById('ice-settings');
iceSettingsHeader = document.getElementById('ice-settings-header');
iceContactItems.push(document.getElementById('ice-contacts-1-switch'));
iceContactItems.push(document.getElementById('ice-contacts-2-switch'));
iceContactCheckboxes.push(iceContactItems[0]
.querySelector('[name="ice-contact-1-enabled"]'));
iceContactCheckboxes.push(iceContactItems[1]
.querySelector('[name="ice-contact-2-enabled"]'));
iceContactButtons.push(document.getElementById('select-ice-contact-1'));
iceContactButtons.push(document.getElementById('select-ice-contact-2'));
iceContactButtons[0].dataset.contactId = '';
iceContactButtons[1].dataset.contactId = '';
// ICE Events handlers
iceSettingsHeader.addEventListener('action', function(){
contacts.Settings.navigation.back();
});
// All the controls do the same, just modifications on the
// specific order.
iceContactItems.forEach(function(item, index) {
iceContactCheckboxes[index].addEventListener('change', function(i) {
return function(evt) {
var localIceContacts = ICEData.iceContacts;
var wasActive = !iceContactCheckboxes[i].checked;
if (wasActive) {
resetIceGroupState(i);
if (localIceContacts[i] && localIceContacts[i].id) {
setICEContact(null, i, !wasActive);
}
}
else {
iceContactButtons[i].disabled = false;
}
};
}(index));
});
iceContactButtons.forEach(function(element){
element.addEventListener('click', function(evt) {
showSelectList(evt.target.id);
});
// Bug 1071064 If users clicked on disabled button, the switch was checked
element.parentNode.addEventListener('click', (evt) => {
evt.stopPropagation();
});
});
iceScreenInitialized = true;
};
function reloadButtonsState(cb) {
ICEData.load().then(retrieveContactData.bind(null, cb));
}
function retrieveContactData(cb) {
var iceContactsData = [];
var iceContactsIds = ICEData.iceContacts;
var numRetrievedContacts = 0;
iceContactsIds.forEach(function(iceContact, index) {
ContactsService.get(iceContact.id, function(cindex, contact) {
var theContact = {
active: iceContactsIds[cindex].active,
mozContact: contact
};
iceContactsData[cindex] = theContact;
numRetrievedContacts++;
if (numRetrievedContacts === 2) {
setButtonsState(iceContactsData, cb);
}
}.bind(null, index));
});
}
function refresh(done) {
if (!iceScreenInitialized) {
init();
}
reloadButtonsState(done);
}
/**
* Given an object representing the internal state
* fills the UI elements.
* @params iceContactsIds (Array) list of contacts and state
*/
function setButtonsState(iceContactsData, done) {
iceContactsData.forEach(setIceButtonState);
typeof done === 'function' && done();
}
function setIceButtonState(iceContactData, index) {
if (!iceContactData) {
return;
}
if (iceContactData.mozContact) {
var iceContact = iceContactData.mozContact;
var givenName = (Array.isArray(iceContact.givenName) &&
iceContact.givenName[0]) || '';
var familyName = (Array.isArray(iceContact.familyName) &&
iceContact.familyName[0]) || '';
var display = [givenName, familyName];
var iceLabel = display.join(' ').trim();
// If contact has no name we the first tel number will be used
if (!iceLabel) {
if (Array.isArray(iceContact.tel) && iceContact.tel[0]) {
iceLabel = iceContact.tel[0].value.trim();
}
}
buildIceContactUI(index, iceLabel, iceContact.id,
iceContactData.active);
}
else {
resetIceGroupState(index);
}
}
function buildIceContactUI(index, label, contactId, active) {
iceContactCheckboxes[index].checked = active;
iceContactButtons[index].disabled = !active;
var bdi = document.createElement('bdi');
bdi.classList.add('ice-contact');
bdi.textContent = label;
iceContactButtons[index].innerHTML = '';
iceContactButtons[index].appendChild(bdi);
iceContactButtons[index].dataset.contactId = contactId;
}
function resetIceGroupState(index) {
iceContactCheckboxes[index].checked = false;
iceContactButtons[index].disabled = true;
iceContactButtons[index].innerHTML = '';
iceContactButtons[index].dataset.contactId = '';
iceContactButtons[index].setAttribute('data-l10n-id', 'ICESelectContact');
}
function resetIceGroupStates() {
for(var j = 0; j < iceContactCheckboxes.length; j++) {
resetIceGroupState(j);
}
}
function goBack() {
contacts.List.clearClickHandlers();
contacts.List.handleClick(Contacts.showContactDetail);
HeaderUI.setNormalHeader();
var hasICESet = ICEData.iceContacts.find(function(x) {
return x.active === true;
});
if (!hasICESet) {
resetIceGroupStates();
}
if (window.Search && Search.isInSearchMode()) {
Search.exitSearchMode();
}
contacts.Settings.navigation.back(() => {
hasICESet && contacts.List.toggleICEGroup(true);
});
}
/**
* Given a contact id, saves it internally. Also restores the contact
* list default handler.
* In case of not valid contact it will display a message on the screen
* indicating the cause of the error.
* @param id (string) contact id
*/
function selectICEHandler(id) {
checkContact(id).then(function() {
setICEContact(id, currentICETarget, true, goBack);
}, function error(l10nId) {
var dismiss = {
title: 'ok',
callback: function() {
ConfirmDialog.hide();
}
};
ConfirmDialog.show(null, l10nId || 'ICEUnknownError', dismiss);
});
}
/**
* Will perform a series of checks to validate the selected
* contact as a valid ICE contact
* @param id (string) contact id
* @return (Promise) fulfilled if contact is valid
*/
function checkContact(id) {
return ICEData.load().then(function() {
return contactNotICE(id).then(contactNotAllowed);
});
}
/**
* Checks if a contacts is already set as ICE
* @param id (string) contact id
* @return (Promise) fulfilled if contact is not repeated,
* rejected otherwise
*/
function contactNotICE(id) {
return new Promise(function(resolve, reject) {
var isICE = ICEData.iceContacts.some(function(x) {
return x.id === id;
});
if (isICE) {
reject('ICERepeatedContact');
} else {
resolve(id);
}
});
}
/**
* Filter to avoid selecting contacts as ICE if they don't have a phone number
* or they are a Facebook contact
* @param id (String) contact id
* @returns (Promise) Fulfilled if contact has phone
*/
function contactNotAllowed(id) {
return new Promise(function(resolve, reject) {
ContactsService.get(id, function(contact) {
if(Array.isArray(contact.tel) && contact.tel[0] &&
contact.tel[0].value && contact.tel[0].value.trim()) {
resolve(id);
} else {
reject('ICEContactNoNumber');
}
});
});
}
/**
* We are using the contact list to choose a contact as ICE. For doing
* this, we need to setup our own click handler.
* @para target (HTMLButton) Button click to select an ICE contact
*/
function showSelectList(target) {
contacts.List.toggleICEGroup(false);
HeaderUI.setCancelableHeader(goBack, 'selectContact');
contacts.Settings.navigation.go('view-contacts-list', 'right-left');
currentICETarget = target === 'select-ice-contact-1' ? 0 : 1;
contacts.List.clearClickHandlers();
contacts.List.handleClick(selectICEHandler);
}
/**
* Set the values for ICE contacts, 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
* @param cb (function) callback when ready
*/
function setICEContact(id, pos, active, cb) {
ICEData.setICEContact(id, pos, active).then(function() {
// Only reload contact info in case there is a change in the contact
if (id === iceContactButtons[pos].dataset.contactId) {
return;
}
ContactsService.get(id, function(contact) {
var theContact = {
active: active,
mozContact: contact
};
var iceContactData = [];
if (pos === 0) {
iceContactData[1] = null;
}
else {
iceContactData[0] = null;
}
iceContactData[pos] = theContact;
setButtonsState(iceContactData);
typeof cb === 'function' && cb();
});
});
}
function reset() {
iceScreenInitialized = false;
iceContactItems = [];
iceContactCheckboxes = [];
iceContactButtons = [];
currentICETarget = null;
}
exports.ICE = {
init: init,
refresh: refresh,
reset: reset,
get initialized() { return iceScreenInitialized; }
};
})(window);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,148 @@
/* global Contacts, contacts, utils, LazyLoader */
/* global ContactsService */
/* global Matcher */
'use strict';
window.Contacts = window.Contacts || {};
Contacts.MultipleSelect = (function() {
const STATUS_TIMEOUT = 3000; // ms before closing view which shows status msg
var contactsToImport = [];
var header, saveButton, container, template, title;
/**
* Initializes references to DOM elements. This is needed before calling
* render.
*/
function init() {
header = document.getElementById('multiple-select-view-header');
saveButton = document.getElementById('save-button');
container = document.getElementById('multiple-select-container');
template = document.getElementById('contactToImportTemplate');
title = document.getElementById('multiple-select-view-title');
saveButton.addEventListener('click', importAll);
header.addEventListener('action', cancelHandler);
}
function cancelHandler() {
header.removeEventListener('action', cancelHandler);
saveButton.removeEventListener('click', importAll);
Contacts.cancel();
}
function importAll() {
var numDupsMerged = 0,
importedContacts = 0,
parsedContacts = 0;
const DEPENDENCIES = [
'/shared/js/contacts/import/utilities/status.js',
'/shared/js/simple_phone_matcher.js',
'/shared/js/contacts/contacts_matcher.js',
'/shared/js/contacts/contacts_merger.js',
'/shared/js/contacts/merger_adapter.js'
];
LazyLoader.load(DEPENDENCIES, function() {
contactsToImport.forEach((contact, index) => {
Matcher.match(contact, 'passive', {
onmatch: (matches) => {
var callbacks = {
success: () => {
numDupsMerged++;
doContinue(true);
},
error: doContinue
};
contacts.adaptAndMerge(contact, matches, callbacks);
},
onmismatch: () => {
ContactsService.save(
contact,
function(e) {
if (e) {
console.error(e);
doContinue();
return;
}
doContinue(true);
}
);
}
});
});
});
function doContinue(isContactImported) {
parsedContacts++;
if (isContactImported) {
importedContacts++;
}
if (contactsToImport.length <= parsedContacts) {
utils.status.show({
id: 'vCardContacts-imported',
args: {
n: importedContacts
}
}, {
id: 'contactsMerged',
args: {
numDups: numDupsMerged
}
});
window.setTimeout(cancelHandler, STATUS_TIMEOUT);
}
}
}
/**
* Allows to preview which contacts will be imported. Requires to call init
* first when DOM is ready.
*
* @param {mozContact[]} contacts - Every contact already read from vCard.
* @param cursor - Reader object to continue reading from vCard.
* @param {string} filename - Name of the vcf file we are reading from.
*/
var render = function(contacts, cursor, filename) {
container.innerHTML = ''; // Avoids rendering the same content twice.
title.textContent = filename;
contacts.forEach(doRenderContact);
cursor.onsuccess = function(event) {
var contact = event.target.result;
if (contact) {
doRenderContact(contact);
cursor.continue();
}
};
function doRenderContact(contact) {
var clone = document.importNode(template.content, true);
var contactData = clone.querySelectorAll('p');
contactData[0].textContent =
Array.isArray(contact.name) && contact.name[0];
var org = (Array.isArray(contact.org) && contact.org[0]) ?
contact.org[0] : '';
contactData[1].textContent = org;
if (Array.isArray(contact.photo) && contact.photo[0] instanceof Blob) {
var picture = clone.querySelector('aside span');
picture.style.backgroundImage = 'url(' +
window.URL.createObjectURL(contact.photo[0]) +
')';
}
contactsToImport.push(contact);
container.appendChild(clone);
}
};
return {
render: render,
init: init
};
})();

View file

@ -0,0 +1,856 @@
'use strict';
/* global _ */
/* global Cache */
/* global ConfirmDialog */
/* global Contacts */
/* global ContactsBTExport */
/* global ContactsExporter */
/* global ContactsSDExport */
/* global ContactsSIMExport */
/* global IccHandler */
/* global LazyLoader */
/* global navigationStack */
/* global Rest */
/* global SimContactsImporter */
/* global SimDomGenerator */
/* global utils */
/* global VCFReader */
/* global ContactsService */
/* global ExtServices */
/* global Loader */
/* global BulkDelete */
/* global ICE */
/* global Telemetry */
var contacts = window.contacts || {};
/***
This class handles all the activity regarding
the settings screen for contacts
**/
contacts.Settings = (function() {
var navigationHandler,
importSettingsHeader,
orderCheckBox,
orderItem,
orderByLastName,
setICEButton,
importSettingsPanel,
importSettingsTitle,
importContacts,
exportContacts,
importOptions,
exportOptions,
importLiveOption,
importGmailOption,
importSDOption,
exportSDOption,
newOrderByLastName = null,
PENDING_LOGOUT_KEY = 'pendingLogout',
bulkDeleteButton;
var EXPORT_TRANSITION_LEVEL = 2, DELETE_TRANSITION_LEVEL = 1;
// Initialise the settings screen (components, listeners ...)
var init = function initialize() {
// Create the DOM for our SIM cards and listen to any changes
IccHandler.init(new SimDomGenerator(), contacts.Settings.cardStateChanged);
initContainers();
// To avoid any race condition we listen for online events once
// containers have been initialized
window.addEventListener('online', checkOnline);
window.addEventListener('offline', checkOnline);
// Subscribe to events related to change state in the sd card
utils.sdcard.subscribeToChanges('check_sdcard', function(value) {
updateStorageOptions(utils.sdcard.checkStorageCard());
});
window.addEventListener('timeformatchange', updateTimestamps);
};
var hideSettings = function hideSettings() {
contacts.Settings.close();
};
// Get the different values that we will show in the app
var getData = function getData() {
var config = utils.cookie.load();
var order = config ? config.order : false;
orderByLastName = order;
newOrderByLastName = null;
updateOrderingUI();
};
var updateOrderingUI = function updateOrderingUI() {
var value = newOrderByLastName === null ? orderByLastName :
newOrderByLastName;
orderCheckBox.checked = value;
orderCheckBox.setAttribute('aria-checked', value);
};
var updateImportTitle = function updateImportTitle(l10nString) {
importSettingsTitle.setAttribute('data-l10n-id', l10nString);
};
// Initialises variables and listener for the UI
var initContainers = function initContainers() {
orderItem = document.getElementById('settingsOrder');
orderCheckBox = orderItem.querySelector('[name="order.lastname"]');
orderCheckBox.addEventListener('change', onOrderingChange.bind(this));
// Creating a navigation handler from this view
navigationHandler = new navigationStack('view-settings');
// Init panel & elements for caching them
importSettingsPanel = document.getElementById('import-settings');
importSDOption = document.getElementById('import-sd-option');
exportSDOption = document.getElementById('export-sd-option');
importSettingsTitle = document.getElementById('import-settings-title');
importLiveOption = document.getElementById('import-live-option');
importGmailOption = document.getElementById('import-gmail-option');
// ICE view
setICEButton = document.getElementById('set-ice');
/*
* Adding listeners
*/
document.getElementById('settings-close').addEventListener(
'click',
hideSettings
);
// Listener for updating the timestamp based on extServices
window.addEventListener('message', function updateList(e) {
if (e.data.type === 'import_updated') {
updateTimestamps();
checkNoContacts();
}
});
// Navigation back
importSettingsHeader = document.getElementById('import-settings-header');
importSettingsHeader.addEventListener('action', importSettingsBackHandler);
// Handlers for the navigation through the panels
importContacts = document.getElementById('importContacts');
importContacts.firstElementChild.
addEventListener('click', importContactsHandler);
exportContacts = document.getElementById('exportContacts');
exportContacts.firstElementChild.
addEventListener('click', exportContactsHandler);
// Handlers for the actions related with EXPORT/IMPORT
importOptions = document.getElementById('import-options');
importOptions.addEventListener('click', importOptionsHandler);
exportOptions = document.getElementById('export-options');
exportOptions.addEventListener('click', exportOptionsHandler);
// ICE view
setICEButton.addEventListener('click', showICEScreen);
// Bulk delete
bulkDeleteButton = document.getElementById('bulkDelete');
bulkDeleteButton.addEventListener('click', bulkDeleteHandler);
};
// UI event handlers
function importSettingsBackHandler() {
navigationHandler.back(function navigateBackHandler() {
// Removing the previous assigned style for having
// a clean view
importSettingsPanel.classList.remove('export');
importSettingsPanel.classList.remove('import');
});
}
function importContactsHandler() {
// Hide elements for export and transition
LazyLoader.load(['/contacts/js/utilities/telemetry.js'], () => {
importSettingsPanel.classList.remove('export');
importSettingsPanel.classList.add('import');
updateImportTitle('importContactsTitle');
navigationHandler.go('import-settings', 'right-left');
});
}
function exportContactsHandler() {
// Hide elements for import and transition
LazyLoader.load(['/contacts/js/export/contacts_exporter.js'], loadSearch);
function loadSearch() {
Loader.view('search', function() {
importSettingsPanel.classList.remove('import');
importSettingsPanel.classList.add('export');
updateImportTitle('exportContactsTitle');
navigationHandler.go('import-settings', 'right-left');
});
}
}
function showICEScreen(cb) {
LazyLoader.load([
'/contacts/js/utilities/ice_data.js',
'/contacts/js/views/ice_settings.js',
'/shared/js/contacts/utilities/ice_store.js'], function(){
ICE.refresh();
navigationHandler.go('ice-settings', 'right-left');
if (typeof cb === 'function') {
cb();
}
});
}
// Given an event, select wich should be the targeted
// import/export source
function getSource(e) {
var source = e.target.parentNode.dataset.source;
// Check special cases
if (source && source.indexOf('-') != -1) {
source = source.substr(0, source.indexOf('-'));
}
return source;
}
function logImportUsage(name) {
return Telemetry.logImportUsage(name);
}
function importOptionsHandler(e) {
/* jshint validthis:true */
var source = getSource(e);
switch (source) {
case 'sim':
var iccId = e.target.parentNode.dataset.iccid;
window.setTimeout(requireSimImport.bind(this,
onSimImport.bind(this, iccId)), 0);
break;
case 'sd':
window.setTimeout(requireOverlay.bind(this, onSdImport), 0);
break;
case 'gmail':
ExtServices.onContactsImported = logImportUsage.bind(null, 'gmail');
ExtServices.importGmail();
break;
case 'live':
ExtServices.onContactsImported = logImportUsage.bind(null, 'live');
ExtServices.importLive();
break;
}
}
var bulkDeleteHandler = function bulkDeleteHandler() {
LazyLoader.load(
[
'/contacts/js/contacts_bulk_delete.js',
'/contacts/js/contacts_remover.js'
],
function() {
Loader.view('search', function() {
contacts.List.selectFromList(_('DeleteTitle'),
function onSelectedContacts(promise, done) {
BulkDelete.performDelete(promise, done);
},
null,
navigationHandler,
{
transitionLevel: DELETE_TRANSITION_LEVEL
}
);
});
}
);
};
function exportOptionsHandler(e) {
var source = getSource(e);
switch (source) {
case 'sim':
var iccId = e.target.parentNode.dataset.iccid;
LazyLoader.load(['/contacts/js/export/sim.js'],
function() {
doExport(new ContactsSIMExport(IccHandler.getIccById(iccId)));
}
);
break;
case 'sd':
LazyLoader.load(
[
'/shared/js/device_storage/get_storage_if_available.js',
'/shared/js/device_storage/get_unused_filename.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js',
'/contacts/js/export/sd.js'
],
function() {
doExport(new ContactsSDExport());
}
);
break;
case 'bluetooth':
LazyLoader.load(
[
'/shared/js/device_storage/get_storage_if_available.js',
'/shared/js/device_storage/get_unused_filename.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js',
'/contacts/js/export/bt.js'
],
function() {
doExport(new ContactsBTExport());
}
);
break;
}
}
function doExport(strategy) {
// Launch the selection mode in the list, and then invoke
// the export with the selected strategy.
contacts.List.selectFromList(_('exportContactsAction'),
function onSelectedContacts(promise) {
// Resolve the promise, meanwhile show an overlay to
// warn the user of the ongoin operation, dismiss it
// once we have the result
requireOverlay(function _loaded() {
utils.overlay.show('preparing-contacts', null, 'spinner');
promise.onsuccess = function onSuccess(ids) {
// Once we start the export process we can exit from select mode
// This will have to evolve once export errors can be captured
contacts.List.exitSelectMode();
var exporter = new ContactsExporter(strategy);
exporter.init(ids, function onExporterReady() {
// Leave the contact exporter to deal with the overlay
exporter.start();
});
};
promise.onerror = function onError() {
contacts.List.exitSelectMode();
utils.overlay.hide();
};
});
},
null,
navigationHandler,
{
isDanger: false,
transitionLevel: EXPORT_TRANSITION_LEVEL
}
);
}
// Options checking & updating
var checkSIMCard = function checkSIMCard() {
var statuses = IccHandler.getStatus();
statuses.forEach(function onStatus(status) {
enableSIMOptions(status.iccId, status.cardState);
});
};
// Disables/Enables an option and show the error if needed
var updateOptionStatus =
function updateOptionStatus(domOption, disabled, error) {
if (domOption === null) {
return;
}
var optionButton = domOption.firstElementChild;
if (disabled) {
optionButton.setAttribute('disabled', 'disabled');
if (error) {
domOption.classList.add('error');
} else {
domOption.classList.remove('error');
}
} else {
optionButton.removeAttribute('disabled');
domOption.classList.remove('error');
}
};
// Disables/Enables the actions over the sim import functionality
var enableSIMOptions = function enableSIMOptions(iccId, cardState) {
var importSimOption = document.getElementById('import-sim-option-' + iccId);
var exportSimOption = document.getElementById('export-sim-option-' + iccId);
var disabled = (cardState !== 'ready' && cardState !== 'illegal');
updateOptionStatus(importSimOption, disabled, true);
updateOptionStatus(exportSimOption, disabled, true);
};
/**
* Disables/Enables the actions over the sdcard import/export functionality
* @param {Boolean} cardAvailable Whether functions should be enabled or not.
*/
var updateStorageOptions = function updateStorageOptions(cardAvailable) {
// Enable/Disable button and shows/hides error message
updateOptionStatus(importSDOption, !cardAvailable, true);
updateOptionStatus(exportSDOption, !cardAvailable, true);
var importSDErrorL10nId = null;
var exportSDErrorL10nId = null;
var cardShared = utils.sdcard.status === utils.sdcard.SHARED;
if (!cardAvailable) {
importSDErrorL10nId = 'noMemoryCardMsg';
exportSDErrorL10nId = 'noMemoryCardMsgExport';
if (cardShared) {
importSDErrorL10nId = exportSDErrorL10nId = 'memoryCardUMSEnabled';
}
}
// update the message
var importSDErrorNode = importSDOption.querySelector('p.error-message');
if (importSDErrorL10nId) {
importSDErrorNode.setAttribute('data-l10n-id', importSDErrorL10nId);
} else {
importSDErrorNode.removeAttribute('data-l10n-id');
importSDErrorNode.textContent = '';
}
var exportSDErrorNode = exportSDOption.querySelector('p');
if (exportSDErrorL10nId) {
exportSDErrorNode.setAttribute('data-l10n-id', exportSDErrorL10nId);
} else {
exportSDErrorNode.removeAttribute('data-l10n-id');
exportSDErrorNode.textContent = '';
}
};
/**
* Loads the overlay class before showing
*/
function requireOverlay(callback) {
Loader.utility('Overlay', callback);
}
/**
* Loads required libraries for sim import
*/
function requireSimImport(callback) {
var libraries = ['Overlay', 'Import_sim_contacts'];
var pending = libraries.length;
libraries.forEach(function onPending(library) {
Loader.utility(library, next);
});
function next() {
if (!(--pending)) {
callback();
}
}
}
function resetWait(wakeLock) {
Contacts.hideOverlay();
if (wakeLock) {
wakeLock.unlock();
}
}
// Listens for any change in the ordering preferences
var onOrderingChange = function onOrderingChange(evt) {
newOrderByLastName = orderCheckBox.checked;
updateOrderingUI();
};
// Import contacts from SIM card and updates ui
var onSimImport = function onSimImport(iccId, done) {
var icc = IccHandler.getIccById(iccId);
if (icc === null) {
return;
}
var progress = Contacts.showOverlay('simContacts-reading',
'activityBar');
var wakeLock = navigator.requestWakeLock('cpu');
var cancelled = false, contactsRead = false;
var importer = new SimContactsImporter(icc);
utils.overlay.showMenu();
utils.overlay.oncancel = function oncancel() {
cancelled = true;
importer.finish();
if (contactsRead) {
// A message about canceling will be displayed while the current chunk
// is being cooked
progress.setClass('activityBar');
utils.overlay.hideMenu();
progress.setHeaderMsg('messageCanceling');
} else {
importer.onfinish(); // Early return while reading contacts
}
};
var totalContactsToImport;
var importedContacts = 0;
// Delay for showing feedback to the user after importing
var DELAY_FEEDBACK = 200;
importer.onread = function import_read(n) {
contactsRead = true;
totalContactsToImport = n;
if (totalContactsToImport > 0) {
progress.setClass('progressBar');
progress.setHeaderMsg('simContacts-importing');
progress.setTotal(totalContactsToImport);
}
};
importer.onfinish = function import_finish(numDupsMerged) {
window.setTimeout(function onfinish_import() {
resetWait(wakeLock);
if (importedContacts > 0) {
var source = 'sim-' + iccId;
utils.misc.setTimestamp(source, function() {
// Once the timestamp is saved, update the list
updateTimestamps();
checkNoContacts();
});
}
if (!cancelled) {
utils.status.show({
id: 'simContacts-imported3',
args: {
n: importedContacts
}
},
!numDupsMerged ? null : {
id: 'contactsMerged',
args: {
numDups: numDupsMerged
}
});
} else {
logImportUsage('sim');
}
typeof done === 'function' && done();
}, DELAY_FEEDBACK);
importer.onfinish = null;
};
importer.onimported = function imported_contact() {
importedContacts++;
if (!cancelled) {
progress.update();
}
};
importer.onerror = function import_error() {
var cancel = {
title: 'cancel',
callback: function() {
ConfirmDialog.hide();
}
};
var retry = {
title: 'retry',
isRecommend: true,
callback: function() {
ConfirmDialog.hide();
// And now the action is reproduced one more time
window.setTimeout(requireSimImport.bind(this,
onSimImport.bind(this, iccId)), 0);
}
};
ConfirmDialog.show(null, 'simContacts-error', cancel, retry);
resetWait(wakeLock);
};
importer.start();
};
var onSdImport = function onSdImport(cb) {
var cancelled = false;
var importer = null;
var progress = Contacts.showOverlay(
'memoryCardContacts-importing',
'activityBar',
'infiniteProgress'
);
utils.overlay.showMenu();
utils.overlay.oncancel = function() {
cancelled = true;
importer ? importer.finish() : Contacts.hideOverlay();
};
var wakeLock = navigator.requestWakeLock('cpu');
var importedContacts = 0;
// Delay for showing feedback to the user after importing
var DELAY_FEEDBACK = 200;
utils.sdcard.retrieveFiles([
'text/vcard',
'text/x-vcard',
'text/directory;profile=vCard',
'text/directory'
], ['vcf', 'vcard'], function(err, fileArray) {
if (err) {
return import_error(err, cb);
}
if (cancelled) {
return;
}
if (fileArray.length) {
var promises = [];
fileArray.forEach(file => {
promises.push(utils.sdcard.getTextFromFile(file, onContacts));
});
Promise.all(promises).then(results => {
var numDupsMerged = results.reduce((sum, current) => {
return sum + current;
});
window.setTimeout(() => {
utils.misc.setTimestamp('sd', () => {
// Once the timestamp is saved, update the list
updateTimestamps();
checkNoContacts();
resetWait(wakeLock);
if (!cancelled) {
var msg1 = {
id: 'memoryCardContacts-imported3',
args: {
n: importedContacts
}
};
var msg2 = !numDupsMerged ? null : {
id: 'contactsMerged',
args: {
numDups: numDupsMerged
}
};
utils.status.show(msg1, msg2);
logImportUsage('sd');
if (typeof cb === 'function') {
cb();
}
}
});
}, DELAY_FEEDBACK);
}).catch(error => {
import_error(error);
});
} else {
import_error('No contacts were found.', cb);
}
});
function onContacts(text) {
if (cancelled) {
return Promise.reject();
}
return new Promise((resolve, reject) => {
importer = new VCFReader(text);
if (!text || !importer) {
var error = 'No contacts were found';
import_error(error);
reject(error);
return;
}
importer.onimported = imported_contact;
importer.onerror = error => {
import_error(error);
reject(error);
};
importer.process((unused, numDupsMerged) => {
if (cancelled) {
reject('Cancelled');
Contacts.hideOverlay();
return;
}
resolve(numDupsMerged);
});
});
}
function imported_contact() {
importedContacts++;
progress.update();
}
function import_error(e, cb) {
var cancel = {
title: 'cancel',
callback: function() {
ConfirmDialog.hide();
}
};
var retry = {
title: 'retry',
isRecommend: true,
callback: function() {
ConfirmDialog.hide();
// And now the action is reproduced one more time
window.setTimeout(requireOverlay.bind(this, onSdImport), 0);
}
};
ConfirmDialog.show(null, 'memoryCardContacts-error', cancel,
retry);
resetWait(wakeLock);
if (typeof cb === 'function') {
cb();
}
}
};
// Dismiss settings window and execute operations if values got modified
var close = function close() {
if (newOrderByLastName != null &&
newOrderByLastName != orderByLastName && contacts.List) {
contacts.List.setOrderByLastName(newOrderByLastName);
utils.cookie.update({order: newOrderByLastName});
Cache.evict();
// Force the reset of the dom, we know that we changed the order
contacts.List.load(null, true);
orderByLastName = newOrderByLastName;
}
Contacts.goBack();
};
var checkOnline = function() {
// Perform pending automatic logouts
window.setTimeout(automaticLogout, 0);
// Other import services settings
updateOptionStatus(importGmailOption, !navigator.onLine, true);
updateOptionStatus(importLiveOption, !navigator.onLine, true);
};
var checkNoContacts = function checkNoContacts() {
var exportButton = exportContacts.firstElementChild;
ContactsService.isEmpty(function(error, isEmpty) {
if (error) {
window.console.warn(
'Error while trying to know the contact number',
error
);
// In case of error is safer to leave enabled
exportButton.removeAttribute('disabled');
bulkDeleteButton.removeAttribute('disabled');
return;
}
if (isEmpty) {
exportButton.setAttribute('disabled', 'disabled');
bulkDeleteButton.setAttribute('disabled', 'disabled');
setICEButton.setAttribute('disabled', 'disabled');
} else {
exportButton.removeAttribute('disabled');
bulkDeleteButton.removeAttribute('disabled');
setICEButton.removeAttribute('disabled');
}
});
};
function saveStatus(data) {
window.asyncStorage.setItem(PENDING_LOGOUT_KEY, data);
}
function automaticLogout() {
if (navigator.offLine === true) {
return;
}
LazyLoader.load(['/shared/js/contacts/utilities/http_rest.js'],
function() {
window.asyncStorage.getItem(PENDING_LOGOUT_KEY, function(data) {
if (!data) {
return;
}
var services = Object.keys(data);
var numResponses = 0;
services.forEach(function(service) {
var url = data[service];
var callbacks = {
success: function logout_success() {
numResponses++;
window.console.log('Successfully logged out: ', service);
delete data[service];
if (numResponses === services.length) {
saveStatus(data);
}
},
error: function logout_error() {
numResponses++;
if (numResponses === services.length) {
saveStatus(data);
}
},
timeout: function logout_timeout() {
numResponses++;
if (numResponses === services.length) {
saveStatus(data);
}
}
};
Rest.get(url, callbacks);
});
});
});
}
var updateTimestamps = function updateTimestamps() {
// TODO Add the same functionality to 'EXPORT' methods when ready.
var importSources =
document.querySelectorAll('#import-options li[data-source]');
Array.prototype.forEach.call(importSources, function(node) {
utils.misc.getTimestamp(node.dataset.source,
function(time) {
var spanID = 'notImported';
if (time) {
spanID = 'imported';
var timeElement = node.querySelector('p > time');
timeElement.setAttribute('datetime',
(new Date(time)).toLocaleString());
timeElement.textContent = utils.time.pretty(time);
}
node.querySelector('p > span').setAttribute('data-l10n-id', spanID);
});
});
};
var refresh = function refresh() {
getData();
checkOnline();
checkSIMCard();
utils.sdcard.getStatus(function statusUpdated() {
updateStorageOptions(utils.sdcard.checkStorageCard());
});
updateTimestamps();
checkNoContacts();
};
return {
'init': init,
'close': close,
'refresh': refresh,
'cardStateChanged': checkSIMCard,
'updateTimestamps': updateTimestamps,
'showICEScreen' : showICEScreen,
get navigation() { return navigationHandler; },
'importFromSDCard': onSdImport,
'importFromSIMCard': onSimImport
};
})();

View file

@ -0,0 +1,248 @@
/* exports WebrtcClient */
/* global MozActivity */
/* global Promise */
/*
* This class is in charge of adding, in a 'plug&play' way, the buttons
* related with the new feature for calling through WebRTC other phone or
* email, taking into account the Contact info.
*
* We will check with MozActivity if the app is installed or not, and if it
* is installed we will show the buttons in the Detail of the contact.
*/
(function(exports) {
'use strict';
var WEBRTC_CLIENTNAME = 'Firefox Hello';
var _webrtcClientIntegrationDOM, _detailListDOM;
var _cachedContact = null;
var _isInDetail = false;
var _isWebrtcClientInstalled;
var voiceCallButton, videoCallButton;
function _launchActivity(videoEnabled) {
var activity = new MozActivity({
name: 'webrtc-call',
data: {
video: videoEnabled,
type: 'webcontacts/contact',
contact: _cachedContact
}
});
activity.onerror = function() {
// TODO Add string if needed
};
}
function _checkIfInstalled(forceCheck) {
return new Promise(function(resolve, reject) {
// If the value was previously checked we dont need to recheck again.
if (_isWebrtcClientInstalled !== undefined && !forceCheck) {
_isWebrtcClientInstalled ? resolve() : reject();
return;
}
var activity =
new MozActivity(
{
name: 'webrtc-call',
data: {
type: 'webcontacts/contact',
video: false,
contact: null
},
// With this param we retrieve a list of apps
// which could handle this activity.
getFilterResults: true
}
);
activity.onsuccess = function(e) {
if (activity.result.length > 0) {
_isWebrtcClientInstalled = true;
resolve();
} else {
_isWebrtcClientInstalled = false;
reject();
}
};
activity.onerror = function(e) {
_isWebrtcClientInstalled = false;
reject();
};
});
}
function _createDOM() {
// Let's build the structure we are going to use
// First of all the element is going to be added to a list
// so we create the 'li' element
_webrtcClientIntegrationDOM = document.createElement('li');
_webrtcClientIntegrationDOM.id = 'webrtc-client-actions';
var title = document.createElement('h2');
// This has been hardcoded to prevent uplifting issues to v2.0
title.textContent = WEBRTC_CLIENTNAME;
var colsWrapper = document.createElement('div');
colsWrapper.className = 'fillflow-twocols';
voiceCallButton = document.createElement('button');
voiceCallButton.className = 'activity icon icon-webrtc-voice';
voiceCallButton.setAttribute('data-l10n-id', 'audio');
voiceCallButton.addEventListener(
'click',
function onVoiceCallRequest() {
_launchActivity(false);
}
);
videoCallButton = document.createElement('button');
videoCallButton.className = 'activity icon icon-webrtc-video';
videoCallButton.setAttribute('data-l10n-id', 'video');
videoCallButton.addEventListener(
'click',
function onVideoCallRequest() {
_launchActivity(true);
}
);
colsWrapper.appendChild(voiceCallButton);
colsWrapper.appendChild(videoCallButton);
// Append elements to the new section
_webrtcClientIntegrationDOM.appendChild(title);
_webrtcClientIntegrationDOM.appendChild(colsWrapper);
}
function _init() {
// Cache the parent element
_detailListDOM = document.getElementById('details-list');
// Create the DOM for the new element in the 'details-list'
_createDOM();
}
function _insert() {
// Get the section which is going to be shown after, we will use it
// as a reference
var reference;
// Is there any phone element?
var phoneNumbers = _detailListDOM.querySelectorAll('[data-phone]');
var phonesLength = phoneNumbers && phoneNumbers.length;
if (phonesLength) {
reference = phoneNumbers[phonesLength - 1].nextSibling;
}
else {
var emails = _detailListDOM.querySelectorAll('[data-mail]');
var emailsLength = emails && emails.length;
if (emailsLength) {
reference = emails[emailsLength - 1];
}
}
_detailListDOM.insertBefore(
_webrtcClientIntegrationDOM,
reference
);
}
function _remove() {
if (!!document.getElementById('webrtc-client-actions')) {
_detailListDOM.removeChild(_webrtcClientIntegrationDOM);
}
}
function _render() {
if (!_webrtcClientIntegrationDOM) {
_init();
}
_insert();
}
function _onVisibilityChange() {
if (!document.hidden) {
_checkIfInstalled(true).then(
_onWebrtcClientInstalled,
_onWebrtcClientUninstalled
);
}
}
function _startListeningAppChanges() {
document.addEventListener('visibilitychange', _onVisibilityChange);
}
function _stopListeningAppChanges() {
document.removeEventListener('visibilitychange', _onVisibilityChange);
}
function _webRtcClientAvailable(contact) {
var mailPresent = false;
var telPresent = false;
if (Array.isArray(contact.email)) {
if (contact.email.length > 0 && contact.email[0] &&
contact.email[0].value && contact.email[0].value.trim()) {
mailPresent = true;
}
}
if (Array.isArray(contact.tel)) {
if (contact.tel.length > 0 && contact.tel[0] &&
contact.tel[0].value && contact.tel[0].value.trim()) {
telPresent = true;
}
}
return telPresent || mailPresent;
}
function _onWebrtcClientInstalled() {
// If we are not in the detail of a contact, we
// dont need to render any button
if (!_webRtcClientAvailable(_cachedContact) || !_isInDetail) {
return;
}
// If is not appended yet, we added it
if (!document.getElementById('webrtc-client-actions')) {
_render();
}
}
function _onWebrtcClientUninstalled() {
_remove();
}
var WebrtcClient = {
start: function start(contact, forceCheck) {
if (!_isInDetail) {
// Specify that we are in the detail of a contact
_isInDetail = true;
// Listening for app changes
_startListeningAppChanges();
}
// Cache the contact for future events
_cachedContact = contact;
// If installed we need to render
_checkIfInstalled(forceCheck).then(
_onWebrtcClientInstalled,
_onWebrtcClientUninstalled
);
},
stop: function stop() {
// Clean the previous params
_cachedContact = null;
_isInDetail = false;
// Stop listening for app changes
_stopListeningAppChanges();
}
};
exports.WebrtcClient = WebrtcClient;
}(this));

View file

@ -0,0 +1,179 @@
(function(exports) {
/* global utils */
/* exported ContactsService*/
'use strict';
/*
* Currently we are going to expose all functionality related with
* mozContacts API through a common interface.
*
* In the near future, all these code will expose the same
* functionality, but we will use Threads.js instead.
*
*/
// All events based on mozConcacts will be handled by this var
var events = {};
// Execute all handlers based on an event type
function executeHandlers(e) {
var handlers = events[e.type];
if (!handlers) {
return;
}
for (var i = 0; i < handlers.length; i++) {
handlers[i](e);
}
}
function registerListeners(event) {
navigator.mozContacts.addEventListener(event, executeHandlers);
}
function unregisterListeners(event) {
navigator.mozContacts.removeEventListener(event, executeHandlers);
}
var ContactsService = {
addListener: function(event, handler) {
if (!events[event]) {
events[event] = [handler];
registerListeners(event);
} else {
events[event].push(handler);
}
},
removeListener: function(event, handler) {
var handlers = events[event];
if (!handlers || handlers.length === 0) {
return;
}
for (var i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
handlers.splice(i, 1);
break;
}
}
handlers = events[event];
if (handlers.length === 0) {
unregisterListeners(event);
delete events[event];
}
},
isEmpty: function(callback) {
var req = navigator.mozContacts.getCount();
req.onsuccess = function() {
callback(null, req.result === 0);
};
req.onerror = function() {
callback(new Error('Error while retrieving the number of Contacts'));
};
},
save: function(contact, callback) {
var req = navigator.mozContacts.save(contact);
req.onsuccess = function() {
callback();
};
req.onerror = function() {
callback(req.error);
};
},
remove: function(contact, callback) {
var request =
navigator.mozContacts.remove(utils.misc.toMozContact(contact));
request.onsuccess = function() {
callback();
};
request.onerror = function() {
callback(new Error('mozContact.remove was not'));
};
},
get: function getContactByID(contactID, successCb, errorCb) {
if (!contactID) {
successCb();
return;
}
var options = {
filterBy: ['id'],
filterOp: 'equals',
filterValue: contactID
};
var request = navigator.mozContacts.find(options);
request.onsuccess = function findCallback(e) {
var result = e.target.result[0];
successCb(result);
};
if (typeof errorCb === 'function') {
request.onerror = errorCb;
}
},
getAll: function(callback) {
if (typeof callback !== 'function') {
return;
}
var request = navigator.mozContacts.find({});
request.onsuccess = function onSuccess() {
callback(null, request.result);
};
request.onerror = function onError() {
callback(new Error('mozContacts.find for retrieving all dont work'));
};
},
getAllStreamed: function(sortBy, onContactCB, onErrorCB, onCompleteCB) {
// Ensure all callbacks are available
if (typeof onContactCB !== 'function') {
onContactCB = function() {};
}
if (typeof onErrorCB !== 'function') {
onErrorCB = function() {};
}
if (typeof onCompleteCB !== 'function') {
onCompleteCB = function() {};
}
// Execute the search
var options = {
sortBy: sortBy,
sortOrder: 'ascending'
};
var cursor = navigator.mozContacts.getAll(options);
cursor.onsuccess = function onsuccess(evt) {
var contact = evt.target.result;
if (contact) {
onContactCB(contact);
cursor.continue();
} else {
onCompleteCB();
}
};
cursor.onerror = onErrorCB;
},
getCount: function(callback) {
var req = navigator.mozContacts.getCount();
req.onsuccess = function() {
callback(req.result);
};
req.onerror = function() {
callback(new Error('Error while retrieving the number of Contacts'));
};
}
};
exports.ContactsService = ContactsService;
}(this));

View file

@ -0,0 +1,627 @@
/* ROOT */
/* Global */
html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-size: 62.5%;
overflow: hidden;
position: relative;
background-color: white;
-moz-user-select: none;
}
body {
color: #fff;
font-size: 1.6rem;
line-height: 2.2rem;
text-align: center;
vertical-align: middle;
}
/* Text conventions */
h1, h2, h3, h4, h5, h6 {
margin: 0;
padding: 0;
font-weight: normal;
}
ul, ol, dl, dt, dd {
padding: 0;
margin: 0;
list-style-type: none;
}
p, li {
word-wrap: break-word;
margin: 0;
padding: 0;
}
q {
quotes: none;
display: block;
}
pre, blockquote {
padding: 0;
margin: 0;
}
em {
font-style: italic;
}
address {
font-style: normal;
display: inline;
}
abbr, acronym {
cursor: default;
border: none;
}
strong {
font-weight: bold;
}
b {
font-weight: 600;
}
small {
font-size: 1.1rem;
line-height: 1rem;
color: #999;
}
/* Links */
a {
text-decoration: none;
outline: none;
color: #333;
}
a:hover {
text-decoration: none;
}
a img {
border: none;
}
a abbr {
cursor: pointer;
}
/* Icons */
span.icon {
font-style: normal;
}
span.icon + b, span.icon + span {
vertical-align: middle;
}
/* Tables */
table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
}
td {
vertical-align: top;
}
caption, th {
text-align: start;
}
caption {
text-indent: -9999rem;
width: 0;
height: 0;
overflow: hidden;
}
/* Forms */
form {
margin: 0;
}
input:focus, textarea:focus {
outline: none;
}
textarea {
resize: none;
overflow: auto;
height: 4.6rem;
}
[contenteditable] {
outline: none;
word-wrap: break-word;
}
/* Media */
img {
display: block;
padding: 0;
margin: 0;
width: 100%;
}
img:-moz-broken {
line-height: 1.1rem;
overflow: hidden;
}
/* HTML 5 */
article, aside, details, figcaption, figure, footer, header, hgroup,
menu, nav, section, video, audio, canvas, progress, meter, time {
display: block;
padding: 0;
margin: 0;
}
/* APP */
/* view */
.view {
position: absolute;
overflow: hidden;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translate(0, 0);
}
.view:not(.view-noscroll) {
overflow-x: hidden;
overflow-y: auto;
}
.view-right {
transform: translateX(100%);
}
.view-bottom {
transform: translateY(100%);
}
/* Rewritting from buttons.css */
section[role="region"] > gaia-header button,
section[role="region"] > gaia-header a {
white-space: normal;
}
/* Buttons inside list redefinition */
li button span {
font-size: 0;
}
li button span[data-l10n-id] {
font-size: 1.6rem;
}
/* Extending components */
li button.activity > b em {
color: #727272;
font-style: normal;
}
[data-type="list"] li p {
margin-inline-start: 1.5rem;
}
/* BB Lists extending */
#view-contacts-list [data-type="list"] {
/**
* BiDi note:
* the non-symmetrical padding accomodates the A-Z scrollbar,
* which is always on the right edge of the screen
*/
padding: 2.5rem 3.5rem 0 1.5rem; /* BiDi-proof */
}
[data-type="list"] gaia-header {
padding: 0;
padding-inline-start: 1.5rem;
position: sticky;
top: -1.5rem;
background: #FFF;
z-index: 2;
}
[data-type="list"].active li:after {
offset-inline-end: 0;
}
/* JS helper */
[data-type="list"] li p,
[data-type="list"] li aside {
pointer-events: none;
}
[data-type="list"] li aside.pack-end {
margin: 0;
margin-inline-end: 1rem;
}
/* Selection tag extended from shared/style/buttons.css */
.view-select-tag .selection-list li button {
background-color: #fff;
}
.view-select-tag .selection-list li button.icon-selected:before {
background: transparent url(gphx/bitmap/default/lists/tick.png) no-repeat center center / 2rem;
}
#custom-tag {
margin-bottom: 2rem;
}
/* Body */
.view-body {
position: relative;
height: calc(100% - 5rem);
}
#view-screenshot.contact-list > #view-screenshot-body {
position: relative;
height: 100%;
width: 100%;
background: -moz-element(#view-contacts-list);
}
.view-body-inner {
overflow-x: hidden;
overflow-y: auto;
position: absolute;
left: 0;
top: 0;
bottom: 0;
z-index: 5;
width: 100%;
box-sizing: border-box;
background: #fff;
color: #333;
text-align: start;
will-change: scroll-position;
}
/* Removes scroll from the view body inner */
.view-noscroll .view-body-inner {
width: calc(100% + 1.5rem);
}
/* STACK: inline block list items; */
.stack {
letter-spacing: -0.4rem;
text-align: center;
}
.stack > li {
display: inline-block;
vertical-align: middle;
letter-spacing: 0;
}
/* ICONS */
span.icon {
font-size: 0;
background: transparent no-repeat center center;
background-size: 100% auto;
display: inline-block;
}
/* GENERIC BUTTONS */
span.icon.icon-delete {
background-image: url(/contacts/style/gphx/bitmap/default/forms/delete.png);
background-size: 3rem;
width: 3rem;
}
span.icon.icon-addimage {
background-image: url(/contacts/style/gphx/bitmap/default/forms/add.png);
background-size: 3rem;
width: 7rem;
height: 3rem;
}
span.icon.icon-address {
background-image: url(/contacts/style/gphx/bitmap/default/buttons/location.png);
}
li button.icon-link:before {
background-image: url(/contacts/style/gphx/bitmap/default/buttons/link.png);
}
li button.icon-share:before {
background-image: url(/contacts/style/gphx/bitmap/default/buttons/share.png);
}
/* Extending icons */
li button.icon-fav:before {
background-image: url(/contacts/style/images/icon-fav-button-off.png);
offset-inline-end: 0.5rem;
}
li button.icon-fav.on:before {
background-image: url(/contacts/style/images/icon-fav-button-on.png);
}
li button.icon-fb-profile:before {
background-image: url(/contacts/style/gphx/bitmap/default/buttons/fb_profile.png);
}
li button.icon-add:before {
background-image: url(/contacts/style/images/Add_button.png);
offset-inline-start: 0.2rem
}
li button.icon-find-and-merge:before {
background-image: url(/contacts/style/images/findandmerge.png);
background-size: 3rem;
}
/* Block type (long text) */
.action-block {
height: auto;
min-height: 4rem;
word-wrap: normal;
}
.action-block b {
display: block;
overflow: hidden;
line-height: 1.4em;
}
.action-block span {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
/* FILL FLOW: general components for helping in defining basic flows in forms; */
.fill-flow {
padding-inline-end: 0;
}
/* ROW: horizontal separation for elements; */
.fillflow-row {
position: relative;
}
.fillflow-left {
margin-inline-end: 0.7rem;
}
.fillflow-twocols {
overflow: hidden;
}
.fillflow-twocols button {
width: calc(50% - 0.5rem);
}
html[dir="ltr"] .fillflow-twocols button { float: left; }
html[dir="rtl"] .fillflow-twocols button { float: right; }
html[dir="ltr"] .fillflow-twocols button:last-child { float: right; }
html[dir="rtl"] .fillflow-twocols button:last-child { float: left; }
/* ROW ACTION */
.fillflow-row-action {
position: absolute;
offset-inline-end: 0;
top: 0;
width: auto;
height: auto;
padding-top: 0;
}
.fillflow-row-action span.icon {
border: none;
margin: 0;
height: 3.8rem;
}
/* MEDIA: specific ratio used in forms; */
.fillflow-media {
margin: 0;
margin-inline-end: 1rem;
margin-bottom: 0.5rem;
width: 7rem;
height: 7rem;
position: relative;
z-index: 0;
border: 1px solid #c7c7c7;
background-color: #fff;
border-radius: 7rem;
box-sizing: border-box;
}
.fillflow-media:active {
color: #fff;
background: rgba(105,230,255,0.5);
}
.fillflow-media.with-photo:active {
background: none;
}
.fillflow-media p {
position: absolute;
margin: 0;
padding: 0;
offset-inline-start: -1.5rem;
top: 2rem;
text-align: center;
z-index: -1;
width: 10rem;
}
.fillflow-media p em {
display: block;
color: #858585;
font-size: 1.1rem;
font-weight: normal;
font-style: italic;
margin-top: 1.8rem;
overflow: hidden;
text-overflow: ellipsis;
}
.fillflow-media .edit-photo {
z-index: 1;
display: none;
pointer-events: none;
margin-top: 3.6rem;
}
.with-photo .edit-photo {
display: block;
}
.fillflow-media #photo-button {
width: 100%;
height: 100%;
background: none;
line-height: inherit;
padding: 0;
}
.fillflow-media img {
width: 100%;
height: 100%;
background: #fff no-repeat center center;
background-size: cover;
}
/* We don't need a delete button on the first section */
.fillflow-media .img-delete-button {
display: none;
}
#thumbnail-action {
margin: 0 1.5rem 0.5rem 1.5rem;
}
/* JUMPER */
nav[data-type="scrollbar"] {
z-index: 6;
}
nav[data-type="scrollbar"].selecting {
bottom: 4.2rem;
}
nav[data-type="scrollbar"] ol > li > * {
pointer-events: none;
}
#groups-container.selecting {
height: calc(100% - 7rem);
}
.contact-checkbox {
offset-inline-start: 0 !important;
display: none !important;
}
.contact-checkbox-selecting {
display: block !important;
}
/* FB contacts will be disabled when adding .disable-fb-items to contact list */
.disable-fb-items li[data-fb-uid] {
color: #333333;
opacity: 0.25;
pointer-events: none;
}
/* This makes the checkbox appear unchecked when using .disable-fb-items */
.disable-fb-items li[data-fb-uid] label span:after {
background-position: center top;
}
#groups-list.selecting .group-section li,
#search-list.selecting li {
padding-inline-start: 2rem;
}
form[role="dialog"].no-overlay {
background: none;
pointer-events: none;
position: fixed;
}
form[role="dialog"].no-overlay button {
pointer-events: auto;
}
#selectable-form {
background: none;
}
#groups-list[data-type="list"].active.selecting li:not(:active):after {
display:none !important;
}
#import-settings button {
margin: 1.5rem;
width: calc(100% - 3rem);
}
#contact-list-ice {
padding-inline-start: 1rem
}
#contact-list-ice li {
display: flex;
align-items: baseline;
}
#contact-list-ice span {
width: 3rem;
height: 3rem;
background: url('/contacts/style/images/icon_ice.png') no-repeat 0 0 / 3rem auto transparent;
}
#groups-list.selecting #contact-list-ice {
display:none;
}
#ice-list {
min-height: 100%;
background: #FFF;
text-align: start;
}
.ice-contact {
font-size: 1.6rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 24rem;
}
/* .ice-contact is a <bdi> */
html[dir="ltr"] .ice-contact { text-align: left; }
html[dir="rtl"] .ice-contact { text-align: right; }
#contacts-form-notes textarea {
height: 9rem;
}
#search-list li.contact-item {
-moz-user-select: none;
text-align: start;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path fill="#ACBEE4" d="M9 0v9h-9"/></svg>

After

Width:  |  Height:  |  Size: 121 B

View file

@ -0,0 +1,84 @@
#iframe_extensions {
position: fixed;
top: 100%;
left: 0;
z-index: 1000;
width: 100%;
height: 100%;
border: 0;
margin: 0;
padding: 0;
transition: transform .6s ease;
background: #f4f4f4;
}
#iframe_extensions.opening {
transform: translateY(-100%);
}
#iframe_extensions.hidden {
visibility: hidden;
}
/* FB info (non-editable) */
.view-edit-contact .facebook fieldset legend,
.view-edit-contact .facebook fieldset input[type="date"] {
border-color: #acbee4;
color: #8d9abb;
background-image: url(fb/dialog.svg);
}
.view-edit-contact .facebook input {
border-color: #acbee4;
color: #8d9abb;
}
.view-edit-contact .facebook span {
color: #8d9abb;
}
.view-edit-contact .facebook fieldset input[value=""] {
display: none;
}
.with-photo.facebook img {
pointer-events: auto;
opacity: 0.95;
}
.with-photo.facebook {
padding-inline-end: 0;
}
/* XXX BiDi warning */
html[dir="ltr"] .with-photo.facebook .edit-photo { right: 0; }
html[dir="rtl"] .with-photo.facebook .edit-photo { right: -1.5rem; }
#settingsFb li p {
margin-inline-start: 0;
}
#settings-article[data-state="fb-disabled"] #settingsFb,
#settings-article[data-state="fb-disabled"] #fb-header {
display: none;
}
#settingsFb[data-state="logged-out"] > #fb-update-option > #import-fb,
#settingsFb[data-state="logged-out"] > #fb-update-option > #renew-pwd-msg,
#settingsFb[data-state="logged-in"] > #fb-update-option > #renew-pwd-msg {
display: none;
}
#settingsFb[data-state="renew-pwd"] > #fb-update-option > #import-fb:before {
background-image: url(/contacts/style/images/error_icon.png);
}
#settingsFb[data-state="logged-in"] > #fb-update-option > #import-fb:before {
background-image: url(/contacts/style/images/sync_icon.png);
}
#no-connection {
white-space: normal;
margin-top: 1rem;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Some files were not shown because too many files have changed in this diff Show more