Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,182 @@
<!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>
<!-- Hack for avoiding 'glitch' when loading the view -->
<style type="text/css">
html, body {
background: #00b7b7 !important;
}
</style>
<!-- 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="/shared/style/contacts.css" rel="stylesheet">
<link href="/shared/style/contacts/contacts_buttons.css" rel="stylesheet">
<link href="/contacts/style/match_service.css" rel="stylesheet">
<link href="/contacts/views/details/style/overwrite.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">
<!-- 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.views.details.details.{locale}.json">
<!-- <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/views/details/js/boot.js"></script> --><script src="./gaia_build_defer_details.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">
<meta content="2.5" name="appVersion"></head>
<body role="application" class="app-contacts skin-comms theme-communications hidden">
<section id="view-contact-details" role="region" class="view view-contact-profile view-noscroll current"><!--
<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>
<iframe src="/shared/pages/import/curtain.html" id="iframe_curtain"></iframe>
<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>
</body>
</html>

View file

@ -0,0 +1,437 @@
;;;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';window.addEventListener('DOMContentLoaded',function(){LazyLoader.load(['/shared/js/l10n.js']).then(function(){LazyLoader.load([document.getElementById('view-contact-details')]).then(function(){});});});window.onload=function(){var dependencies=['/contacts/services/contacts.js','/shared/js/contact_photo_helper.js','/shared/js/contacts/contacts_buttons.js','/shared/js/text_normalizer.js','/shared/js/contacts/utilities/dom.js','/shared/js/contacts/utilities/templates.js','/shared/js/contacts/import/utilities/misc.js','/contacts/js/match_service.js','/contacts/views/details/js/details_ui.js','/contacts/views/details/js/details_controller.js','/shared/pages/import/js/curtain.js'];LazyLoader.load(dependencies).then(function(){DetailsUI.init();DetailsController.init();window.addEventListener('renderdone',function fn(){window.removeEventListener('renderdone',fn);document.body.classList.remove('hidden');});navigator.mozSetMessageHandler('activity',activity=>{DetailsController.setActivity(activity);var id=activity.source.data.params.id;ContactsService.get(id,function onSuccess(savedContact){ContactsService.getCount(count=>{DetailsUI.render(savedContact,count,true);});},function onError(){console.error('Error retrieving contact');});});});};

View file

@ -0,0 +1 @@
;'use strict';window.addEventListener('DOMContentLoaded',function(){LazyLoader.load(['/shared/js/l10n.js']).then(function(){LazyLoader.load([document.getElementById('view-contact-details')]).then(function(){});});});window.onload=function(){var dependencies=['/contacts/services/contacts.js','/shared/js/contact_photo_helper.js','/shared/js/contacts/contacts_buttons.js','/shared/js/text_normalizer.js','/shared/js/contacts/utilities/dom.js','/shared/js/contacts/utilities/templates.js','/shared/js/contacts/import/utilities/misc.js','/contacts/js/match_service.js','/contacts/views/details/js/details_ui.js','/contacts/views/details/js/details_controller.js','/shared/pages/import/js/curtain.js'];LazyLoader.load(dependencies).then(function(){DetailsUI.init();DetailsController.init();window.addEventListener('renderdone',function fn(){window.removeEventListener('renderdone',fn);document.body.classList.remove('hidden');});navigator.mozSetMessageHandler('activity',activity=>{DetailsController.setActivity(activity);var id=activity.source.data.params.id;ContactsService.get(id,function onSuccess(savedContact){ContactsService.getCount(count=>{DetailsUI.render(savedContact,count,true);});},function onError(){console.error('Error retrieving contact');});});});};

View file

@ -0,0 +1,165 @@
'use strict';
/* global utils */
/* global MatchService */
/* global ContactsService */
/* global LazyLoader */
/* global ContactToVcardBlob */
/* global VcardFilename */
/* global MozActivity */
/* exported Details */
/*
* Once the deatils view is loaded, we will listen events dispatched
* from the UI. This events will come with the info needed in order
* to execute actions related with the UI (back, toggle favorite, share...).
*
* Controller will *not* contain any code related with the DOM/UI,
* and will rely on the info provided by the event.
*/
(function(exports) {
var _activity = null;
function setActivity(activity) {
_activity = activity;
}
function findDuplicates(evt) {
if (!evt.detail || !evt.detail.contactId) {
console.error('Missing parameters in CustomEvent');
return;
}
var contactId = evt.detail.contactId;
MatchService.match(contactId);
}
function toggleFavorite(evt){
if (!evt.detail || typeof evt.detail.contact === 'undefined' ||
typeof evt.detail.isFavorite === 'undefined') {
console.error('Missing parameters in CustomEvent');
return;
}
var contact = evt.detail.contact;
var isFavorite = evt.detail.isFavorite;
var favorite = !isFavorite;
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);
}
}
function onError(e) {
console.error('Error saving favorite');
// We must restore contact in order to update the UX accordingly
if (favorite) {
var pos = contact.category.indexOf('favorite');
if (pos > -1) {
contact.category.splice(pos, 1);
}
} else {
contact.category = contact.category || [];
contact.category.push('favorite');
}
dispatchEvent('toggleFavoriteDone', {contact: contact});
}
ContactsService.save(
utils.misc.toMozContact(contact),
function(e) {
if (typeof e !== 'undefined') {
onError(e);
return;
}
// TODO: Listening to oncontactchange event, we can save the evt
// and send it via sessionStorage to the main list in order to
// update it accordingly with the changes made by the user in this view.
ContactsService.get(contact.id, function(savedContact) {
dispatchEvent('toggleFavoriteDone', {contact: savedContact});
}, onError);
}
);
}
function init() {
window.addEventListener('backAction', handleBackAction);
window.addEventListener('editAction', handleEditAction);
window.addEventListener('toggleFavoriteAction', toggleFavorite);
window.addEventListener('shareAction', shareContact);
window.addEventListener('findDuplicatesAction', findDuplicates);
// TODO: Need to save the oncontactchange event in order to update
// the main list when the user go back from this view. It could be done
// by saving the event in sessionStorage
}
function shareContact(evt) {
const VCARD_DEPS = [
'/shared/js/text_normalizer.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js'
];
if (!evt.detail || typeof evt.detail.contact === 'undefined') {
console.error('Missing parameters in CustomEvent');
return;
}
var contact = evt.detail.contact;
LazyLoader.load(VCARD_DEPS,function vcardLoaded() {
ContactToVcardBlob([contact], function blobReady(vcardBlob) {
VcardFilename(contact).then(filename => {
/* jshint nonew: false */
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'} );
});
}
function handleBackAction(evt) {
if (_activity) {
_activity.postResult({});
} else {
window.history.back();
}
}
function handleEditAction(evt) {
// In the future the navigation will change the URL to navigate
// to #update view: Bug 1169579
}
function dispatchEvent(name, data) {
window.dispatchEvent(new CustomEvent(name, {detail: data}));
}
exports.DetailsController = {
'init': init,
'setActivity': setActivity
};
})(window);

View file

@ -0,0 +1,490 @@
/* global ContactsButtons, LazyLoader, utils, WebrtcClient */
/* global Normalizer, ContactPhotoHelper */
/* exported DetailsUI */
/*
* UI *must* work alone, without any 'Controller' tied. This will
* help us to have a well-separated model, where the UI will dispatch
* events that a Controller could potentially handle.
*
* UI will be in charge of rendering the info provided by 'Boot', which
* is the only part of the code which knows UI & Controller.
*
* Every action performed by the user in the UI will dispatch an event that
* should be handle by a controller
*/
(function(exports) {
'use strict';
const PHOTO_POS = 7;
const INIT_MARGIN = 8;
const MAX_POSITION = 150;
// Scale ratio for different devices
var SCALE_RATIO = window.devicePixelRatio || 1;
var DetailsUI = {
init: function() {
var self = this;
function initContainers() {
self.header = document.querySelector('#details-view-header');
self.contactDetails = document.querySelector('#contact-detail');
self.listContainer = document.querySelector('#details-list');
self.detailsName = document.querySelector('#contact-name-title');
self.detailsNameText =
document.querySelector('#contact-name-title bdi');
self.orgTitle = document.querySelector('#org-title');
self.datesTemplate = document.querySelector('#dates-template-\\#i\\#');
self.addressesTemplate =
document.querySelector('#address-details-template-\\#i\\#');
self.duplicateTemplate =
document.querySelector('#duplicate-contacts-template');
self.editContactButton = document.querySelector('#edit-contact-button');
self.cover = document.querySelector('#cover-img');
self.detailsInner = document.querySelector('#contact-detail-inner');
self.favoriteMessage = document.querySelector('#toggle-favorite');
self.notesTemplate =
document.querySelector('#note-details-template-\\#i\\#');
self.socialTemplate =
document.querySelector('#social-template-\\#i\\#');
}
function addListeners() {
self.initPullEffect(self.cover);
self.favoriteMessage.addEventListener('click',
self.toggleFavorite.bind(self));
self.header.addEventListener('action',
self.handleBackAction.bind(self));
self.editContactButton.addEventListener('click',
self.handleEditAction.bind(self));
ContactsButtons.init(self.listContainer, self.contactDetails, null);
window.addEventListener('toggleFavoriteDone',
self.toggleFavoriteHandler.bind(self));
}
initContainers();
addListeners();
},
handleBackAction: function(evt) {
this.dispatchEvent('backAction');
},
handleEditAction: function(evt) {
this.dispatchEvent('editAction');
},
initPullEffect: function(cover) {
var maxPosition = Math.round(MAX_POSITION * SCALE_RATIO);
var startPosition = 0;
var self = this;
function onTouchStart(e) {
if (self.contactDetails.classList.contains('no-photo')) {
return;
}
e.preventDefault();
startPosition = e.changedTouches[0].clientY;
self.contactDetails.classList.add('up');
cover.classList.add('up');
window.addEventListener('touchmove', onTouchMove, true);
window.addEventListener('touchend', onTouchEnd, true);
}
function onTouchEnd(e) {
e.preventDefault();
self.contactDetails.style.transform = null;
self.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(' + INIT_MARGIN + 'rem + ' + deltaY + 'px)';
self.contactDetails.style.transform = 'translateY(' + calc + ')';
self.contactDetails.classList.remove('up');
// Divide by 40 (4 times slower and in rems)
var coverPosition = (-PHOTO_POS + (deltaY / 40)) + 'rem';
cover.style.transform = 'translateY(' + coverPosition + ')';
cover.classList.remove('up');
}
cover.addEventListener('touchstart', onTouchStart, true);
},
render: function(currentContact, contactsCount, readOnly) {
if(this.isAFavoriteChange){
this.isAFavoriteChange = false;
return Promise.resolve(this.isAFavoriteChange);
}
this.dispatchEvent('renderinit');
this.contactData = currentContact;
this.contactsCount = contactsCount;
// Initially enabled and only disabled if necessary
this.editContactButton.removeAttribute('disabled');
this.editContactButton.classList.remove('hide');
this.header.setAttribute('action', 'back');
if (readOnly) {
this.editContactButton.classList.add('hide');
this.header.setAttribute('action', 'close');
}
this.doReloadContactDetails(this.contactData);
},
doReloadContactDetails: function(contact) {
this.setupDisplayName(contact);
this.contactDetails.classList.remove('no-photo');
this.contactDetails.classList.remove('up');
utils.dom.removeChildNodes(this.listContainer);
this.renderFavorite(contact);
this.renderOrg(contact);
ContactsButtons.renderPhones(contact);
ContactsButtons.renderEmails(contact);
this.renderWebrtcClient(contact);
this.renderAddresses(contact);
this.renderDates(contact);
this.renderNotes(contact);
this.renderShareButton(contact);
this.renderDuplicate(contact);
this.renderPhoto(contact);
this.dispatchEvent('renderdone');
},
setupDisplayName: function(contact) {
var name = this.getDisplayName(contact);
if (typeof name === 'string') {
this.detailsNameText.setAttribute('data-l10n-id', name);
} else if (name.raw) {
this.detailsNameText.textContent = name.raw;
} else if (name.id) {
navigator.mozL10n.setAttributes(this.detailsNameText,
name.id, name.args);
}
},
getDisplayName: function(contact) {
var l10nId = 'noName';
if (this.hasName(contact)) {
l10nId = {'raw': contact.name[0]};
} else if (this.hasContent(contact.tel)) {
l10nId = {'raw': contact.tel[0].value};
} else if (this.hasContent(contact.email)) {
l10nId = {'raw': contact.email[0].value};
}
return l10nId;
},
hasContent: function(field) {
return (Array.isArray(field) &&
field.length > 0 &&
field[0].value &&
field[0].value.trim());
},
hasName: function(contact) {
return (Array.isArray(contact.name) &&
contact.name[0] &&
contact.name[0].trim());
},
renderFavorite: function(contact) {
var favorite = this.isFavorite();
this.toggleFavoriteMessage(favorite);
this.header.classList.toggle('favorite', !!favorite);
},
toggleFavorite: function() {
var contact = this.contactData;
var favorite = !this.isFavorite();
this.toggleFavoriteMessage(favorite);
// Disabling button while saving the contact
this.favoriteMessage.style.pointerEvents = 'none';
this.dispatchEvent('toggleFavoriteAction',
{contact: contact, isFavorite: !favorite});
},
toggleFavoriteMessage: function(isFav) {
var cList = this.favoriteMessage.classList;
var l10nId = isFav ? 'removeFavorite' : 'addFavorite';
this.favoriteMessage.setAttribute('data-l10n-id', l10nId);
isFav ? cList.add('on') : cList.remove('on');
},
toggleFavoriteHandler: function(evt) {
var savedContact = evt.detail.contact;
this.favoriteMessage.style.pointerEvents = 'auto';
this.setContact(savedContact);
this.renderFavorite(savedContact);
this.isAFavoriteChange = true;
},
setContact: function(currentContact) {
this.contactData = currentContact;
},
renderOrg: function(contact) {
if (contact.org && contact.org.length > 0 && contact.org[0] !== '') {
this.orgTitle.textContent = contact.org[0];
this.orgTitle.classList.remove('hide');
} else {
this.orgTitle.classList.add('hide');
this.orgTitle.textContent = '';
}
},
renderWebrtcClient: function(contact) {
LazyLoader.load([
'/contacts/style/webrtc-client/webrtc_client.css',
'/contacts/js/webrtc-client/webrtc_client.js'
]).then(function() {
WebrtcClient.start(contact);
});
},
renderAddresses: function(contact) {
if (!contact.adr) {
return;
}
// Load what we need
LazyLoader.load('/contacts/js/utilities/mozContact.js', () => {
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 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_l10n_id': currentAddress.type,
i: i
};
var template = utils.templates.render(this.addressesTemplate,
addressField);
this.listContainer.appendChild(template);
}
});
},
renderDates: function(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(this.datesTemplate, {
i: ++rendered,
date: dateString,
'type_l10n_id': l10nIds[j]
});
this.listContainer.appendChild(element);
}
},
renderNotes: function(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(this.notesTemplate, noteField);
container.appendChild(template);
this.listContainer.appendChild(container);
}
},
renderShareButton: function(contact) {
var social = utils.templates.render(this.socialTemplate, {
i: contact.id
});
var shareButton = social.querySelector('#share_button');
shareButton.addEventListener('click', () => {
this.dispatchEvent('shareAction', {contact: this.contactData});
});
shareButton.classList.remove('hide');
this.listContainer.appendChild(social);
},
renderDuplicate: function(contact) {
var dupItem = utils.templates.render(this.duplicateTemplate, {});
var findMergeButton = dupItem.querySelector('#find-merge-button');
findMergeButton.disabled = true;
if (this.contactsCount > 1) {
// Only have this active if contact list has more than one entry
findMergeButton.disabled = false;
findMergeButton.addEventListener('click', () => {
this.dispatchEvent('findDuplicatesAction',
{contactId: this.contactData.id});
});
}
this.listContainer.appendChild(dupItem);
},
renderPhoto: function(contact) {
this.contactDetails.classList.remove('up');
var photo = ContactPhotoHelper.getFullResolution(contact);
if (photo) {
var currentHash = this.cover.dataset.imgHash;
if (!currentHash) {
utils.dom.updatePhoto(photo, this.cover);
this.updateHash(photo, this.cover);
}
else {
// Need to recalculate the hash and see whether the images changed
this.calculateHash(photo, newHash => {
if (currentHash !== newHash) {
utils.dom.updatePhoto(photo, this.cover);
this.cover.dataset.imgHash = newHash;
}
else {
// Only for testing purposes
this.cover.dataset.photoReady = 'true';
}
});
}
this.contactDetails.classList.add('up');
this.cover.classList.add('translated');
this.contactDetails.classList.add('translated');
var clientHeight = this.contactDetails.clientHeight -
(INIT_MARGIN * 10 * SCALE_RATIO);
if (this.detailsInner.offsetHeight < clientHeight) {
this.cover.style.overflow = 'hidden';
} else {
this.cover.style.overflow = 'auto';
}
} else {
this.resetPhoto();
}
},
updateHash: function(photo, cover) {
this.calculateHash(photo, function(hash) {
cover.dataset.imgHash = hash;
});
},
calculateHash: function(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);
},
resetPhoto: function() {
this.cover.classList.remove('translated');
this.contactDetails.classList.remove('translated');
this.cover.style.backgroundImage = '';
this.cover.style.overflow = 'auto';
this.contactDetails.style.transform = '';
this.contactDetails.classList.add('no-photo');
this.cover.dataset.imgHash = '';
},
isFavorite: function() {
return this.contactData != null && this.contactData.category != null &&
this.contactData.category.indexOf('favorite') != -1;
},
dispatchEvent: function(name, data) {
window.dispatchEvent(new CustomEvent(name, {detail: data}));
}
};
exports.DetailsUI = DetailsUI;
})(window);

