Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
|
|
@ -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>
|
||||
|
|
@ -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);});});});
|
||||
|
|
@ -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);});});});
|
||||
|
|
@ -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));
|
||||
|
|
@ -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));
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/* global navigationStack */
|
||||
/* exported MainNavigation */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
exports.MainNavigation = new navigationStack('view-contact-form');
|
||||
}(window));
|
||||
|
|
@ -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));
|
||||
|
|
@ -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));
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#view-contact-form {
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
Loading…
Reference in a new issue