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.
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"thumbnail": {
|
||||
"format": "image/jpeg",
|
||||
"size": 65,
|
||||
"quality": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"defaultContactsOrder": "givenName",
|
||||
"facebookEnabled": true,
|
||||
"operationsTimeout": 25000,
|
||||
"logLevel": "DEBUG",
|
||||
"facebookSyncPeriod": 24,
|
||||
"testToken": "",
|
||||
"defaultImage": true
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<element name="view-screenshot" extends="section">
|
||||
<template>
|
||||
<article id="view-screenshot-body">
|
||||
</article>
|
||||
</template>
|
||||
</element>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<element name="status-message" extends="section">
|
||||
<template>
|
||||
<p></p>
|
||||
</template>
|
||||
</element>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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#&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>
|
||||
|
|
@ -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)});
|
||||
|
|
@ -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);
|
||||
|
|
@ -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,'&').replace(/</g,'<').replace(/>/g,'>');if(escapeQuotes){return escaped.replace(/"/g,'"').replace(/'/g,''');}
|
||||
return escaped;},unescapeHTML:function normalizer_unescapeHTML(text){return text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/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={'&':'&','<':'<','>':'>',};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);
|
||||
778
assets/webapps/communications.gaiamobile.org/contacts/index.html
Normal 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>
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
})();
|
||||
4
assets/webapps/communications.gaiamobile.org/contacts/js/bootstrap.js
vendored
Normal 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);
|
||||
|
|
@ -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;})();
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
};
|
||||
})();
|
||||
|
|
@ -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
|
||||
};
|
||||
})();
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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);});};})();}
|
||||
|
|
@ -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);}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
@ -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; }
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
|
@ -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));
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/* global navigationStack */
|
||||
/* exported MainNavigation */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
exports.MainNavigation = new navigationStack('view-contacts-list');
|
||||
}(window));
|
||||
|
|
@ -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));
|
||||
|
|
@ -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
|
||||
};
|
||||
})();
|
||||
|
|
@ -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 : '';
|
||||
};
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
@ -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));
|
||||
|
|
@ -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')}
|
||||
]
|
||||
};
|
||||
|
|
@ -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;
|
||||
})();
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
}();
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
};
|
||||
}());
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
|
@ -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;})();
|
||||
|
|
@ -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]');
|
||||
};
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
};
|
||||
})();
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
};
|
||||
})();
|
||||
|
|
@ -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
|
||||
};
|
||||
})();
|
||||
|
|
@ -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));
|
||||
|
|
@ -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));
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 |
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 344 B |
|
After Width: | Height: | Size: 364 B |
|
After Width: | Height: | Size: 416 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 255 B |
|
After Width: | Height: | Size: 237 B |
|
After Width: | Height: | Size: 342 B |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 463 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 338 B |
|
After Width: | Height: | Size: 363 B |
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 377 B |
|
After Width: | Height: | Size: 421 B |