View file

@ -0,0 +1,7 @@
body * {
transition: opacity 0.3s ease;
}
body.hidden * {
opacity: 0;
}

View file

@ -0,0 +1,314 @@
<!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>
<!-- Hack for avoiding 'glitch' when loading the view -->
<style type="text/css">
html, body {
background: #00b7b7 !important;
}
</style>
<!-- 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="/shared/style/contacts.css" rel="stylesheet">
<link href="/shared/style/contacts/contacts_buttons.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
<!-- 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> -->
<!-- Localization -->
<link rel="localization" href="/locales-obj/contacts.views.form.form.{locale}.json">
<!-- <script defer="" src="/shared/js/html_imports.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/views/form/js/boot.js"></script> --><script src="./gaia_build_defer_form.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">
<!-- Some CSS magic needed to work in a separate html -->
<link href="/contacts/views/form/style/overwrite.css" rel="stylesheet">
<!-- Load just the templates needed -->
<meta content="2.5" name="appVersion"></head>
<body role="application" class="app-contacts skin-comms theme-communications">
<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>
<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>
<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>
</body>
</html>

View file

@ -0,0 +1,387 @@
;;;;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 HtmlImports={populate:function(callback){var imports=document.querySelectorAll('link[rel="import"]');if(!imports.length){return;}
var pending=imports.length;Array.prototype.forEach.call(imports,function perImport(eachImport){this.getImportContent(eachImport.href,function gotContent(content){var elementTemplates={};var elementRoot=document.createElement('div');elementRoot.innerHTML=content;var elements=elementRoot.querySelectorAll('element');for(var i=0,iLen=elements.length;i<iLen;i++){var element=elements[i];var template=element.querySelector('template');elementTemplates[element.getAttribute('name')]=template.innerHTML;}
var replaceableElements=document.querySelectorAll('*[is]');Array.prototype.forEach.call(replaceableElements,function _each(el){if(elementTemplates[el.getAttribute('is')]){el.innerHTML=elementTemplates[el.getAttribute('is')];el.removeAttribute('is');}});if(!(--pending)){callback();}});},this);},getImportContent:function(path,callback){var parsedURL=new URL(path,location.href);if(parsedURL.origin!==location.origin){return;}
var xhr=new XMLHttpRequest();xhr.onload=function(o){callback(xhr.responseText);};xhr.open('GET',path,true);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';(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';window.addEventListener('DOMContentLoaded',function(){LazyLoader.load(['/shared/js/l10n.js'],function(){LazyLoader.load([document.getElementById('view-contact-form')],function(){});});});window.addEventListener('load',function(){var dependencies=['/contacts/js/navigation.js','/contacts/services/contacts.js','/shared/js/contact_photo_helper.js','/shared/js/contacts/utilities/templates.js','/contacts/js/contacts_tag.js','/contacts/js/tag_options.js','/shared/js/text_normalizer.js','/shared/js/contacts/import/utilities/status.js','/shared/js/contacts/utilities/dom.js','/shared/js/contacts/import/utilities/misc.js','/contacts/views/form/js/form_ui.js','/contacts/views/form/js/form_controller.js','/contacts/views/form/js/main_navigation.js'];LazyLoader.load(dependencies,function(){FormUI.init();FormController.init();navigator.mozSetMessageHandler('activity',function(activity){FormController.setActivity(activity);FormUI.render(activity.source.data.params);});});});

View file

@ -0,0 +1 @@
;'use strict';window.addEventListener('DOMContentLoaded',function(){LazyLoader.load(['/shared/js/l10n.js'],function(){LazyLoader.load([document.getElementById('view-contact-form')],function(){});});});window.addEventListener('load',function(){var dependencies=['/contacts/js/navigation.js','/contacts/services/contacts.js','/shared/js/contact_photo_helper.js','/shared/js/contacts/utilities/templates.js','/contacts/js/contacts_tag.js','/contacts/js/tag_options.js','/shared/js/text_normalizer.js','/shared/js/contacts/import/utilities/status.js','/shared/js/contacts/utilities/dom.js','/shared/js/contacts/import/utilities/misc.js','/contacts/views/form/js/form_ui.js','/contacts/views/form/js/form_controller.js','/contacts/views/form/js/main_navigation.js'];LazyLoader.load(dependencies,function(){FormUI.init();FormController.init();navigator.mozSetMessageHandler('activity',function(activity){FormController.setActivity(activity);FormUI.render(activity.source.data.params);});});});

View file

@ -0,0 +1,217 @@
/* global LazyLoader, MergeHelper, ContactsService, utils,
Matcher, MatchService */
/*
* Once critical path is loaded, we will listen events dispatched
* from the UI. This events will come with the info needed in order
* to execute actions related with the UI (close, save, update...).
*
* Controller will *not* contain any code related with the DOM/UI,
* and will rely on the info provided by the event.
*/
(function(exports) {
'use strict';
var _;
var _activity;
const CONTACTS_APP_ORIGIN = location.origin;
function close() {
if (_activity) {
_activity.postResult({});
} else {
window.history.back();
}
}
function hasName(contact) {
return (Array.isArray(contact.givenName) && contact.givenName[0] &&
contact.givenName[0].trim()) ||
(Array.isArray(contact.familyName) && contact.familyName[0] &&
contact.familyName[0].trim());
}
function doMerge(contact, list, cb) {
var callbacks = {
success: cb,
error: function(e) {
console.error('Failed merging duplicate contacts: ', e.name);
cb();
}
};
LazyLoader.load('/contacts/js/utilities/merge_helper.js', function() {
MergeHelper.merge(contact, list).then(callbacks.success, callbacks.error);
});
}
function getCompleteName(contact) {
var givenName = Array.isArray(contact.givenName) ?
contact.givenName[0] : '';
var familyName = Array.isArray(contact.familyName) ?
contact.familyName[0] : '';
var completeName = givenName && familyName ?
givenName + ' ' + familyName :
givenName || familyName;
return completeName;
}
// Fills the contact data to display if no givenName and familyName
function getDisplayName(contact) {
if (hasName(contact)) {
return { givenName: contact.givenName, familyName: contact.familyName };
}
// TODO Remove l10n deprecated methods:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1181586
var givenName = [];
if (Array.isArray(contact.name) && contact.name.length > 0) {
givenName.push(contact.name[0]);
} else if (contact.org && contact.org.length > 0) {
givenName.push(contact.org[0]);
} else if (contact.tel && contact.tel.length > 0) {
givenName.push(contact.tel[0].value);
} else if (contact.email && contact.email.length > 0) {
givenName.push(contact.email[0].value);
} else {
givenName.push(_('noName'));
}
return { givenName: givenName, modified: true };
}
function doMatch(contact, callbacks) {
LazyLoader.load(['/shared/js/text_normalizer.js',
'/shared/js/simple_phone_matcher.js',
'/shared/js/contacts/contacts_matcher.js'], function() {
Matcher.match(contact, 'active', callbacks);
});
}
var mergeHandler;
function cookMatchingCallbacks(contact) {
return {
onmatch: function(results) {
MatchService.showDuplicateContacts();
mergeHandler = function mergeHandler(e) {
if (e.origin !== CONTACTS_APP_ORIGIN) {
return;
}
var data = e.data;
switch (data.type) {
case 'duplicate_contacts_loaded':
// UI ready, passing duplicate contacts
var duplicateContacts = {};
Object.keys(results).forEach(function(id) {
duplicateContacts[id] = {
matchingContactId: id,
matchings: results[id].matchings
};
});
window.postMessage({
type: 'show_duplicate_contacts',
data: {
name: getCompleteName(getDisplayName(contact)),
duplicateContacts: duplicateContacts
}
}, CONTACTS_APP_ORIGIN);
break;
case 'merge_duplicate_contacts':
window.removeEventListener('message', mergeHandler);
// List of duplicate contacts to merge (identifiers)
var list = [];
var ids = data.data;
Object.keys(ids).forEach(id => {
list.push(results[id]);
});
doMerge(contact, list, function finished() {
window.postMessage({
type: 'duplicate_contacts_merged',
data: ids
}, CONTACTS_APP_ORIGIN);
close();
});
break;
case 'ready':
// TODO Use if needed
break;
case 'window_close':
// If user igonores duplicate contacts we save the contact
window.removeEventListener('message', mergeHandler);
doSave(contact, true);
break;
}
};
window.addEventListener('message', mergeHandler);
},
onmismatch: function() {
// Saving because there aren't duplicate contacts
doSave(contact);
}
};
}
function doSave(contact) {
ContactsService.addListener('contactchange',
function oncontactchange(event) {
sessionStorage.setItem('contactID', event.contactID);
sessionStorage.setItem('reason', event.reason);
ContactsService.removeListener('contactchange', oncontactchange);
close();
}
);
ContactsService.save(contact, function(error) {
// Use if needed.
});
}
exports.FormController = {
init: function() {
_ = navigator.mozL10n.get;
window.addEventListener('close-ui', function() {
close();
});
function saveContactHandler(event) {
var contact = utils.misc.toMozContact(event.detail);
LazyLoader.load(
[
'/contacts/style/match_service.css',
'/contacts/js/match_service.js'
],
function() {
var callbacks = cookMatchingCallbacks(contact);
doMatch(contact, callbacks);
}
);
}
window.addEventListener(
'save-contact',
saveContactHandler
);
},
setActivity: function(activity) {
_activity = activity;
}
};
}(window));

View file

@ -0,0 +1,803 @@
/* global ContactPhotoHelper */
/* global ContactsTag */
/* global LazyLoader */
/* global Normalizer */
/* global utils */
/* global TAG_OPTIONS */
/* global ActionMenu */
/* global TagSelector */
/* global PhotoPicker */
/*
* UI *must* work alone, without any 'Controller' tied. This will
* help us to have a well-separated model, where the UI will dispatch
* events that a Controller could potentially handle.
*
* UI will be in charge of rendering the info provided by 'Boot', which
* is the only part of the code which knows UI & Controller.
*/
(function(exports) {
'use strict';
var counters = {
'tel': 0,
'email': 0,
'adr': 0,
'date': 0,
'note': 0
};
// Cached elements that we will keep in memory
var contactForm,
deleteContactButton,
addNewDateButton,
thumb,
thumbAction,
saveButton,
formHeader,
formTitle,
givenName,
company,
familyName,
formView;
var _, currentPhoto;
var configs;
function initConfigs() {
// Keep in memory the template configuration
configs = {
'tel': {
template: document.getElementById('add-phone-#i#'),
tags: TAG_OPTIONS['phone-type'],
fields: ['value', 'type', 'carrier'],
container: document.getElementById('contacts-form-phones')
},
'email': {
template: document.getElementById('add-email-#i#'),
tags: TAG_OPTIONS['email-type'],
fields: ['value', 'type'],
container: document.getElementById('contacts-form-emails')
},
'adr': {
template: document.getElementById('add-address-#i#'),
tags: TAG_OPTIONS['address-type'],
fields: [
'type',
'streetAddress',
'postalCode',
'locality',
'countryName'
],
container: document.getElementById('contacts-form-addresses')
},
'date': {
template: document.getElementById('add-date-#i#'),
tags: TAG_OPTIONS['date-type'],
fields: ['value', 'type'],
container: document.getElementById('contacts-form-dates')
},
'note': {
template: document.getElementById('add-note-#i#'),
tags: TAG_OPTIONS['address-type'],
fields: ['note'],
container: document.getElementById('contacts-form-notes')
}
};
}
// Let's get the right event for the current device
var touchstart = 'ontouchstart' in window ? 'touchstart' : 'mousedown';
const INVALID_CLASS = 'invalid';
const DELETE_BUTTON_CLASS = 'img-delete-button';
function initContainers() {
deleteContactButton = document.querySelector('#delete-contact');
thumb = document.querySelector('#thumbnail-photo');
thumbAction = document.querySelector('#thumbnail-action');
saveButton = document.querySelector('#save-button');
addNewDateButton = document.querySelector('#add-new-date');
contactForm = document.getElementById('contact-form');
formHeader = document.querySelector('#contact-form-header');
formTitle = document.getElementById('contact-form-title');
givenName = document.getElementById('givenName');
company = document.getElementById('org');
familyName = document.getElementById('familyName');
formView = document.getElementById('view-contact-form');
}
function closeHandler() {
window.dispatchEvent(new Event('close-ui'));
}
function addFieldHandler(e) {
e.preventDefault();
e.stopPropagation();
if (e.target.dataset && e.target.dataset.action === 'delete') {
return;
}
if (e.target.dataset && e.target.dataset.fieldType) {
var type = e.target.dataset.fieldType;
insertField(
type,
null,
true
);
}
}
function saveHandler() {
var contact = {};
var inputs = { givenName, familyName };
for (var field in inputs) {
var value = inputs[field].value;
if (value && value.length > 0) {
contact[field] = [value];
} else {
contact[field] = null;
}
}
if (company.value && company.value.length > 0) {
contact.org = [company.value];
}
contact.email = getEmails();
contact.tel = getPhones();
contact.adr = getAddresses();
contact.note = getNotes();
var dates = getDates();
contact.bday = dates.bday;
contact.anniversary = dates.anniversary;
contact.name = getName(contact);
if (currentPhoto) {
contact.photo = [currentPhoto];
}
var customEvent = new CustomEvent('save-contact', {'detail': contact});
window.dispatchEvent(customEvent);
}
function addListeners() {
thumbAction.querySelector('#photo-button').onclick = photoAction;
document.addEventListener('input', function input(event) {
checkDisableButton();
});
contactForm.addEventListener(touchstart, function click(event) {
// Filter any other 'hit' in the DOM
var target = event.target;
if (target.tagName !== 'BUTTON') {
return;
}
var action = target.dataset.action || target.getAttribute('type');
switch(action) {
case 'reset':
event.preventDefault();
var input = target.previousElementSibling;
if (input.getAttribute('name').startsWith('tel') &&
input.dataset.field === 'value') {
var telId = input.id;
var telIndex = telId.substring(telId.indexOf('_') + 1);
var carrierField =
document.getElementById('carrier' + '_' + telIndex);
carrierField.classList.add(INVALID_CLASS);
cachedInputs = null;
}
input.value = '';
checkDisableButton();
break;
case 'delete':
event.preventDefault();
event.stopPropagation();
var type = target.dataset.type;
var selector = target.dataset.selector;
var container = configs[type].container;
var elem = document.getElementById(selector);
if (type !== 'photo') {
// Remove field
elem.parentNode.removeChild(elem);
// Show empty if no more fields of the same type are available
if (container.querySelectorAll('[data-field]').length === 0) {
container.classList.add('empty');
}
}
counters[type]--;
// In this version only two dates are allowed
if (type === 'date') {
checkAddDateButton();
}
cachedInputs = null;
// textFieldsCache.clear();
checkDisableButton();
break;
}
});
thumbAction.addEventListener(touchstart, function click(event) {
// Removing current photo
if (event.target.tagName === 'BUTTON') {
saveButton.removeAttribute('disabled');
}
});
// 'ValueModified' comes from the 'TagSelector', and will return a value
// from a set of Tags.
formView.addEventListener('ValueModified', function onValueModified(event) {
if (!event.detail) {
return;
}
if (!emptyForm() && event.detail.prevValue !== event.detail.newValue) {
saveButton.removeAttribute('disabled');
}
});
formHeader.addEventListener('action', closeHandler);
saveButton.addEventListener('click', saveHandler);
contactForm.addEventListener('click', addFieldHandler);
}
function init() {
// Cache l10n functionality
_ = navigator.mozL10n.get;
// Cache all DOM elements and reuse them
initContainers();
// Cache configuration for templates
initConfigs();
// Add listeners to DOM elements
addListeners();
}
// Renders the birthday as per the locale
function renderDate(date, bdayInputText) {
if (!date) {
return;
}
bdayInputText.textContent = utils.misc.formatDate(date);
bdayInputText.classList.remove('placeholder');
}
function onInputDate(bdayInputText, e) {
renderDate(e.target.valueAsDate, bdayInputText);
}
function extractValue(value) {
if (value) {
if (Array.isArray(value) && value.length > 0 && value[0]) {
return value[0].trim();
} else if (!Array.isArray(value)) {
return value.trim();
}
}
return '';
// The last statement allows to return the value in case we are getting
// just params from an activity and not a mozContact.
}
function fillDates(contact) {
if (contact.bday) {
contact.date = [];
contact.date.push({
type: 'birthday',
value: contact.bday
});
}
if (contact.anniversary) {
contact.date = contact.date || [];
contact.date.push({
type: 'anniversary',
value: contact.anniversary
});
}
}
function renderPhoto(contact) {
if (contact.photo && contact.photo.length > 0) {
currentPhoto = ContactPhotoHelper.getFullResolution(contact);
appendRemovePhotoButton();
}
utils.dom.updatePhoto(currentPhoto, thumb);
}
function render(params) {
formView.classList.remove('skin-organic');
saveButton.setAttribute('disabled', 'disabled');
saveButton.setAttribute('data-l10n-id', 'done');
deleteContactButton.parentNode.classList.add('hide');
formTitle.setAttribute('data-l10n-id', 'addContact');
params = params || {};
givenName.value = extractValue(params.givenName);
familyName.value = extractValue(params.lastName || params.familyName);
company.value = extractValue(params.company || params.org);
renderPhoto(params);
fillDates(params);
['tel', 'email', 'adr', 'date', 'note'].forEach(function(field) {
params[field] && renderTemplate(field, params[field]);
});
checkDisableButton();
}
/**
* Render Template
*
* @param {string} type Type of template, eg. 'tel'
* @param {object[]} toRender
*/
function renderTemplate(type, toRender) {
if (!Array.isArray(toRender)) {
toRender = [{value: toRender}];
}
for (var i = 0; i < toRender.length; i++) {
insertField(type, toRender[i] || {});
}
}
function checkAddDateButton() {
addNewDateButton.disabled = (counters.date >= 2);
}
function checkCarrierTel(carrierInput, event) {
var telInput = event.target;
var value = telInput.value;
if (!value || !value.trim()) {
// deletedTelNumber = true;
// If it was not previously filled then it will be disabled
if (!telInput.dataset.wasFilled) {
carrierInput.setAttribute('disabled', 'disabled');
}
else {
// Otherwise marked as invalid in order not to submit it
carrierInput.classList.add(INVALID_CLASS);
cachedInputs = null;
// textFieldsCache.clear();
}
}
else {
// Marked as filled
telInput.dataset.wasFilled = true;
// Enabling and marking as valid
carrierInput.removeAttribute('disabled');
carrierInput.classList.remove(INVALID_CLASS);
}
}
/**
* We cannot relay on the counter, but in the next id after the
* last field.
* See bug 1113134 for related explanation.
*/
function getNextTemplateId(container) {
var nodes = container.childNodes;
if (!nodes || nodes.length === 0) {
return 0;
}
var lastNode = nodes[nodes.length - 1];
var value = lastNode.dataset.index;
return value ? parseInt(value) + 1 : 0;
}
function isBuiltInType(type, tagList) {
return tagList.some(function(tag) {
return tag.type === type;
});
}
function insertField(type, object, isUserInteraction) {
if (!type || !configs[type]) {
console.error('Inserting field with unknown type');
return;
}
var obj = object || {};
var config = configs[type];
var template = config.template;
var tags = ContactsTag.filterTags(type, null, config.tags);
var container = config.container;
var default_type = tags[0] && tags[0].type || '';
var currField = {};
config.fields.forEach(function(currentElem) {
var def = (currentElem === 'type') ? default_type : '';
var defObj = (typeof(obj) === 'string') ? obj : obj[currentElem];
var value = '';
var isDate = (defObj && typeof defObj.getMonth === 'function');
currField[currentElem] = (defObj && typeof(defObj) === 'object' &&
!isDate ? defObj.toString() : defObj);
value = currField[currentElem] || def;
if (currentElem === 'type') {
currField.type_value = value;
// Do localization for built-in types
if (isBuiltInType(value, tags)) {
currField.type_l10n_id = value;
value = _(value) || value;
}
}
if (!isDate) {
currField[currentElem] = Normalizer.escapeHTML(value, true);
}
});
currField.i = getNextTemplateId(container);
var rendered = utils.templates.render(template, currField);
// Controlling that if no tel phone is present carrier field is disabled
if (type === 'tel') {
var carrierInput = rendered.querySelector('input[data-field="carrier"]');
var telInput = rendered.querySelector('input[data-field="value"]');
var cb = checkCarrierTel.bind(null, carrierInput);
telInput.addEventListener('input', cb, true);
checkCarrierTel(carrierInput, {target: telInput});
}
// Adding listener to properly render dates
if (type === 'date') {
var dateInput = rendered.querySelector('input[type="date"]');
// Setting the max value as today's date
var currentDate = new Date();
dateInput.setAttribute('max', currentDate.getFullYear() + '-' +
(currentDate.getMonth() + 1) + '-' +
currentDate.getDate());
var dateInputText = dateInput.previousElementSibling;
if (currField.value) {
dateInput.valueAsDate = currField.value;
renderDate(currField.value, dateInputText);
}
else {
dateInputText.setAttribute('data-l10n-id', 'date-span-placeholder');
}
dateInput.addEventListener(
'input',
onInputDate.bind(null, dateInputText));
}
var removeEl = createDeleteButton(rendered.id, type);
rendered.insertBefore(removeEl, rendered.firstChild);
// Add event listeners
var boxTitle = rendered.querySelector('legend.action');
if (boxTitle) {
boxTitle.addEventListener('click', selectTag);
}
// This will happen when the fields are added by the user on demand
if (isUserInteraction) {
rendered.classList.add('inserted');
window.setTimeout(() => rendered.classList.add('displayed'));
}
container.classList.remove('empty');
container.appendChild(rendered);
counters[type]++;
cachedInputs = null;
// Finally we need to check the status of the add date button
if (type === 'date') {
checkAddDateButton();
}
}
/*
* Method for selecting a tag (i.e. "phone type"). This needs to handle
* navigation among panels, that's why we need a 'MainNavigation' object
*/
function selectTag(evt) {
evt.preventDefault();
var target = evt.currentTarget;
LazyLoader.load('/contacts/js/utilities/tagSelector.js', function() {
TagSelector.show(target.children[0]);
});
return false;
}
function getPhones(contact) {
var phones = [];
var selector = '#view-contact-form form div.phone-template';
var phonesDOM = document.querySelectorAll(selector);
for (var i = 0; i < phonesDOM.length; i++) {
var currentPhone = phonesDOM[i];
var index = currentPhone.dataset.index;
var numberField = document.getElementById('number_' + index);
var value = numberField.value;
if (!value) {
continue;
}
var type =
[document.getElementById('tel_type_' + index).dataset.value || ''];
var carrierSelector = 'carrier_' + index;
var carrier = document.getElementById(carrierSelector).value || '';
phones.push({ value, type, carrier });
}
return phones;
}
function getEmails() {
var emails = [];
var selector = '#view-contact-form form div.email-template';
var emailsDOM = document.querySelectorAll(selector);
for (var i = 0; i < emailsDOM.length; i++) {
var currentEmail = emailsDOM[i];
var index = currentEmail.dataset.index;
var emailField = document.getElementById('email_' + index);
var value = emailField.value;
value = value && value.trim();
if (!value) {
continue;
}
var type = [
document.getElementById('email_type_' + index).dataset.value || ''];
emails.push({ value, type });
}
return emails;
}
function getName(contact) {
var givenName = '', familyName = '';
if (contact.givenName && Array.isArray(contact.givenName)) {
givenName = contact.givenName[0].trim();
}
if (contact.familyName && Array.isArray(contact.familyName)) {
familyName = contact.familyName[0].trim();
}
var completeName = (givenName + ' ' + familyName).trim();
return completeName ? [completeName] : [];
}
function getDates(contact) {
var dates = {};
var selector = '#view-contact-form form div.date-template';
var datesDOM = document.querySelectorAll(selector);
var bdayVal = null, anniversaryVal = null;
for (var i = 0; i < datesDOM.length; i++) {
var currentDate = datesDOM[i];
var arrayIndex = currentDate.dataset.index;
var dateField = document.getElementById('date_' + arrayIndex);
var dateValue = dateField.valueAsDate;
selector = 'date_type_' + arrayIndex;
var type = document.getElementById(selector).dataset.value || '';
if (!dateValue || !type) {
continue;
}
// Date value is referred to current TZ but it is not needed to normalize
// as that will be done only when the date is presented to the user
// by calculating the corresponding offset
switch (type) {
case 'birthday':
bdayVal = dateValue;
break;
case 'anniversary':
anniversaryVal = dateValue;
break;
}
}
dates.bday = bdayVal;
dates.anniversary = anniversaryVal;
return dates;
}
function getAddresses(contact) {
var addresses = [];
var selector =
'#view-contact-form form div.address-template';
var addressesDOM = document.querySelectorAll(selector);
for (var i = 0; i < addressesDOM.length; i++) {
var currentAddress = addressesDOM[i];
var arrayIndex = currentAddress.dataset.index;
var addressField = document.getElementById('streetAddress_' + arrayIndex);
var addressValue = addressField.value || '';
addressValue = addressValue.trim();
selector = 'address_type_' + arrayIndex;
var typeField = document.getElementById(selector).dataset.value || '';
typeField = typeField.trim();
selector = 'locality_' + arrayIndex;
var locality = document.getElementById(selector).value || '';
locality = locality.trim();
selector = 'postalCode_' + arrayIndex;
var postalCode = document.getElementById(selector).value || '';
postalCode = postalCode.trim();
selector = 'countryName_' + arrayIndex;
var countryName = document.getElementById(selector).value || '';
countryName = countryName.trim();
// Sanity check for pameters, check all params but the typeField
if (addressValue === '' && locality === '' &&
postalCode === '' && countryName === '') {
continue;
}
addresses.push({
streetAddress: addressValue,
postalCode: postalCode,
locality: locality,
countryName: countryName,
type: [typeField]
});
}
return addresses;
}
function getNotes(contact) {
var notes = [];
var selector = '#view-contact-form form div.note-template';
var notesDOM = document.querySelectorAll(selector);
for (var i = 0; i < notesDOM.length; i++) {
var currentNote = notesDOM[i];
var arrayIndex = currentNote.dataset.index;
var noteField = document.getElementById('note_' + arrayIndex);
var noteValue = noteField.value;
noteValue = noteValue && noteValue.trim();
if (!noteValue) {
continue;
}
notes.push(noteValue);
}
return notes;
}
function checkDisableButton() {
var saveButton = document.getElementById('save-button');
if (emptyForm()) {
saveButton.setAttribute('disabled', 'disabled');
} else {
saveButton.removeAttribute('disabled');
}
}
var cachedInputs = null;
function emptyForm() {
if (!cachedInputs) {
cachedInputs =
contactForm.querySelectorAll('input[data-field]:not(.invalid)');
}
for (var i = cachedInputs.length - 1; i >= 0; i--) {
if ((cachedInputs[i].value && cachedInputs[i].value.trim()) ||
(cachedInputs[i].valueAsDate)) {
return false;
}
}
return true;
}
function createDeleteButton(selector, type) {
var delButton = document.createElement('button');
delButton.className = DELETE_BUTTON_CLASS;
delButton.setAttribute('data-l10n-id', 'removeField');
delButton.dataset.type = type;
delButton.dataset.action = 'delete';
delButton.dataset.selector = selector;
return delButton;
}
function appendRemovePhotoButton() {
var button = thumbAction.querySelector('button.' + DELETE_BUTTON_CLASS);
if (!button) {
button = createDeleteButton(thumbAction.id, 'photo');
thumbAction.appendChild(button);
}
else {
// Ensure it is visible
button.classList.remove('hide');
}
thumbAction.classList.add('with-photo');
}
function appendPhoto(resized) {
appendRemovePhotoButton();
if (!emptyForm()) {
saveButton.removeAttribute('disabled');
}
utils.dom.updatePhoto(resized, thumb);
currentPhoto = resized;
}
function photoAction() {
if (!!currentPhoto) {
removeOrUpdatePhoto();
} else {
LazyLoader.load(
[
'/contacts/views/form/js/photo_picker.js'
],
function() {
PhotoPicker.pick(appendPhoto);
}
);
}
}
function removeOrUpdatePhoto() {
LazyLoader.load(
[
'/contacts/views/form/js/photo_picker.js',
'/contacts/js/action_menu.js'
],
function() {
var prompt = new ActionMenu('photo-options');
prompt.addToList({id: 'remove-photo'}, removePhoto);
prompt.addToList(
{id: 'change-photo'},
PhotoPicker.pick.bind(null, appendPhoto)
);
prompt.show();
}
);
}
function removePhoto() {
currentPhoto = null;
thumbAction.classList.remove('with-photo');
utils.dom.updatePhoto(null, thumb);
if (!emptyForm()) {
saveButton.removeAttribute('disabled');
}
}
window.FormUI = {
'init': init,
'render': render
};
}(window));

View file

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

View file

@ -0,0 +1,89 @@
/* global MozActivity */
(function(exports) {
'use strict';
// The size we want our contact photos to be
const PHOTO_WIDTH = 320;
const PHOTO_HEIGHT = 320;
// bug 1038414: ask for an image about 2MP before
// doing the crop to save memory in both apps
const MAX_PHOTO_SIZE = 200000;
function pickImage(callback) {
callback = callback || function foo() {};
var activity = new MozActivity({
name: 'pick',
data: {
type: 'image/jpeg',
maxFileSizeBytes: MAX_PHOTO_SIZE
}
});
activity.onsuccess = function() {
// XXX
// this.result.blob is valid now, but it won't stay valid
// (see https://bugzilla.mozilla.org/show_bug.cgi?id=806503)
// And it might not be the size we want, anyway, so we make
// our own copy that is at the right size.
resizeBlob(
this.result.blob,
PHOTO_WIDTH,
PHOTO_HEIGHT,
callback
);
};
activity.onerror = function() {
window.console.error('Error in the activity', activity.error);
};
}
function resizeBlob(blob, target_width, target_height, callback) {
var img = document.createElement('img');
var url = URL.createObjectURL(blob);
img.src = url;
function cleanupImg() {
img.src = '';
URL.revokeObjectURL(url);
}
img.onerror = cleanupImg;
img.onload = function() {
var image_width = img.width;
var image_height = img.height;
var scalex = image_width / target_width;
var scaley = image_height / target_height;
var scale = Math.min(scalex, scaley);
var w = target_width * scale;
var h = target_height * scale;
var x = (image_width - w) / 2;
var y = (image_height - h) / 2;
var canvas = document.createElement('canvas');
canvas.width = target_width;
canvas.height = target_height;
var context = canvas.getContext('2d', { willReadFrequently: true });
context.drawImage(img, x, y, w, h, 0, 0, target_width, target_height);
cleanupImg();
canvas.toBlob(function(resized) {
context = null;
canvas.width = canvas.height = 0;
canvas = null;
callback(resized);
} , 'image/jpeg');
};
}
exports.PhotoPicker = {
pick: pickImage
};
}(window));

View file

@ -0,0 +1,22 @@
/*
* Throbber is just a loading bar at the top of the UI for
* showing the progress of the action requested by the user.
*
* This is needed for low-end devices, where 'saving' or
* *updating* a contact could take some seconds.
*/
(function(exports) {
'use strict';
var throbberDOM = document.getElementById('throbber');
exports.Throbber = {
show: function() {
throbberDOM.classList.remove('hide');
},
hide: function() {
throbberDOM.classList.add('hide');
}
};
}(window));

View file

@ -0,0 +1,4 @@
#view-contact-form {
visibility: visible;
transform: translateY(0);
}

View file

@ -0,0 +1 @@
;;window.COMPONENTS_BASE_URL='/shared/elements/';

View file

@ -0,0 +1,277 @@
;;(function(window,undefined){'use strict';function L10nError(message,id,loc){this.name='L10nError';this.message=message;this.id=id;this.loc=loc;}
L10nError.prototype=Object.create(Error.prototype);L10nError.prototype.constructor=L10nError;var io={_load:function(type,url,callback,sync){var xhr=new XMLHttpRequest();var needParse;if(xhr.overrideMimeType){xhr.overrideMimeType(type);}
xhr.open('GET',url,!sync);if(type==='application/json'){if(sync){needParse=true;}else{xhr.responseType='json';}}
xhr.addEventListener('load',function io_onload(e){if(e.target.status===200||e.target.status===0){var res=e.target.response||e.target.responseText;callback(null,needParse?JSON.parse(res):res);}else{callback(new L10nError('Not found: '+url));}});xhr.addEventListener('error',callback);xhr.addEventListener('timeout',callback);try{xhr.send(null);}catch(e){if(e.name==='NS_ERROR_FILE_NOT_FOUND'){callback(new L10nError('Not found: '+url));}else{throw e;}}},load:function(url,callback,sync){return io._load('text/plain',url,callback,sync);},loadJSON:function(url,callback,sync){return io._load('application/json',url,callback,sync);}};function EventEmitter(){}
EventEmitter.prototype.emit=function ee_emit(){if(!this._listeners){return;}
var args=Array.prototype.slice.call(arguments);var type=args.shift();if(!this._listeners[type]){return;}
var typeListeners=this._listeners[type].slice();for(var i=0;i<typeListeners.length;i++){typeListeners[i].apply(this,args);}};EventEmitter.prototype.addEventListener=function ee_add(type,listener){if(!this._listeners){this._listeners={};}
if(!(type in this._listeners)){this._listeners[type]=[];}
this._listeners[type].push(listener);};EventEmitter.prototype.removeEventListener=function ee_rm(type,listener){if(!this._listeners){return;}
var typeListeners=this._listeners[type];var pos=typeListeners.indexOf(listener);if(pos===-1){return;}
typeListeners.splice(pos,1);};function getPluralRule(lang){var locales2rules={'af':3,'ak':4,'am':4,'ar':1,'asa':3,'az':0,'be':11,'bem':3,'bez':3,'bg':3,'bh':4,'bm':0,'bn':3,'bo':0,'br':20,'brx':3,'bs':11,'ca':3,'cgg':3,'chr':3,'cs':12,'cy':17,'da':3,'de':3,'dv':3,'dz':0,'ee':3,'el':3,'en':3,'eo':3,'es':3,'et':3,'eu':3,'fa':0,'ff':5,'fi':3,'fil':4,'fo':3,'fr':5,'fur':3,'fy':3,'ga':8,'gd':24,'gl':3,'gsw':3,'gu':3,'guw':4,'gv':23,'ha':3,'haw':3,'he':2,'hi':4,'hr':11,'hu':0,'id':0,'ig':0,'ii':0,'is':3,'it':3,'iu':7,'ja':0,'jmc':3,'jv':0,'ka':0,'kab':5,'kaj':3,'kcg':3,'kde':0,'kea':0,'kk':3,'kl':3,'km':0,'kn':0,'ko':0,'ksb':3,'ksh':21,'ku':3,'kw':7,'lag':18,'lb':3,'lg':3,'ln':4,'lo':0,'lt':10,'lv':6,'mas':3,'mg':4,'mk':16,'ml':3,'mn':3,'mo':9,'mr':3,'ms':0,'mt':15,'my':0,'nah':3,'naq':7,'nb':3,'nd':3,'ne':3,'nl':3,'nn':3,'no':3,'nr':3,'nso':4,'ny':3,'nyn':3,'om':3,'or':3,'pa':3,'pap':3,'pl':13,'ps':3,'pt':3,'rm':3,'ro':9,'rof':3,'ru':11,'rwk':3,'sah':0,'saq':3,'se':7,'seh':3,'ses':0,'sg':0,'sh':11,'shi':19,'sk':12,'sl':14,'sma':7,'smi':7,'smj':7,'smn':7,'sms':7,'sn':3,'so':3,'sq':3,'sr':11,'ss':3,'ssy':3,'st':3,'sv':3,'sw':3,'syr':3,'ta':3,'te':3,'teo':3,'th':0,'ti':4,'tig':3,'tk':3,'tl':4,'tn':3,'to':0,'tr':0,'ts':3,'tzm':22,'uk':11,'ur':3,'ve':3,'vi':0,'vun':3,'wa':4,'wae':3,'wo':0,'xh':3,'xog':3,'yo':0,'zh':0,'zu':3};function isIn(n,list){return list.indexOf(n)!==-1;}
function isBetween(n,start,end){return typeof n===typeof start&&start<=n&&n<=end;}
var pluralRules={'0':function(){return'other';},'1':function(n){if((isBetween((n%100),3,10))){return'few';}
if(n===0){return'zero';}
if((isBetween((n%100),11,99))){return'many';}
if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'2':function(n){if(n!==0&&(n%10)===0){return'many';}
if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'3':function(n){if(n===1){return'one';}
return'other';},'4':function(n){if((isBetween(n,0,1))){return'one';}
return'other';},'5':function(n){if((isBetween(n,0,2))&&n!==2){return'one';}
return'other';},'6':function(n){if(n===0){return'zero';}
if((n%10)===1&&(n%100)!==11){return'one';}
return'other';},'7':function(n){if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'8':function(n){if((isBetween(n,3,6))){return'few';}
if((isBetween(n,7,10))){return'many';}
if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'9':function(n){if(n===0||n!==1&&(isBetween((n%100),1,19))){return'few';}
if(n===1){return'one';}
return'other';},'10':function(n){if((isBetween((n%10),2,9))&&!(isBetween((n%100),11,19))){return'few';}
if((n%10)===1&&!(isBetween((n%100),11,19))){return'one';}
return'other';},'11':function(n){if((isBetween((n%10),2,4))&&!(isBetween((n%100),12,14))){return'few';}
if((n%10)===0||(isBetween((n%10),5,9))||(isBetween((n%100),11,14))){return'many';}
if((n%10)===1&&(n%100)!==11){return'one';}
return'other';},'12':function(n){if((isBetween(n,2,4))){return'few';}
if(n===1){return'one';}
return'other';},'13':function(n){if((isBetween((n%10),2,4))&&!(isBetween((n%100),12,14))){return'few';}
if(n!==1&&(isBetween((n%10),0,1))||(isBetween((n%10),5,9))||(isBetween((n%100),12,14))){return'many';}
if(n===1){return'one';}
return'other';},'14':function(n){if((isBetween((n%100),3,4))){return'few';}
if((n%100)===2){return'two';}
if((n%100)===1){return'one';}
return'other';},'15':function(n){if(n===0||(isBetween((n%100),2,10))){return'few';}
if((isBetween((n%100),11,19))){return'many';}
if(n===1){return'one';}
return'other';},'16':function(n){if((n%10)===1&&n!==11){return'one';}
return'other';},'17':function(n){if(n===3){return'few';}
if(n===0){return'zero';}
if(n===6){return'many';}
if(n===2){return'two';}
if(n===1){return'one';}
return'other';},'18':function(n){if(n===0){return'zero';}
if((isBetween(n,0,2))&&n!==0&&n!==2){return'one';}
return'other';},'19':function(n){if((isBetween(n,2,10))){return'few';}
if((isBetween(n,0,1))){return'one';}
return'other';},'20':function(n){if((isBetween((n%10),3,4)||((n%10)===9))&&!(isBetween((n%100),10,19)||isBetween((n%100),70,79)||isBetween((n%100),90,99))){return'few';}
if((n%1000000)===0&&n!==0){return'many';}
if((n%10)===2&&!isIn((n%100),[12,72,92])){return'two';}
if((n%10)===1&&!isIn((n%100),[11,71,91])){return'one';}
return'other';},'21':function(n){if(n===0){return'zero';}
if(n===1){return'one';}
return'other';},'22':function(n){if((isBetween(n,0,1))||(isBetween(n,11,99))){return'one';}
return'other';},'23':function(n){if((isBetween((n%10),1,2))||(n%20)===0){return'one';}
return'other';},'24':function(n){if((isBetween(n,3,10)||isBetween(n,13,19))){return'few';}
if(isIn(n,[2,12])){return'two';}
if(isIn(n,[1,11])){return'one';}
return'other';}};var index=locales2rules[lang.replace(/-.*$/,'')];if(!(index in pluralRules)){return function(){return'other';};}
return pluralRules[index];}
var MAX_PLACEABLES=100;var PropertiesParser={patterns:null,entryIds:null,init:function(){this.patterns={comment:/^\s*#|^\s*$/,entity:/^([^=\s]+)\s*=\s*(.*)$/,multiline:/[^\\]\\$/,index:/\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,unicode:/\\u([0-9a-fA-F]{1,4})/g,entries:/[^\r\n]+/g,controlChars:/\\([\\\n\r\t\b\f\{\}\"\'])/g,placeables:/\{\{\s*([^\s]*?)\s*\}\}/,};},parse:function(ctx,source){if(!this.patterns){this.init();}
var ast=[];this.entryIds=Object.create(null);var entries=source.match(this.patterns.entries);if(!entries){return ast;}
for(var i=0;i<entries.length;i++){var line=entries[i];if(this.patterns.comment.test(line)){continue;}
while(this.patterns.multiline.test(line)&&i<entries.length){line=line.slice(0,-1)+entries[++i].trim();}
var entityMatch=line.match(this.patterns.entity);if(entityMatch){try{this.parseEntity(entityMatch[1],entityMatch[2],ast);}catch(e){if(ctx){ctx._emitter.emit('parseerror',e);}else{throw e;}}}}
return ast;},parseEntity:function(id,value,ast){var name,key;var pos=id.indexOf('[');if(pos!==-1){name=id.substr(0,pos);key=id.substring(pos+1,id.length-1);}else{name=id;key=null;}
var nameElements=name.split('.');if(nameElements.length>2){throw new L10nError('Error in ID: "'+name+'".'+' Nested attributes are not supported.');}
var attr;if(nameElements.length>1){name=nameElements[0];attr=nameElements[1];if(attr[0]==='$'){throw new L10nError('Attribute can\'t start with "$"',id);}}else{attr=null;}
this.setEntityValue(name,attr,key,this.unescapeString(value),ast);},setEntityValue:function(id,attr,key,rawValue,ast){var pos,v;var value=rawValue.indexOf('{{')>-1?this.parseString(rawValue):rawValue;if(attr){pos=this.entryIds[id];if(pos===undefined){v={$i:id};if(key){v[attr]={};v[attr][key]=value;}else{v[attr]=value;}
ast.push(v);this.entryIds[id]=ast.length-1;return;}
if(key){if(typeof(ast[pos][attr])==='string'){ast[pos][attr]={$x:this.parseIndex(ast[pos][attr]),$v:{}};}
ast[pos][attr].$v[key]=value;return;}
ast[pos][attr]=value;return;}
if(key){pos=this.entryIds[id];if(pos===undefined){v={};v[key]=value;ast.push({$i:id,$v:v});this.entryIds[id]=ast.length-1;return;}
if(typeof(ast[pos].$v)==='string'){ast[pos].$x=this.parseIndex(ast[pos].$v);ast[pos].$v={};}
ast[pos].$v[key]=value;return;}
ast.push({$i:id,$v:value});this.entryIds[id]=ast.length-1;},parseString:function(str){var chunks=str.split(this.patterns.placeables);var complexStr=[];var len=chunks.length;var placeablesCount=(len-1)/2;if(placeablesCount>=MAX_PLACEABLES){throw new L10nError('Too many placeables ('+placeablesCount+', max allowed is '+MAX_PLACEABLES+')');}
for(var i=0;i<chunks.length;i++){if(chunks[i].length===0){continue;}
if(i%2===1){complexStr.push({t:'idOrVar',v:chunks[i]});}else{complexStr.push(chunks[i]);}}
return complexStr;},unescapeString:function(str){if(str.lastIndexOf('\\')!==-1){str=str.replace(this.patterns.controlChars,'$1');}
return str.replace(this.patterns.unicode,function(match,token){return unescape('%u'+'0000'.slice(token.length)+token);});},parseIndex:function(str){var match=str.match(this.patterns.index);if(!match){throw new L10nError('Malformed index');}
if(match[2]){return[{t:'idOrVar',v:match[1]},match[2]];}else{return[{t:'idOrVar',v:match[1]}];}}};var KNOWN_MACROS=['plural'];var MAX_PLACEABLE_LENGTH=2500;var rePlaceables=/\{\{\s*(.+?)\s*\}\}/g;var nonLatin1=/[^\x01-\xFF]/;var FSI='\u2068';var PDI='\u2069';function createEntry(node,env){var keys=Object.keys(node);if(typeof node.$v==='string'&&keys.length===2){return node.$v;}
var attrs;for(var i=0,key;key=keys[i];i++){if(key[0]==='$'){continue;}
if(!attrs){attrs=Object.create(null);}
attrs[key]=createAttribute(node[key],env,node.$i+'.'+key);}
return{id:node.$i,value:node.$v!==undefined?node.$v:null,index:node.$x||null,attrs:attrs||null,env:env,dirty:false};}
function createAttribute(node,env,id){if(typeof node==='string'){return node;}
return{id:id,value:node.$v||(node!==undefined?node:null),index:node.$x||null,env:env,dirty:false};}
function format(args,entity){if(typeof entity==='string'){return[{},entity];}
if(entity.dirty){throw new L10nError('Cyclic reference detected: '+entity.id);}
entity.dirty=true;var rv;try{rv=resolveValue({},args,entity.env,entity.value,entity.index);}finally{entity.dirty=false;}
return rv;}
function resolveIdentifier(args,env,id){if(KNOWN_MACROS.indexOf(id)>-1){return[{},env['__'+id]];}
if(args&&args.hasOwnProperty(id)){if(typeof args[id]==='string'||(typeof args[id]==='number'&&!isNaN(args[id]))){return[{},args[id]];}else{throw new L10nError('Arg must be a string or a number: '+id);}}
if(id in env&&id!=='__proto__'){return format(args,env[id]);}
throw new L10nError('Unknown reference: '+id);}
function subPlaceable(locals,args,env,id){var res;try{res=resolveIdentifier(args,env,id);}catch(err){return[{error:err},'{{ '+id+' }}'];}
var value=res[1];if(typeof value==='number'){return res;}
if(typeof value==='string'){if(value.length>=MAX_PLACEABLE_LENGTH){throw new L10nError('Too many characters in placeable ('+
value.length+', max allowed is '+
MAX_PLACEABLE_LENGTH+')');}
if(locals.contextIsNonLatin1||value.match(nonLatin1)){res[1]=FSI+value+PDI;}
return res;}
return[{},'{{ '+id+' }}'];}
function interpolate(locals,args,env,arr){return arr.reduce(function(prev,cur){if(typeof cur==='string'){return[prev[0],prev[1]+cur];}else if(cur.t==='idOrVar'){var placeable=subPlaceable(locals,args,env,cur.v);return[prev[0],prev[1]+placeable[1]];}},[locals,'']);}
function resolveSelector(args,env,expr,index){var selectorName=index[0].v;var selector=resolveIdentifier(args,env,selectorName)[1];if(typeof selector!=='function'){return selector;}
var argValue=index[1]?resolveIdentifier(args,env,index[1])[1]:undefined;if(selector===env.__plural){if(argValue===0&&'zero'in expr){return'zero';}
if(argValue===1&&'one'in expr){return'one';}
if(argValue===2&&'two'in expr){return'two';}}
return selector(argValue);}
function resolveValue(locals,args,env,expr,index){if(!expr){return[locals,expr];}
if(typeof expr==='string'||typeof expr==='boolean'||typeof expr==='number'){return[locals,expr];}
if(Array.isArray(expr)){locals.contextIsNonLatin1=expr.some(function($_){return typeof($_)==='string'&&$_.match(nonLatin1);});return interpolate(locals,args,env,expr);}
if(index){var selector=resolveSelector(args,env,expr,index);if(expr.hasOwnProperty(selector)){return resolveValue(locals,args,env,expr[selector]);}}
if('other'in expr){return resolveValue(locals,args,env,expr.other);}
throw new L10nError('Unresolvable value');}
var Resolver={createEntry:createEntry,format:format,rePlaceables:rePlaceables};function walkContent(node,fn){if(typeof node==='string'){return fn(node);}
if(node.t==='idOrVar'){return node;}
var rv=Array.isArray(node)?[]:{};var keys=Object.keys(node);for(var i=0,key;(key=keys[i]);i++){if(key==='$i'||key==='$x'){rv[key]=node[key];}else{rv[key]=walkContent(node[key],fn);}}
return rv;}
var reAlphas=/[a-zA-Z]/g;var reVowels=/[aeiouAEIOU]/g;var ACCENTED_MAP='\u0226\u0181\u0187\u1E12\u1E16\u0191\u0193\u0126\u012A'+'\u0134\u0136\u013F\u1E3E\u0220\u01FE\u01A4\u024A\u0158'+'\u015E\u0166\u016C\u1E7C\u1E86\u1E8A\u1E8E\u1E90'+'[\\]^_`'+'\u0227\u0180\u0188\u1E13\u1E17\u0192\u0260\u0127\u012B'+'\u0135\u0137\u0140\u1E3F\u019E\u01FF\u01A5\u024B\u0159'+'\u015F\u0167\u016D\u1E7D\u1E87\u1E8B\u1E8F\u1E91';var FLIPPED_MAP='\u2200\u0510\u2183p\u018E\u025F\u05E4HI\u017F'+'\u04FC\u02E5WNO\u0500\xD2\u1D1AS\u22A5\u2229\u0245'+'\uFF2DX\u028EZ'+'[\\]\u1D65_,'+'\u0250q\u0254p\u01DD\u025F\u0183\u0265\u0131\u027E'+'\u029E\u0285\u026Fuodb\u0279s\u0287n\u028C\u028Dx\u028Ez';function makeLonger(val){return val.replace(reVowels,function(match){return match+match.toLowerCase();});}
function replaceChars(map,val){return val.replace(reAlphas,function(match){return map.charAt(match.charCodeAt(0)-65);});}
var reWords=/[^\W0-9_]+/g;function makeRTL(val){return val.replace(reWords,function(match){return'\u202e'+match+'\u202c';});}
var reExcluded=/(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;function mapContent(fn,val){if(!val){return val;}
var parts=val.split(reExcluded);var modified=parts.map(function(part){if(reExcluded.test(part)){return part;}
return fn(part);});return modified.join('');}
function Pseudo(id,name,charMap,modFn){this.id=id;this.translate=mapContent.bind(null,function(val){return replaceChars(charMap,modFn(val));});this.name=this.translate(name);}
var PSEUDO={'fr-x-psaccent':new Pseudo('fr-x-psaccent','Runtime Accented',ACCENTED_MAP,makeLonger),'ar-x-psbidi':new Pseudo('ar-x-psbidi','Runtime Bidi',FLIPPED_MAP,makeRTL)};function Locale(id,ctx){this.id=id;this.ctx=ctx;this.isReady=false;this.entries=Object.create(null);this.entries.__plural=getPluralRule(this.isPseudo()?this.ctx.defaultLocale:id);}
Locale.prototype.isPseudo=function(){return this.ctx.qps.indexOf(this.id)!==-1;};var bindingsIO={extra:function(id,ver,path,type,callback,errback){if(type==='properties'){type='text';}
navigator.mozApps.getLocalizationResource(id,ver,path,type).then(callback.bind(null,null),errback);},app:function(id,ver,path,type,callback,errback,sync){switch(type){case'properties':io.load(path,callback,sync);break;case'json':io.loadJSON(path,callback,sync);break;}},};Locale.prototype.build=function L_build(callback){var sync=!callback;var ctx=this.ctx;var self=this;var l10nLoads=ctx.resLinks.length;function onL10nLoaded(err){if(err){ctx._emitter.emit('fetcherror',err);}
if(--l10nLoads<=0){self.isReady=true;if(callback){callback();}}}
if(l10nLoads===0){onL10nLoaded();return;}
function onJSONLoaded(err,json){if(!err&&json){self.addAST(json);}
onL10nLoaded(err);}
function onPropLoaded(err,source){if(!err&&source){var ast=PropertiesParser.parse(ctx,source);self.addAST(ast);}
onL10nLoaded(err);}
var idToFetch=this.isPseudo()?ctx.defaultLocale:this.id;var appVersion=null;var source='app';if(typeof(navigator)!=='undefined'){source=navigator.mozL10n._config.localeSources[this.id]||'app';appVersion=navigator.mozL10n._config.appVersion;}
for(var i=0;i<ctx.resLinks.length;i++){var resLink=decodeURI(ctx.resLinks[i]);var path=resLink.replace('{locale}',idToFetch);var type=path.substr(path.lastIndexOf('.')+1);var cb;switch(type){case'json':cb=onJSONLoaded;break;case'properties':cb=onPropLoaded;break;}
bindingsIO[source](this.id,appVersion,path,type,cb,onL10nLoaded,sync);}};function createPseudoEntry(node,entries){return Resolver.createEntry(walkContent(node,PSEUDO[this.id].translate),entries);}
Locale.prototype.addAST=function(ast){var createEntry=this.isPseudo()?createPseudoEntry.bind(this):Resolver.createEntry;for(var i=0;i<ast.length;i++){this.entries[ast[i].$i]=createEntry(ast[i],this.entries);}};function Context(id){this.id=id;this.isReady=false;this.isLoading=false;this.defaultLocale='en-US';this.availableLocales=[];this.supportedLocales=[];this.qps=[];this.resLinks=[];this.locales={};this._emitter=new EventEmitter();this._ready=new Promise(this.once.bind(this));}
function reportMissing(id,err){this._emitter.emit('notfounderror',err);return id;}
function getWithFallback(id){var cur=0;var loc;var locale;while(loc=this.supportedLocales[cur]){locale=this.getLocale(loc);if(!locale.isReady){locale.build(null);}
var entry=locale.entries[id];if(entry===undefined){cur++;reportMissing.call(this,id,new L10nError('"'+id+'"'+' not found in '+loc+' in '+this.id,id,loc));continue;}
return entry;}
throw new L10nError('"'+id+'"'+' missing from all supported locales in '+this.id,id);}
function formatTuple(args,entity){try{return Resolver.format(args,entity);}catch(err){this._emitter.emit('resolveerror',err);var locals={error:err};return[locals,entity.id];}}
function formatValue(args,entity){if(typeof entity==='string'){return entity;}
return formatTuple.call(this,args,entity)[1];}
function formatEntity(args,entity){var entityTuple=formatTuple.call(this,args,entity);var value=entityTuple[1];var formatted={value:value,attrs:null,};if(entity.attrs){formatted.attrs=Object.create(null);}
for(var key in entity.attrs){var attrTuple=formatTuple.call(this,args,entity.attrs[key]);formatted.attrs[key]=attrTuple[1];}
return formatted;}
function formatAsync(fn,id,args){return this._ready.then(getWithFallback.bind(this,id)).then(fn.bind(this,args),reportMissing.bind(this,id));}
Context.prototype.formatValue=function(id,args){return formatAsync.call(this,formatValue,id,args);};Context.prototype.formatEntity=function(id,args){return formatAsync.call(this,formatEntity,id,args);};function legacyGet(fn,id,args){if(!this.isReady){throw new L10nError('Context not ready');}
var entry;try{entry=getWithFallback.call(this,id);}catch(err){if(err.loc){throw err;}
reportMissing.call(this,id,err);return'';}
return fn.call(this,args,entry);}
Context.prototype.get=function(id,args){return legacyGet.call(this,formatValue,id,args);};Context.prototype.getEntity=function(id,args){return legacyGet.call(this,formatEntity,id,args);};Context.prototype.getLocale=function getLocale(code){var locales=this.locales;if(locales[code]){return locales[code];}
return locales[code]=new Locale(code,this);};function negotiate(available,requested,defaultLocale){var supportedLocale;for(var i=0;i<requested.length;i++){var locale=requested[i];if(available.indexOf(locale)!==-1){supportedLocale=locale;break;}}
if(!supportedLocale||supportedLocale===defaultLocale){return[defaultLocale];}
return[supportedLocale,defaultLocale];}
function freeze(supported){var locale=this.getLocale(supported[0]);if(locale.isReady){setReady.call(this,supported);}else{locale.build(setReady.bind(this,supported));}}
function setReady(supported){this.supportedLocales=supported;this.isReady=true;this._emitter.emit('ready');}
Context.prototype.registerLocales=function(defLocale,available){if(defLocale){this.defaultLocale=defLocale;}
this.availableLocales=[this.defaultLocale];this.qps=Object.keys(PSEUDO);if(available){for(var i=0,loc;loc=available[i];i++){if(this.availableLocales.indexOf(loc)===-1){this.availableLocales.push(loc);var pos=this.qps.indexOf(loc);if(pos!==-1){this.qps.splice(pos,1);}}}}};Context.prototype.requestLocales=function requestLocales(){if(this.isLoading&&!this.isReady){throw new L10nError('Context not ready');}
this.isLoading=true;var requested=Array.prototype.slice.call(arguments);if(requested.length===0){throw new L10nError('No locales requested');}
var supported=negotiate(this.availableLocales.concat(this.qps),requested,this.defaultLocale);if(this.supportedLocales[0]!==supported[0]){freeze.call(this,supported);}};Context.prototype.addEventListener=function(type,listener){this._emitter.addEventListener(type,listener);};Context.prototype.removeEventListener=function(type,listener){this._emitter.removeEventListener(type,listener);};Context.prototype.ready=function(callback){if(this.isReady){setTimeout(callback);}
this.addEventListener('ready',callback);};Context.prototype.once=function(callback){if(this.isReady){setTimeout(callback);return;}
var callAndRemove=(function(){this.removeEventListener('ready',callAndRemove);callback();}).bind(this);this.addEventListener('ready',callAndRemove);};var allowed={elements:['a','em','strong','small','s','cite','q','dfn','abbr','data','time','code','var','samp','kbd','sub','sup','i','b','u','mark','ruby','rt','rp','bdi','bdo','span','br','wbr'],attributes:{global:['title','aria-label','aria-valuetext','aria-moz-hint'],a:['download'],area:['download','alt'],input:['alt','placeholder'],menuitem:['label'],menu:['label'],optgroup:['label'],option:['label'],track:['label'],img:['alt'],textarea:['placeholder'],th:['abbr']}};var rtlList=['ar','he','fa','ps','ar-x-psbidi','ur'];var nodeObserver=null;var pendingElements=null;var moConfig={attributes:true,characterData:false,childList:true,subtree:true,attributeFilter:['data-l10n-id','data-l10n-args']};navigator.mozL10n={ctx:null,get:function get(id,ctxdata){return navigator.mozL10n.ctx.get(id,ctxdata);},formatValue:function(id,ctxdata){return navigator.mozL10n.ctx.formatValue(id,ctxdata);},formatEntity:function(id,ctxdata){return navigator.mozL10n.ctx.formatEntity(id,ctxdata);},translateFragment:function(fragment){return translateFragment.call(navigator.mozL10n,fragment);},setAttributes:setL10nAttributes,getAttributes:getL10nAttributes,ready:function ready(callback){return navigator.mozL10n.ctx.ready(callback);},once:function once(callback){return navigator.mozL10n.ctx.once(callback);},get readyState(){return navigator.mozL10n.ctx.isReady?'complete':'loading';},language:{set code(lang){navigator.mozL10n.ctx.requestLocales(lang);},get code(){return navigator.mozL10n.ctx.supportedLocales[0];},get direction(){return getDirection(navigator.mozL10n.ctx.supportedLocales[0]);}},qps:PSEUDO,_config:{appVersion:null,localeSources:Object.create(null),isPretranslated:false,},_getInternalAPI:function(){return{Error:L10nError,Context:Context,Locale:Locale,Resolver:Resolver,getPluralRule:getPluralRule,rePlaceables:rePlaceables,translateDocument:translateDocument,onMetaInjected:onMetaInjected,PropertiesParser:PropertiesParser,walkContent:walkContent,buildLocaleList:buildLocaleList};}};function getDirection(lang){return(rtlList.indexOf(lang)>=0)?'rtl':'ltr';}
var readyStates={loading:0,interactive:1,complete:2};function whenInteractive(callback){if(readyStates[document.readyState]>=readyStates.interactive){callback();return;}
document.addEventListener('readystatechange',function l10n_onrsc(){if(readyStates[document.readyState]>=readyStates.interactive){document.removeEventListener('readystatechange',l10n_onrsc);callback();}});}
function initObserver(){nodeObserver=new MutationObserver(onMutations.bind(navigator.mozL10n));nodeObserver.observe(document,moConfig);}
function init(pretranslate){if(!pretranslate){initObserver();}
initResources.call(navigator.mozL10n);}
function initResources(){var meta={};var nodes=document.head.querySelectorAll('link[rel="localization"],'+'meta[name="availableLanguages"],'+'meta[name="defaultLanguage"],'+'meta[name="appVersion"],'+'script[type="application/l10n"]');for(var i=0,node;node=nodes[i];i++){var type=node.getAttribute('rel')||node.nodeName.toLowerCase();switch(type){case'localization':this.ctx.resLinks.push(node.getAttribute('href'));break;case'meta':onMetaInjected.call(this,node,meta);break;case'script':onScriptInjected.call(this,node);break;}}
var additionalLanguagesPromise;if(navigator.mozApps&&navigator.mozApps.getAdditionalLanguages){additionalLanguagesPromise=navigator.mozApps.getAdditionalLanguages().catch(function(e){console.error('Error while loading getAdditionalLanguages',e);});document.addEventListener('additionallanguageschange',function(evt){registerLocales.call(this,meta,evt.detail);this.ctx.requestLocales.apply(this.ctx,navigator.languages||[navigator.language]);}.bind(this));}else{additionalLanguagesPromise=Promise.resolve();}
additionalLanguagesPromise.then(function(extraLangs){registerLocales.call(this,meta,extraLangs);initLocale.call(this);}.bind(this));}
function registerLocales(meta,extraLangs){var locales=buildLocaleList.call(this,meta,extraLangs);navigator.mozL10n._config.localeSources=locales[1];this.ctx.registerLocales(locales[0],Object.keys(locales[1]));}
function getMatchingLangpack(appVersion,langpacks){for(var i=0,langpack;(langpack=langpacks[i]);i++){if(langpack.target===appVersion){return langpack;}}
return null;}
function buildLocaleList(meta,extraLangs){var loc,lp;var localeSources=Object.create(null);var defaultLocale=meta.defaultLanguage||this.ctx.defaultLocale;if(meta.availableLanguages){for(loc in meta.availableLanguages){localeSources[loc]='app';}}
if(extraLangs){for(loc in extraLangs){lp=getMatchingLangpack(this._config.appVersion,extraLangs[loc]);if(!lp){continue;}
if(!(loc in localeSources)||!meta.availableLanguages[loc]||parseInt(lp.revision)>meta.availableLanguages[loc]){localeSources[loc]='extra';}}}
if(!(defaultLocale in localeSources)){localeSources[defaultLocale]='app';}
return[defaultLocale,localeSources];}
function splitAvailableLanguagesString(str){var langs={};str.split(',').forEach(function(lang){lang=lang.trim().split(':');langs[lang[0]]=parseInt(lang[1]);});return langs;}
function onMetaInjected(node,meta){switch(node.getAttribute('name')){case'availableLanguages':meta.availableLanguages=splitAvailableLanguagesString(node.getAttribute('content'));break;case'defaultLanguage':meta.defaultLanguage=node.getAttribute('content');break;case'appVersion':navigator.mozL10n._config.appVersion=node.getAttribute('content');break;}}
function onScriptInjected(node){var lang=node.getAttribute('lang');var locale=this.ctx.getLocale(lang);locale.addAST(JSON.parse(node.textContent));}
function initLocale(){this.ctx.requestLocales.apply(this.ctx,navigator.languages||[navigator.language]);window.addEventListener('languagechange',function l10n_langchange(){this.ctx.requestLocales.apply(this.ctx,navigator.languages||[navigator.language]);}.bind(this));}
function localizeMutations(mutations){var mutation;var targets=new Set();for(var i=0;i<mutations.length;i++){mutation=mutations[i];if(mutation.type==='childList'){var addedNode;for(var j=0;j<mutation.addedNodes.length;j++){addedNode=mutation.addedNodes[j];if(addedNode.nodeType!==Node.ELEMENT_NODE){continue;}
targets.add(addedNode);}}
if(mutation.type==='attributes'){targets.add(mutation.target);}}
targets.forEach(function(target){if(target.childElementCount){translateFragment.call(this,target);}else if(target.hasAttribute('data-l10n-id')){translateElement.call(this,target);}},this);}
function onMutations(mutations,self){self.disconnect();localizeMutations.call(this,mutations);self.observe(document,moConfig);}
function onReady(){if(!navigator.mozL10n._config.isPretranslated){translateDocument.call(this);}
navigator.mozL10n._config.isPretranslated=false;if(pendingElements){for(var i=0,element;element=pendingElements[i];i++){translateElement.call(this,element);}
pendingElements=null;}
if(!nodeObserver){initObserver();}
fireLocalizedEvent.call(this);}
function fireLocalizedEvent(){var event=new CustomEvent('localized',{'bubbles':false,'cancelable':false,'detail':{'language':this.ctx.supportedLocales[0]}});window.dispatchEvent(event);}
var reOverlay=/<|&#?\w+;/;var reHtml=/[&<>]/g;var htmlEntities={'&':'&amp;','<':'&lt;','>':'&gt;',};function translateDocument(){document.documentElement.lang=this.language.code;document.documentElement.dir=this.language.direction;translateFragment.call(this,document.documentElement);}
function translateFragment(element){if(typeof element.hasAttribute==='function'&&element.hasAttribute('data-l10n-id')){translateElement.call(this,element);}
var nodes=getTranslatableChildren(element);for(var i=0;i<nodes.length;i++){translateElement.call(this,nodes[i]);}}
function setL10nAttributes(element,id,args){element.setAttribute('data-l10n-id',id);if(args){element.setAttribute('data-l10n-args',JSON.stringify(args));}}
function getL10nAttributes(element){return{id:element.getAttribute('data-l10n-id'),args:JSON.parse(element.getAttribute('data-l10n-args'))};}
function getTranslatableChildren(element){return element?element.querySelectorAll('*[data-l10n-id]'):[];}
function camelCaseToDashed(string){if(string==='ariaValueText'){return'aria-valuetext';}
return string.replace(/[A-Z]/g,function(match){return'-'+match.toLowerCase();}).replace(/^-/,'');}
function escapeL10nArgs(match){return htmlEntities[match];}
function translateElement(element){if(!this.ctx.isReady){if(!pendingElements){pendingElements=[];}
pendingElements.push(element);return;}
var l10nId=element.getAttribute('data-l10n-id');if(!l10nId){return false;}
var l10nArgs=element.getAttribute('data-l10n-args');var entity=this.ctx.getEntity(l10nId,l10nArgs?JSON.parse(l10nArgs.replace(reHtml,escapeL10nArgs)):undefined);var value=entity.value;if(typeof value==='string'){if(!reOverlay.test(value)){element.textContent=value;}else{var translation=element.ownerDocument.createElement('template');translation.innerHTML=value;overlayElement(element,translation.content);}}
for(var key in entity.attrs){var attrName=camelCaseToDashed(key);if(isAttrAllowed({name:attrName},element)){element.setAttribute(attrName,entity.attrs[key]);}}}
function overlayElement(sourceElement,translationElement){var result=translationElement.ownerDocument.createDocumentFragment();var k,attr;var childElement;while((childElement=translationElement.childNodes[0])){translationElement.removeChild(childElement);if(childElement.nodeType===Node.TEXT_NODE){result.appendChild(childElement);continue;}
var index=getIndexOfType(childElement);var sourceChild=getNthElementOfType(sourceElement,childElement,index);if(sourceChild){overlayElement(sourceChild,childElement);result.appendChild(sourceChild);continue;}
if(isElementAllowed(childElement)){var sanitizedChild=childElement.ownerDocument.createElement(childElement.nodeName);overlayElement(sanitizedChild,childElement);result.appendChild(sanitizedChild);continue;}
result.appendChild(document.createTextNode(childElement.textContent));}
sourceElement.textContent='';sourceElement.appendChild(result);if(translationElement.attributes){for(k=0,attr;(attr=translationElement.attributes[k]);k++){if(isAttrAllowed(attr,sourceElement)){sourceElement.setAttribute(attr.name,attr.value);}}}}
function isElementAllowed(element){return allowed.elements.indexOf(element.tagName.toLowerCase())!==-1;}
function isAttrAllowed(attr,element){var attrName=attr.name.toLowerCase();var tagName=element.tagName.toLowerCase();if(allowed.attributes.global.indexOf(attrName)!==-1){return true;}
if(!allowed.attributes[tagName]){return false;}
if(allowed.attributes[tagName].indexOf(attrName)!==-1){return true;}
if(tagName==='input'&&attrName==='value'){var type=element.type.toLowerCase();if(type==='submit'||type==='button'||type==='reset'){return true;}}
return false;}
function getNthElementOfType(context,element,index){var nthOfType=0;for(var i=0,child;child=context.children[i];i++){if(child.nodeType===Node.ELEMENT_NODE&&child.tagName===element.tagName){if(nthOfType===index){return child;}
nthOfType++;}}
return null;}
function getIndexOfType(element){var index=0;var child;while((child=element.previousElementSibling)){if(child.tagName===element.tagName){index++;}}
return index;}
var DEBUG=false;navigator.mozL10n.ctx=new Context(window.document?document.URL:null);navigator.mozL10n.ctx.ready(onReady.bind(navigator.mozL10n));navigator.mozL10n.ctx.addEventListener('notfounderror',function reportMissingEntity(e){if(DEBUG||e.loc==='en-US'){console.warn(e.toString());}});if(DEBUG){navigator.mozL10n.ctx.addEventListener('fetcherror',console.error.bind(console));navigator.mozL10n.ctx.addEventListener('parseerror',console.error.bind(console));navigator.mozL10n.ctx.addEventListener('resolveerror',console.error.bind(console));}
if(window.document){navigator.mozL10n._config.isPretranslated=document.documentElement.lang===navigator.language;var forcePretranslate=!navigator.mozL10n._config.isPretranslated;whenInteractive(init.bind(navigator.mozL10n,forcePretranslate));}
document.l10n={setAttributes:navigator.mozL10n.setAttributes,getAttributes:navigator.mozL10n.getAttributes,formatValue:function(id,args){return navigator.mozL10n.formatValue(id,args);},translateFragment:function(frag){return Promise.resolve(navigator.mozL10n.translateFragment(frag));},ready:new Promise(function(resolve){navigator.mozL10n.once(resolve);}),formatValues:function(){var keys=arguments;var resp=keys.map(function(key){if(Array.isArray(key)){return navigator.mozL10n.formatValue(key[0],key[1]);}
return navigator.mozL10n.formatValue(key);});return Promise.all(resp);},requestLanguages:function(langs){navigator.mozL10n.ctx.requestLocales.apply(navigator.mozL10n.ctx,langs);},pseudo:{'fr-x-psaccent':{getName:function(){return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].name);},processString:function(s){return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].translate(s));}},'ar-x-psbidi':{getName:function(){return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].name);},processString:function(s){return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].translate(s));}}},};navigator.mozL10n.ready(function(){document.documentElement.setAttribute('langs',navigator.mozL10n.ctx.supportedLocales.join(' '));});navigator.mozL10n.once(function(){window.addEventListener('localized',function(){document.dispatchEvent(new CustomEvent('DOMRetranslated',{bubbles:false,cancelable:false}));});});})(this);;;;'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';window.addEventListener('DOMContentLoaded',function(){LazyLoader.load(['/shared/js/l10n.js']).then(function(){});});window.onload=function(){var dependencies=['/shared/js/contact_photo_helper.js','/shared/js/sanitizer.js','/shared/js/contacts/import/utilities/misc.js','/contacts/views/matching/js/matching_ui.js'];window.screen.mozLockOrientation('portrait-primary');LazyLoader.load(dependencies).then(function(){MatchingUI.init();});};;'use strict';(function(exports){var CONTACTS_APP_ORIGIN=location.origin;var contact=null,matchings={};var type='matching';var parentWindow=null;function start(cid){if(!cid){type='listing';window.addEventListener('message',duplicateContactsHandler);parentWindow.postMessage({type:'duplicate_contacts_loaded'},CONTACTS_APP_ORIGIN);return;}
Curtain.show('wait','duplicateContacts');var callbacks={onmatch:showUI,onmismatch:function(){Curtain.show('alert','noDuplicateContacts');Curtain.onok=abort;}};Curtain.oncancel=function oncancel(){callbacks.onmatch=callbacks.onmismatch=null;abort();};var matcherDependencies=['/shared/js/text_normalizer.js','/shared/js/simple_phone_matcher.js','/shared/js/contacts/contacts_matcher.js'];LazyLoader.load(matcherDependencies,function loaded(){setTimeout(function(){parentWindow.ContactsService.get(cid,function success(mContact){contact=mContact;Matcher.match(contact,'active',callbacks);},abort);},1000);});}
function duplicateContactsHandler(e){if(e.origin!==CONTACTS_APP_ORIGIN){return;}
var data=e.data;if(data&&data.type==='show_duplicate_contacts'){window.removeEventListener('message',duplicateContactsHandler);contact={name:[data.data.name]};var duplicateContacts=data.data.duplicateContacts;var total=Object.keys(duplicateContacts).length;var matchingReady=function(){if(--total===0){window.addEventListener('message',contactsMergedHandler);showUI(duplicateContacts);}};Object.keys(duplicateContacts).forEach(function(cid){parentWindow.ContactsService.get(cid,function success(contact){duplicateContacts[cid]={matchingContact:contact,matchings:duplicateContacts[cid].matchings};matchingReady();},matchingReady);});}else if(data&&data.type==='sync'){parentWindow=e.source;navigator.mozL10n.once(function localized(evt){start(window.location.search.substring('contactId'.length+2));});}}
function contactsMergedHandler(e){if(e.origin!==CONTACTS_APP_ORIGIN){return;}
if(e.data&&e.data.type==='duplicate_contacts_merged'){window.removeEventListener('message',contactsMergedHandler);Curtain.hide(function(){parentWindow.postMessage({type:'window_close',data:''},CONTACTS_APP_ORIGIN);});}}
function showUI(results){matchings=results;window.dispatchEvent(new CustomEvent('initUI',{'detail':{'type':type,'contact':contact,'results':results}}));window.addEventListener('UIReady',function fn(){window.removeEventListener('UIReady',fn);if(type==='matching'){Curtain.hide(sendReadyEvent,CONTACTS_APP_ORIGIN);}else{sendReadyEvent();}});}
function sendReadyEvent(){parentWindow.postMessage({type:'ready',data:''},CONTACTS_APP_ORIGIN);}
function abort(){var notifyParent=function cmc_notifyParent(){parentWindow.postMessage({type:'abort',data:''},CONTACTS_APP_ORIGIN);};Curtain.hide(notifyParent);}
function merge(checkedContacts){Curtain.show('wait','mergingDuplicateContacts');Curtain.hideMenu();if(type==='listing'){parentWindow.postMessage({type:'merge_duplicate_contacts',data:checkedContacts},CONTACTS_APP_ORIGIN);return;}
var MERGE_DEPS=['/contacts/js/utilities/merge_helper.js'];LazyLoader.load(MERGE_DEPS,function loaded(){var cb=function cb(){Curtain.hide(function(){parentWindow.postMessage({type:'window_close',data:''},CONTACTS_APP_ORIGIN);});};var list=[];Object.keys(checkedContacts).forEach(function(id){list.push(matchings[id]);});MergeHelper.merge(contact,list).then(cb,cb);});}
window.addEventListener('message',duplicateContactsHandler);window.addEventListener('merge',evt=>{merge(evt.detail.checkedContacts);});})(window);;'use strict';var Curtain=(function(){var cancelButton,retryButton,okButton,progressElement,progressTitle;var cpuWakeLock;var messages=[];var elements=['error','timeout','wait','message','progress','alert'];var _cancelCB=function foo(){};var _retryCB=function foo(){};var _okCB=function foo(){};var form;var panel=document.createElement('panel');panel.id='curtain';var link=document.createElement('link');link.setAttribute('rel','import');link.setAttribute('href','/shared/pages/import/elements/curtain.html');function addListeners(){okButton.onclick=function on_ok(e){delete okButton.onclick;_okCB();_okCB=null;};retryButton.onclick=function on_retry(e){delete retryButton.onclick;_retryCB();_retryCB=null;};cancelButton.onclick=function on_cancel(e){delete cancelButton.onclick;_cancelCB();_cancelCB=null;};}
function cacheElements(){cancelButton=document.querySelector('#cancel');retryButton=document.querySelector('#retry');okButton=document.querySelector('#ok');progressElement=document.querySelector('#progressElement');elements.forEach(function createElementRef(name){messages[name]=document.getElementById(name+'Msg');});progressTitle=document.getElementById('progressTitle');}
function init(){return new Promise(function(resolve,reject){document.head.appendChild(link);panel.setAttribute('is','curtain');panel.className='fade-out';document.body.appendChild(panel);LazyLoader.load([document.getElementById('curtain')],function(){cacheElements();addListeners();resolve();});});}
function clean(){document.body.removeChild(panel);form=null;cancelButton=null;retryButton=null;okButton=null;progressElement=null;progressTitle=null;}
function doShow(type){if(!form){form=panel.querySelector('form');}
form.dataset.state=type;panel.classList.remove('no-menu');panel.classList.add('visible');panel.classList.remove('fade-out');if(type!=='alert'){panel.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(){navigator.mozL10n.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){init().then(function(){var out;from=capitalize(from);switch(type){case'wait':messages[type].setAttribute('data-l10n-id',type+from);break;case'timeout':navigator.mozL10n.formatValue('timeout'+from).then(from=>{navigator.mozL10n.setAttributes(messages[type],'timeout1',{from:from});});break;case'error':navigator.mozL10n.formatValue(type+from).then(from=>{navigator.mozL10n.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;}
panel.classList.add('fade-out');panel.addEventListener('animationend',function cu_fadeOut(ev){panel.removeEventListener('animationend',cu_fadeOut);delete form.dataset.state;clean();if(typeof hiddenCB==='function'){hiddenCB();}});},set oncancel(cancelCb){if(typeof cancelCb==='function'){_cancelCB=cancelCb;}else{_cancelCB=function foo(){};}},set onretry(retryCb){if(typeof retryCb==='function'){_retryCB=retryCb;}else{_retryCB=function foo(){};}},set onok(okCb){if(typeof okCb==='function'){_okCB=okCb;}else{_okCB=function foo(){};}},get visible(){return panel.classList.contains('visible');},hideMenu:function c_hideMenu(){panel.classList.add('no-menu');}};})();

View file

@ -0,0 +1 @@
;'use strict';window.addEventListener('DOMContentLoaded',function(){LazyLoader.load(['/shared/js/l10n.js']).then(function(){});});window.onload=function(){var dependencies=['/shared/js/contact_photo_helper.js','/shared/js/sanitizer.js','/shared/js/contacts/import/utilities/misc.js','/contacts/views/matching/js/matching_ui.js'];window.screen.mozLockOrientation('portrait-primary');LazyLoader.load(dependencies).then(function(){MatchingUI.init();});};

View file

@ -0,0 +1,12 @@
;'use strict';(function(exports){var CONTACTS_APP_ORIGIN=location.origin;var contact=null,matchings={};var type='matching';var parentWindow=null;function start(cid){if(!cid){type='listing';window.addEventListener('message',duplicateContactsHandler);parentWindow.postMessage({type:'duplicate_contacts_loaded'},CONTACTS_APP_ORIGIN);return;}
Curtain.show('wait','duplicateContacts');var callbacks={onmatch:showUI,onmismatch:function(){Curtain.show('alert','noDuplicateContacts');Curtain.onok=abort;}};Curtain.oncancel=function oncancel(){callbacks.onmatch=callbacks.onmismatch=null;abort();};var matcherDependencies=['/shared/js/text_normalizer.js','/shared/js/simple_phone_matcher.js','/shared/js/contacts/contacts_matcher.js'];LazyLoader.load(matcherDependencies,function loaded(){setTimeout(function(){parentWindow.ContactsService.get(cid,function success(mContact){contact=mContact;Matcher.match(contact,'active',callbacks);},abort);},1000);});}
function duplicateContactsHandler(e){if(e.origin!==CONTACTS_APP_ORIGIN){return;}
var data=e.data;if(data&&data.type==='show_duplicate_contacts'){window.removeEventListener('message',duplicateContactsHandler);contact={name:[data.data.name]};var duplicateContacts=data.data.duplicateContacts;var total=Object.keys(duplicateContacts).length;var matchingReady=function(){if(--total===0){window.addEventListener('message',contactsMergedHandler);showUI(duplicateContacts);}};Object.keys(duplicateContacts).forEach(function(cid){parentWindow.ContactsService.get(cid,function success(contact){duplicateContacts[cid]={matchingContact:contact,matchings:duplicateContacts[cid].matchings};matchingReady();},matchingReady);});}else if(data&&data.type==='sync'){parentWindow=e.source;navigator.mozL10n.once(function localized(evt){start(window.location.search.substring('contactId'.length+2));});}}
function contactsMergedHandler(e){if(e.origin!==CONTACTS_APP_ORIGIN){return;}
if(e.data&&e.data.type==='duplicate_contacts_merged'){window.removeEventListener('message',contactsMergedHandler);Curtain.hide(function(){parentWindow.postMessage({type:'window_close',data:''},CONTACTS_APP_ORIGIN);});}}
function showUI(results){matchings=results;window.dispatchEvent(new CustomEvent('initUI',{'detail':{'type':type,'contact':contact,'results':results}}));window.addEventListener('UIReady',function fn(){window.removeEventListener('UIReady',fn);if(type==='matching'){Curtain.hide(sendReadyEvent,CONTACTS_APP_ORIGIN);}else{sendReadyEvent();}});}
function sendReadyEvent(){parentWindow.postMessage({type:'ready',data:''},CONTACTS_APP_ORIGIN);}
function abort(){var notifyParent=function cmc_notifyParent(){parentWindow.postMessage({type:'abort',data:''},CONTACTS_APP_ORIGIN);};Curtain.hide(notifyParent);}
function merge(checkedContacts){Curtain.show('wait','mergingDuplicateContacts');Curtain.hideMenu();if(type==='listing'){parentWindow.postMessage({type:'merge_duplicate_contacts',data:checkedContacts},CONTACTS_APP_ORIGIN);return;}
var MERGE_DEPS=['/contacts/js/utilities/merge_helper.js'];LazyLoader.load(MERGE_DEPS,function loaded(){var cb=function cb(){Curtain.hide(function(){parentWindow.postMessage({type:'window_close',data:''},CONTACTS_APP_ORIGIN);});};var list=[];Object.keys(checkedContacts).forEach(function(id){list.push(matchings[id]);});MergeHelper.merge(contact,list).then(cb,cb);});}
window.addEventListener('message',duplicateContactsHandler);window.addEventListener('merge',evt=>{merge(evt.detail.checkedContacts);});})(window);

View file

@ -0,0 +1,304 @@
'use strict';
/* global ContactPhotoHelper */
/* global ImageLoader */
/* global LazyLoader */
/* global Sanitizer */
/* jshint nonew: false */
/**
* This object handles every interaction between the user and the UI of the
* matching contacts view
*/
(function(exports) {
var CONTACTS_APP_ORIGIN = location.origin;
var listDependencies = [
'/shared/js/contacts/utilities/image_loader.js'
];
var MatchingUI = {
_: null,
// Counter for checked list items
checked: 0,
// Hash contains identifiers of checked contacts
checkedContacts: {},
// Field order when showing matching details main reason
fieldOrder: ['tel', 'email', 'name'],
init: function() {
this._ = navigator.mozL10n.get;
this.mergeButton = document.getElementById('merge-action');
if (!this.mergeButton) {
return;
}
this.duplicateMessage = document.querySelector('#duplicate-msg > p');
this.contactsList =
document.querySelector('#contacts-list-container > ol');
this.contactsList.addEventListener('click', this.onClick.bind(this)
, true);
this.mergeButton.addEventListener('click', this.onMerge.bind(this));
window.addEventListener('initUI', evt => {
this.load(evt.detail.type, evt.detail.contact, evt.detail.results);
});
},
/*
* Loads the UI that implements the merge of duplicate contacts
*
* @param{Object} Master contact
*
* @param{Object} Hash of matching contacts from Matcher module
*
*/
load: function(type, contact, results) {
this.matchingResults = results;
document.body.dataset.mode = type;
var contactName = this.getDisplayName(contact);
var params = { name: Sanitizer.escapeHTML `${contactName}` };
if (type === 'matching') {
// "Suggested duplicate contacts for xxx"
navigator.mozL10n.setAttributes(this.duplicateMessage,
'suggestedDuplicatesMessage',
params);
} else {
navigator.mozL10n.setAttributes(document.querySelector('title'),
'duplicatesFoundTitle');
// "xxx duplicates information in the following contacts"
navigator.mozL10n.setAttributes(this.duplicateMessage,
'foundDuplicatesMessage',
params);
}
// Rendering the duplicate contacts list
this.renderList(results);
},
templateDuplicateContact: function(contact) {
return Sanitizer.createSafeHTML `<li data-uuid="${contact.id}"
class="block-item">
<label class="pack-checkbox">
<input type="checkbox" checked>
<span></span>
</label>
<aside class="pack-end">
<img data-src="${contact.thumb}">
</aside>
<p>
<bdi class="ellipsis-dir-fix">${contact.displayName}</bdi>
</p>
<p class="match-main-reason">
<bdi class="ellipsis-dir-fix">${contact.mainReason}</bdi>
</p>
</li>`;
},
renderList: function(contacts) {
LazyLoader.load(listDependencies, () => {
// For each contact in the list
var contactsKeys = Object.keys(contacts);
contactsKeys.forEach(id => {
// New contact appended
this.checkedContacts[id] = id;
var contact = this.cookContact(contacts[id]);
// Use a template node so we can append it and also access the node
var item = document.createElement('template');
item.innerHTML =
Sanitizer.unwrapSafeHTML(this.templateDuplicateContact(contact));
this.contactsList.appendChild(item.content);
if (contact.email1 === '') {
var emailField = item.content.querySelector('p:last-child');
emailField && emailField.parentNode.removeChild(emailField);
}
});
this.checked = contactsKeys.length;
this.checkMergeButton();
new ImageLoader('#main', 'li');
setTimeout(() => {
window.dispatchEvent(new CustomEvent('UIReady'));
// Remove the hidden ui
document.body.classList.remove('ui-not-ready');
});
});
},
/*
* It creates a new copy becase it is forbidden to add new attributes to
* original contacts provided by the API
*
* @param(Object) Contact object provided by Gecko
*/
cookContact: function(matching) {
var contact = matching.matchingContact;
var reasons = matching.matchings || {};
var out = {};
this.populate(contact, out, Object.getOwnPropertyNames(contact));
this.populate(contact, out,
Object.getOwnPropertyNames(Object.getPrototypeOf(contact)));
out.displayName = this.getDisplayName(contact);
out.mainReason = this.selectMainReason(reasons);
var photo = ContactPhotoHelper.getThumbnail(out);
if (photo) {
out.thumb = window.URL.createObjectURL(photo);
}
return out;
},
getCompleteName: function(contact) {
var givenName = Array.isArray(contact.givenName) ?
contact.givenName[0] : '';
var familyName = Array.isArray(contact.familyName) ?
contact.familyName[0] : '';
var completeName = givenName && familyName ?
givenName + ' ' + familyName :
givenName || familyName;
return completeName;
},
// Fills the contact data to display if no givenName and familyName
getDisplayName: function(contact) {
if (this.hasName(contact)) {
return this.getCompleteName(contact);
}
var name = [];
if (Array.isArray(contact.name) && contact.name[0] &&
contact.name[0].trim()) {
name.push(contact.name[0]);
} else if (contact.org && contact.org[0] && contact.org[0].trim()) {
name.push(contact.org[0]);
} else if (contact.tel && contact.tel[0]) {
name.push(contact.tel[0].value);
} else if (contact.email && contact.email[0]) {
name.push(contact.email[0].value);
} else {
name.push(this._('noName'));
}
return name[0];
},
hasName: function(contact) {
return (Array.isArray(contact.givenName) && contact.givenName[0] &&
contact.givenName[0].trim()) ||
(Array.isArray(contact.familyName) && contact.familyName[0] &&
contact.familyName[0].trim());
},
selectMainReason: function(matchings) {
var out = '';
for (var j = 0; j < this.fieldOrder.length; j++) {
var aField = this.fieldOrder[j];
var theMatchings = matchings[aField];
if (Array.isArray(theMatchings) && theMatchings[0]) {
out = theMatchings[0].matchedValue;
if (out) {
break;
}
}
}
return out;
},
populate: function(source, target, propertyNames) {
propertyNames.forEach(function(property) {
var propertyValue = source[property];
if (propertyValue) {
target[property] = propertyValue;
}
});
},
onClose: function(e) {
e.stopPropagation();
e.preventDefault();
parent.postMessage({
type: 'window_close',
data: ''
}, CONTACTS_APP_ORIGIN);
},
onClick: function(e) {
e.stopPropagation();
e.preventDefault();
if (e.target.tagName === 'INPUT') {
return false;
}
var li = e.target.closest('li');
var uuid = li.dataset.uuid;
var el = li.querySelector('input');
if (el.checked) {
--this.checked;
delete this.checkedContacts[uuid];
} else {
++this.checked;
this.checkedContacts[uuid] = uuid;
}
// Change manually the status as we prevented this
el.checked = !el.checked;
this.checkMergeButton();
},
isMatch: function(matchings, aField, fieldValue) {
var noMatch = true;
if (matchings[aField]) {
// Using every to short circuit the checking when a match is found.
noMatch = matchings[aField].every(function(obj) {
var val = fieldValue.value || fieldValue;
if (obj.matchedValue === val) {
return false;
}
return true;
});
}
return !noMatch;
},
checkMergeButton: function() {
navigator.mozL10n.setAttributes(this.mergeButton,
'mergeActionButtonLabel',
{ n: this.checked });
this.mergeButton.disabled = (this.checked === 0);
},
onMerge: function(e) {
e.stopPropagation();
e.preventDefault();
window.dispatchEvent(new CustomEvent('merge', {
'detail': {
'checkedContacts': this.checkedContacts
}
}));
}
};
exports.MatchingUI = MatchingUI;
})(window);

View file

@ -0,0 +1,75 @@
<!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 data-l10n-id="mergeDuplicateTitle">Merge duplicates</title>
<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.views.matching.matching_contacts.{locale}.json">
<!-- <script type="text/javascript" src="/shared/js/l10n.js"></script> -->
<!-- <script type="text/javascript" src="/shared/js/lazy_loader.js"></script> -->
<!-- <script type="text/javascript" src="/contacts/views/matching/js/boot.js"></script> -->
<!-- <script type="text/javascript" src="/contacts/views/matching/js/matching_controller.js"></script> -->
<!-- <script type="text/javascript" src="/shared/pages/import/js/curtain_panel.js"></script> --><script src="./gaia_build_matching_contacts.js" type="text/javascript"></script>
<!-- Lazy loaded, when rendering duplicate list
<script type="text/javascript" src="/shared/js/contacts/utilities/image_loader.js"></script>
<script type="text/javascript" src="/shared/js/contacts/utilities/templates.js"></script>
<script defer src="/shared/js/sanitizer.js"></script>
-->
<!-- Lazy loaded, files needed for match&merge
<script type="text/javascript" src="/shared/js/text_normalizer.js"></script>
<script type="text/javascript" src="/shared/js/simple_phone_matcher.js"></script>
<script type="text/javascript" src="/shared/js/contacts/contacts_matcher.js"></script>
<script type="text/javascript" src="/shared/js/contacts/contacts_merger.js"></script>
-->
<!-- Building Blocks -->
<link href="/shared/style/switches.css" rel="stylesheet">
<link href="/shared/style/lists.css" rel="stylesheet">
<link href="/shared/style/confirm.css" rel="stylesheet">
<link href="/shared/style/animations.css" rel="stylesheet">
<link href="/shared/style/headers.css" rel="stylesheet">
<link href="/shared/style/progress_activity.css" rel="stylesheet">
<link href="/shared/pages/import/style/root.css" rel="stylesheet">
<link href="/shared/pages/import/style/curtain.css" rel="stylesheet">
<!-- Web Components -->
<!-- <script defer="" src="/shared/elements/config.js"></script> --><script src="./gaia_build_defer_matching_contacts.js" defer="" type="text/javascript"></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">
<link rel="stylesheet" href="style/matching_contacts.css">
<!--<link rel="import" href="/shared/pages/import/elements/curtain.html">-->
<meta content="2.5" name="appVersion"></head>
<body role="application" class="skin-comms ui-not-ready">
<section role="region">
<section id="main">
<section id="duplicate-msg">
<p></p>
</section>
<!-- Contacts List Section -->
<section id="contacts-list-container" data-type="list" class="active">
<ol>
</ol>
</section> <!-- contacts-list-container -->
<form id="footer" role="dialog" data-type="confirm">
<menu>
<button id="merge-action" data-l10n-id="merge" class="danger full">Merge</button>
</menu>
</form>
</section> <!-- main -->
</section> <!-- role="section" -->
</body>
</html>

View file

@ -0,0 +1,128 @@
@import url("/contacts/style/root.css");
.hide, *[data-template] {
display: none;
}
#main {
position: fixed;
height: 100%;
width: calc(100% + 0.8rem);
overflow-x: hidden;
overflow-y: auto;
background: #F4F4F4;
}
#contacts-list-container.active li:not(:active):after {
display: none;
}
#contacts-list-container label {
left: 0;
}
#contacts-list-container > ol > li[data-uuid] {
display: block;
-moz-padding-start: 4rem;
border-top: none;
}
/*#contacts-list-container > ol > li[data-uuid] aside,
#contacts-list-container > ol > li[data-uuid] label,
#contacts-list-container > ol > li[data-uuid] p {
pointer-events: none;
}*/
#contacts-list-container > ol > li[data-uuid] aside > img {
box-shadow: none;
border: none;
}
#contacts-list-container > ol > li[data-uuid]:last-child {
border-bottom: none;
}
/* BB overrides */
.match-main-reason {
color: #008eab;
}
form[role="dialog"][data-type="edit"] #duplicate-msg > p {
color: #000;
overflow: visible;
white-space: normal;
}
form[role="dialog"][data-type="edit"] #main {
pointer-events: auto;
}
#duplicate-msg {
width: 100%;
z-index: 1;
padding: 0.4rem 0;
}
#duplicate-msg > p {
font-size: 1.8rem;
line-height: 2.2rem;
margin: 1rem 3rem;
}
#footer {
background: none;
pointer-events: none;
position: fixed;
}
#footer menu {
border-top: #d8d8d8 0.1rem solid;
background: #e8e8e8;
}
#footer button:not([disabled]) {
pointer-events: auto;
}
[data-type="list"] aside img {
border-style: hidden;
}
[data-type="list"] li[data-visited="true"] aside img {
border-style: solid;
}
/* Ellipsis direction hack before bug 883884 is fixed */
.ellipsis-dir-fix {
display: block;
width: -moz-fit-content;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
/* RTL View */
html[dir="rtl"] #contacts-list-container label {
left: unset;
right: 0;
}
#contacts-list-container > ol > li[data-uuid] label {
pointer-events: auto;
}
.ui-not-ready {
background-color: #27c8c2;
}
.ui-not-ready section[role="region"] {
display: none;
}
/* Bug 1202359: Prevent send two events */
span::after {
pointer-events: none;
}

View file

@ -0,0 +1,387 @@
;;;;;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 HtmlImports={populate:function(callback){var imports=document.querySelectorAll('link[rel="import"]');if(!imports.length){return;}
var pending=imports.length;Array.prototype.forEach.call(imports,function perImport(eachImport){this.getImportContent(eachImport.href,function gotContent(content){var elementTemplates={};var elementRoot=document.createElement('div');elementRoot.innerHTML=content;var elements=elementRoot.querySelectorAll('element');for(var i=0,iLen=elements.length;i<iLen;i++){var element=elements[i];var template=element.querySelector('template');elementTemplates[element.getAttribute('name')]=template.innerHTML;}
var replaceableElements=document.querySelectorAll('*[is]');Array.prototype.forEach.call(replaceableElements,function _each(el){if(elementTemplates[el.getAttribute('is')]){el.innerHTML=elementTemplates[el.getAttribute('is')];el.removeAttribute('is');}});if(!(--pending)){callback();}});},this);},getImportContent:function(path,callback){var parsedURL=new URL(path,location.href);if(parsedURL.origin!==location.origin){return;}
var xhr=new XMLHttpRequest();xhr.onload=function(o){callback(xhr.responseText);};xhr.open('GET',path,true);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';window.addEventListener('DOMContentLoaded',function(){LazyLoader.load(['/shared/js/l10n.js']).then(function(){LazyLoader.load([document.getElementById('multiple-select-view')]);});});window.addEventListener('load',function(){var dependencies=['/shared/js/contacts/import/utilities/vcard_reader.js','/contacts/views/vcard_load/js/vcard_handler.js','/contacts/views/vcard_load/js/vcard_load_ui.js','/contacts/views/vcard_load/js/vcard_load_controller.js'];LazyLoader.load(dependencies).then(function(){VCardLoadUI.init();VCardLoadController.init();navigator.mozSetMessageHandler('activity',function(activity){VCardLoadController.setActivity(activity);VCardHandler.handle(activity).then(contacts=>{var data=activity.source.data;var filename=VCardHandler.getFileName(data.filename||data.src);VCardLoadUI.render(contacts,filename);});});});});

View file

@ -0,0 +1 @@
;'use strict';window.addEventListener('DOMContentLoaded',function(){LazyLoader.load(['/shared/js/l10n.js']).then(function(){LazyLoader.load([document.getElementById('multiple-select-view')]);});});window.addEventListener('load',function(){var dependencies=['/shared/js/contacts/import/utilities/vcard_reader.js','/contacts/views/vcard_load/js/vcard_handler.js','/contacts/views/vcard_load/js/vcard_load_ui.js','/contacts/views/vcard_load/js/vcard_load_controller.js'];LazyLoader.load(dependencies).then(function(){VCardLoadUI.init();VCardLoadController.init();navigator.mozSetMessageHandler('activity',function(activity){VCardLoadController.setActivity(activity);VCardHandler.handle(activity).then(contacts=>{var data=activity.source.data;var filename=VCardHandler.getFileName(data.filename||data.src);VCardLoadUI.render(contacts,filename);});});});});

View file

@ -0,0 +1,58 @@
/* global VCardReader */
(function(exports) {
'use strict';
function handle(activity) {
return getvCardReader(activity.source.data.blob).then(readvCard);
}
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 contacts = [];
return new Promise(function(resolve, reject) {
var cursor = vCardReader.getAll();
cursor.onsuccess = function(event) {
if (!event.target.result) {
resolve(contacts);
return;
}
contacts.push(event.target.result);
cursor.continue();
};
cursor.onerror = function(event) {
console.error('Error while retrieving cursor from vcard');
reject();
};
});
}
function getFileName(path) {
var barIndex = path.lastIndexOf('/');
if (barIndex > -1) {
path = path.substring(barIndex + 1);
}
return path;
}
exports.VCardHandler = {
'handle': handle,
'getFileName': getFileName
};
})(window);

View file

@ -0,0 +1,100 @@
/* global LazyLoader, ContactsService, Matcher, contacts */
(function(exports) {
'use strict';
var _activity;
const STATUS_TIMEOUT = 3000; // ms before closing view which shows status msg
function close() {
if (_activity) {
_activity.postResult({});
} else {
window.history.back();
}
}
function cleanup() {
window.removeEventListener('closeAction', close);
window.removeEventListener('saveAction', importAll);
}
function importAll(evt) {
if (!evt.detail.contactsToImport) {
console.error('Missing parameter');
return;
}
cleanup();
var contactsToImport = evt.detail.contactsToImport;
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',
'/contacts/services/contacts.js',
'/shared/js/text_normalizer.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) {
window.dispatchEvent(new CustomEvent('showStatusAction', {
'detail': {
'numDups': numDupsMerged,
'importedContacts': importedContacts
}
}));
window.setTimeout(close, STATUS_TIMEOUT);
}
}
}
exports.VCardLoadController = {
init: function() {
window.addEventListener('closeAction', close);
window.addEventListener('saveAction', importAll);
},
setActivity: function(activity) {
_activity = activity;
}
};
})(window);

View file

@ -0,0 +1,97 @@
/*global utils */
(function(exports) {
'use strict';
var contactsToImport = [];
var header, saveButton, container, template, title;
function cacheElements() {
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');
}
function addListeners() {
saveButton.addEventListener('click',
dispatchEvent.bind(null, 'saveAction',
{'contactsToImport': contactsToImport}));
header.addEventListener('action', dispatchEvent.bind(null, 'closeAction'));
window.addEventListener('showStatusAction', onShowStatus);
}
/**
* Initializes references to DOM elements. This is needed before calling
* render.
*/
function init() {
cacheElements();
addListeners();
}
/**
* Allows to preview which contacts will be imported. Requires to call init
* first when DOM is ready.
*
* @param cursor - Reader object to continue reading from vCard.
* @param {string} filename - Name of the vcf file we are reading from.
*/
function render(contacts, filename) {
container.innerHTML = ''; // Avoids rendering the same content twice.
title.textContent = filename;
contacts.forEach(doRenderContact);
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);
}
}
function dispatchEvent(name, data) {
window.dispatchEvent(new CustomEvent(name, {'detail': data}));
}
function onShowStatus(evt) {
if (!evt.detail) {
return;
}
var imported = evt.detail.importedContacts,
merged = evt.detail.numDups;
utils.status.show({
id: 'vCardContacts-imported',
args: {
n: imported
}
}, {
id: 'contactsMerged',
args: {
numDups: merged
}
});
}
exports.VCardLoadUI = {
'init': init,
'render': render
};
})(window);

View file

@ -0,0 +1,4 @@
aside span {
background-position: center;
background-size: 5rem;
}

View file

@ -0,0 +1,86 @@
<!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/curtain_frame.css" rel="stylesheet">
<link href="/contacts/style/status.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="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
<!-- 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> -->
<!-- Localization -->
<link rel="localization" href="/locales-obj/contacts.views.vcard_load.vcard_load.{locale}.json">
<!-- <script defer="" src="/shared/js/html_imports.js"></script> -->
<!-- <script defer="" src="/shared/js/lazy_loader.js"></script> -->
<!-- <script defer="" src="/contacts/views/vcard_load/js/boot.js"></script> --><script src="./gaia_build_defer_vcard_load.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">
<!-- Load just the templates needed -->
<meta content="2.5" name="appVersion"></head>
<body role="application" class="app-contacts skin-comms theme-communications">
<section id="multiple-select-view" role="region" class="view current"><!--
<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>
<section id="statusMsg" role="status" class="hidden"><!--
<p></p>
--></section>
</body>
</html>