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

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,547 @@
<!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>Messages</title>
<!--App Style root.css-->
<link rel="stylesheet" type="text/css" href="/views/shared/style/root.css">
<!-- Stable Building blocks -->
<link rel="stylesheet" type="text/css" href="/shared/style/animations.css">
<link rel="stylesheet" type="text/css" href="/shared/style/action_menu.css">
<link rel="stylesheet" type="text/css" href="/shared/style/option_menu.css">
<link rel="stylesheet" type="text/css" href="/shared/style/buttons.css">
<link rel="stylesheet" type="text/css" href="/shared/style/confirm.css">
<link rel="stylesheet" type="text/css" href="/shared/style/edit_mode.css">
<link rel="stylesheet" type="text/css" href="/shared/style/input_areas.css">
<link rel="stylesheet" type="text/css" href="/shared/style/switches.css">
<link rel="stylesheet" type="text/css" href="/shared/style/lists.css">
<link rel="stylesheet" type="text/css" href="/shared/style/progress_activity.css">
<link rel="stylesheet" type="text/css" href="/shared/style/status.css">
<link rel="stylesheet" type="text/css" href="/shared/style/tabs.css">
<link rel="stylesheet" type="text/css" href="/shared/elements/gaia-icons/gaia-icons.css">
<!-- App styles -->
<link rel="stylesheet" type="text/css" href="/views/shared/style/sms.css">
<link rel="stylesheet" type="text/css" href="/views/shared/style/panel.css">
<link rel="stylesheet" type="text/css" href="/views/shared/style/edit-mode.css">
<link rel="stylesheet" type="text/css" href="/views/shared/style/notification.css">
<link rel="stylesheet" type="text/css" href="/views/shared/style/common.css">
<link rel="stylesheet" type="text/css" href="/views/shared/style/composer.css">
<link rel="stylesheet" type="text/css" href="/views/conversation/style/message.css">
<link rel="stylesheet" type="text/css" href="/views/conversation/style/report_view.css">
<link rel="stylesheet" type="text/css" href="/views/conversation/style/attachment.css">
<link rel="stylesheet" type="text/css" href="/views/conversation/style/conversation.css">
<link rel="stylesheet" type="text/css" href="/views/conversation/style/edit-mode.css">
<link rel="stylesheet" type="text/css" href="/views/new_message/style/new_message.css">
<link rel="stylesheet" type="text/css" href="/views/new_message/style/recipients.css">
<!-- Localization -->
<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/views.conversation.index.{locale}.json">
<!-- <script defer="" src="/mocks/navigator_moz_icc_manager.js"></script> -->
<!-- Firefox Desktop shims -->
<!-- Web Components -->
<!-- <script defer="" src="/shared/elements/config.js"></script> -->
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
<script async="" src="/shared/elements/gaia-header/dist/gaia-header.js"></script>
<!--
<script defer src="/shared/js/component_utils.js"></script>
<script defer src="/shared/elements/gaia_menu/script.js"></script>
<script defer src="/shared/elements/gaia_sim_picker/script.js"></script>
<link rel="stylesheet" type="text/css" href="/shared/elements/gaia_menu/style.css">
<link rel="stylesheet" type="text/css" href="/shared/elements/gaia_sim_picker/style.css">
-->
<!-- Shared code -->
<!-- <script defer="" src="/shared/js/intl/l20n.js"></script> -->
<!-- Should be removed while gecko support navigator.mozHour12 API -->
<!-- <script defer="" src="/shared/js/date_time_helper.js"></script> -->
<!-- <script defer="" src="/shared/js/template.js"></script> -->
<!-- <script defer="" src="/shared/js/async_storage.js"></script> -->
<!-- <script defer="" src="/shared/js/lazy_loader.js"></script> -->
<!-- <script defer="" src="/shared/js/contact_photo_helper.js"></script> -->
<!-- <script defer="" src="/shared/js/fb/fb_request.js"></script> -->
<!-- <script defer="" src="/shared/js/fb/fb_data_reader.js"></script> -->
<!-- <script defer="" src="/shared/js/fb/fb_reader_utils.js"></script> -->
<!-- <script defer="" src="/shared/js/event_dispatcher.js"></script> -->
<!-- <script defer="" src="/views/shared/js/utils.js"></script> -->
<!-- <script defer="" src="/views/shared/js/navigation.js"></script> -->
<!-- <script defer="" src="/views/shared/js/inter_instance_event_dispatcher.js"></script> -->
<!-- <script defer="" src="/views/shared/js/time_headers.js"></script> -->
<!-- <script defer="" src="/views/shared/js/contacts.js"></script> -->
<!-- <script defer="" src="/services/js/drafts.js"></script> -->
<!-- <script defer="" src="/services/js/threads.js"></script> -->
<!-- <script defer="" src="/views/shared/js/settings.js"></script> -->
<!-- <script defer="" src="/services/js/message_manager.js"></script> -->
<!-- <script defer="" src="/views/shared/js/app.js"></script> -->
<!-- <script defer="" src="/shared/js/multi_sim_action_button.js"></script> -->
<!-- <script defer="" src="/views/shared/js/task_runner.js"></script> -->
<!-- <script defer="" src="/views/shared/js/wbmp.js"></script> -->
<!-- <script defer="" src="/views/shared/js/smil.js"></script> -->
<!-- <script defer="" src="/views/shared/js/shared_components.js"></script> -->
<!-- <script defer="" src="/views/conversation/js/link_helper.js"></script> -->
<!-- <script defer="" src="/shared/js/image_utils.js"></script> -->
<!-- <script defer="" src="/views/conversation/js/attachment.js"></script> -->
<!-- <script defer="" src="/views/conversation/js/attachment_renderer.js"></script> -->
<!-- <script defer="" src="/views/conversation/js/subject_composer.js"></script> -->
<!-- <script defer="" src="/views/conversation/js/compose.js"></script> -->
<!-- <script defer="" src="/shared/js/gesture_detector.js"></script> -->
<!-- <script defer="" src="/lib/bridge/client.js"></script> -->
<!-- <script defer="" src="/services/js/activity/activity_client.js"></script> -->
<!-- <script defer="" src="/views/conversation/js/recipients.js"></script> -->
<!-- <script defer="" src="/views/conversation/js/conversation.js"></script> -->
<!-- <script defer="" src="/views/conversation/js/startup.js"></script> -->
<!-- <script defer="" src="/views/shared/js/bootstrap.js"></script> --><script src="./gaia_build_defer_index.js" defer="" type="text/javascript"></script>
<!-- Lazy load scripts
<script defer src="/shared/js/settings_url.js"></script>
<script defer src="/shared/js/mime_mapper.js"></script>
<script defer src="/shared/js/mobile_operator.js"></script>
<script defer src="/shared/js/settings_listener.js"></script>
<script defer src="/shared/js/option_menu.js"></script>
<script defer src="/views/shared/js/errors.js"></script>
<script defer src="/views/shared/js/shared_components.js"></script>
<script defer src="/views/shared/js/dialog.js"></script>
<script defer src="/views/shared/js/error_dialog.js"></script>
<script defer src="/views/shared/js/waiting_screen.js"></script>
<script defer src="/views/shared/js/activity_picker.js"></script>
<script defer src="/views/conversation/js/link_action_handler.js"></script>
<script defer src="/views/shared/js/contact_renderer.js"></script>
<script defer src="/views/shared/js/localization_helper.js"></script>
<script defer src="/views/conversation/js/information.js"></script>
<script defer src="/services/js/messaging/messaging_client.js"></script>
<script defer src="/services/js/moz_mobile_connections/moz_mobile_connections_client.js"></script>
-->
<meta content="2.5" name="appVersion"></head>
<body role="application" class="skin-comms theme-communications">
<iframe src="/views/shared/shim_host.html" class="shim-host" hidden=""></iframe>
<article id="main-wrapper" class="wrapper">
<section class="panel panel-ConversationView editable-select-mode" role="region" aria-hidden="true">
<gaia-header id="messages-header" class="view-header regular-header" action="back" no-font-fit="">
<h1 id="messages-header-text"></h1>
<button aria-label="Call" id="messages-call-number-button" data-icon="call" class="hide" data-l10n-id="callButton"></button>
<button aria-label="Actions" id="messages-options-button" data-icon="more" data-l10n-id="actionsButton"></button>
</gaia-header>
<div id="messages-subheader" class="subheader">
<section id="messages-to-field">
<section data-l10n-id="to" id="to-label">To:</section>
<!-- When overflow occurs in the recipients list, scrolling is
commonly triggered automatically through focus and keyboard events.
In these cases, white space described by CSS `padding-bottom` will
not be automatically scrolled into view (because the element will
only scroll far enough to bring the cursor into view). A container
element is necessary to consistently render this vertical space
(instead realized with CSS `margin-bottom`). -->
<div id="messages-recipients-list-container" class="bb-editable" role="group" aria-labelledby="to-label">
<section id="messages-recipients-list" class="singleline">
</section>
</div>
<menu type="toolbar">
<a id="messages-contact-pick-button">
<span class="icon icon-user"></span>
</a>
</menu>
</section>
<div id="contact-carrier" class="carrier-wrapper"></div>
<!-- Notifications in this container will be rendered on top of the
Compose field. These notifications should be hidden automatically so
they do not prevent the user from inputting text. -->
<div class="overlay">
<section id="messages-subject-max-length-notice" role="notification" class="hide">
<p data-l10n-id="messages-max-subject-length-text">Maximum length of subject reached.</p>
</section>
<section id="messages-max-length-notice" role="notification" class="hide">
<p></p>
</section>
<section id="messages-sms-counter-notice" role="notification" class="hide">
<p></p>
</section>
<section id="messages-convert-notice" role="notification" class="hide">
<p></p>
</section>
<section id="messages-resize-notice" role="notification" class="hide">
<p data-l10n-id="resize-image">Resizing the attached image</p>
</section>
<section id="messages-new-message-notice" role="notification" class="hide">
<div class="text-container">
<div data-l10n-id="new-message-notice-text">New message from</div>
<div dir="auto" class="contact">Contact</div>
</div>
<div class="button-container">
<button class="notice-button" data-l10n-id="new-message-notice-a-text">Read it</button>
</div>
</section>
</div>
</div>
<article id="composer-container" class="view-body">
<div class="flex-composer">
<div id="messages-container" data-type="list" class="article-list"></div>
<form id="messages-compose-form" role="search" class="bottom" data-message-type="sms">
<section class="mms-label-line">
<section class="subject-composer js-subject-composer hide">
<div class="subject-composer-input" role="textbox" aria-multiline="false" contenteditable="true">
</div>
<div class="subject-composer-placeholder" data-l10n-id="messagesSubjectInput_placeholder">Subject</div>
</section>
<span class="mms-label" data-l10n-id="mms-label">MMS</span>
</section>
<section class="content-composer">
<div class="messages-attach-container">
<button id="messages-attach-button" aria-label="Add attachment" data-l10n-id="attach-button" type="button"></button>
</div>
<div class="composer-message-input-container">
<div title="Message" id="messages-input" name="message" x-inputmode="-moz-sms" data-l10n-id="messagesInput" role="textbox" aria-multiline="true" contenteditable="true">
</div>
</div>
<div class="composer-button-container">
<span class="message-counter js-message-counter"></span>
<button id="messages-send-button" class="icon" aria-label="Send" data-l10n-id="send-button" type="submit">
<div aria-label="using SIM ⁨{{ n }}⁩" class="js-sim-indication sim-indication" data-l10n-id="send-button-sim-indication">⁨{{ n }}⁩</div>
</button>
</div>
</section>
</form>
</div>
</article>
<form role="dialog" id="messages-edit-form" data-type="edit">
<section role="region" class="theme-settings">
<gaia-header id="messages-edit-header" action="close" no-font-fit="">
<h1 id="messages-edit-mode" data-l10n-id="deleteMessages-title">Delete messages</h1>
<button id="messages-check-uncheck-all-button" class="edit-button" data-l10n-id="select-all">Select all</button>
</gaia-header>
</section>
<div class="bottom-tab" role="region">
<ul class="bb-tablist">
<li role="presentation">
<button id="messages-delete-button" class="disable-when-dialog-animates" data-action="delete-button"></button>
</li>
</ul>
</div>
</form>
<section id="messages-recipient-suggestions" class="article-list hide" data-type="list">
<ul class="contact-list recipient-suggestions-list" role="listbox"></ul>
</section>
</section>
<section class="panel panel-GroupView" role="region" aria-hidden="true">
<gaia-header id="information-group-header" class="header" action="back" no-font-fit="">
<h1 data-l10n-id="participant" class="header-text">participant</h1>
</gaia-header>
<article class="view-body article-list" data-type="list">
<ul class="contact-list"></ul>
</article>
</section>
<section class="panel panel-ReportView" role="region" aria-hidden="true">
<gaia-header id="information-report-header" class="header" action="close" no-font-fit="">
<h1 data-l10n-id="message-report">Message report</h1>
</gaia-header>
<article class="view-body article-list report-view container" data-type="list">
<div class="description">
<p class="type"></p>
<p class="subject">
<span data-l10n-id="message-subject">Subject:</span>
<span class="detail"></span>
</p>
<p class="sent-block">
<span class="sent-title"></span>
<span class="sent-timestamp l10n-contains-date detail"></span>
</p>
<p class="received-block">
<span data-l10n-id="message-received">Received:</span>
<span class="received-timestamp l10n-contains-date detail"></span>
</p>
<p class="lateness-block">
<span data-l10n-id="message-lateness">Lateness:</span>
<span class="lateness">
<span class="lateness-month detail"></span>
<span class="lateness-day detail"></span>
<span class="lateness-hour detail"></span>
<span class="lateness-minute detail"></span>
<span class="lateness-second detail"></span>
</span>
</p>
<p class="size-block">
<span data-l10n-id="message-size-was">Message size:</span>
<span class="size detail"></span>
</p>
<p class="sim-info hide">
<span data-l10n-id="message-via">Via:</span>
<span class="sim-detail detail"></span>
</p>
</div>
<span class="contact-title"></span>
<ul class="contact-list"></ul>
<button class="resend-btn" data-l10n-id="message-resend">Resend</button>
</article>
</section>
</article>
<article id="loading">
<form role="dialog" data-type="confirm" class="loading-container">
<section>
<p class="loading-header" data-l10n-id="deleting-messages">Deleting…</p>
<div role="status">
<progress class="small"></progress>
</div>
</section>
</form>
</article>
<!--
- Templates
-->
<div id="contact-suggestion-tmpl" class="hide">
<!--
<li role="presentation">
<a class="suggestion js-contact-info" role="option"
data-number="${number}" data-source="contacts" data-name="${name}"
data-type="${type}" data-carrier="${carrier}">
${photoHTML}
<p class="name"><bdi class="ellipsis-dir-fix">${nameHTML}</bdi></p>
<p class="number">${phoneDetailsHTML}</p>
</a>
</li>
-->
</div>
<div id="contact-report-tmpl" class="hide">
<!--
<li>
<div class="js-contact-info">
${photoHTML}
<p class="name"><bdi class="ellipsis-dir-fix">${nameHTML}</bdi></p>
<p class="number">${phoneDetailsHTML}</p>
</div>
</li>
-->
</div>
<div id="contact-suggestion-unknown-tmpl" class="hide">
<!--
<li role="presentation">
<a class="suggestion" role="option"
data-number="${number}" data-source="contacts" data-name="${name}"
data-type="${type}" data-carrier="${carrier}">
${photoHTML}
<p class="name">${phoneDetailsHTML}</p>
<p class="number">
<span data-l10n-id="unknown-contact-title">Unknown Contact</span>
</p>
</a>
</li>
-->
</div>
<div id="contact-prompt-tmpl" class="hide">
<!--
<div class="contact-header">
${photoHTML}
<p class="name"><bdi class="ellipsis-dir-fix">${name}</bdi></p>
<p class="details">${phoneDetailsHTML}</p>
</div>
-->
</div>
<div id="contact-photo-tmpl" class="hide">
<!--
<aside role="img" data-l10n-id="contact-photo" aria-label="Contact photo" class="pack-end">
<span class="contact-photo" data-type="img"></span>
</aside>
-->
</div>
<div id="contact-highlight-tmpl" class="hide">
<!--
<span class="highlight">${str}</span>
-->
</div>
<div id="messages-number-tmpl" class="hide">
<!--
<a class="suggestion js-contact-info" data-number="${number}" data-source="manual" data-name="${number}">
<p class="name"><bdi class="ellipsis-dir-fix">${number}</bdi></p>
</a>
-->
</div>
<div id="information-report-tmpl" class="hide">
<!--
<span class="icon"></span>
<span data-l10n-id="${titleL10n}"></span>
<span class="timestamp l10n-contains-date"
data-l10n-date="${timestamp}"
data-l10n-date-format="${messageL10nDateFormat}"
>${reportDateL10n}</span>
-->
</div>
<div id="messages-not-downloaded-tmpl" class="hide">
<!--
<span class="not-downloaded-message l10n-contains-date"
data-l10n-id="${messageL10nId}"
data-l10n-args="${messageL10nArgs}"
data-l10n-date="${messageL10nDate}"
data-l10n-date-format="${messageL10nDateFormat}"></span>
<button type="button" class="download" data-l10n-id="${downloadL10nId}"></button>
-->
</div>
<div id="messages-date-group-tmpl">
<!--
<div id="${id}" class="messages-date-group" data-timestamp="${timestamp}">
<header
class="messages-date-group-header"
data-time-update="${headerTimeUpdate}"
data-time="${headerTime}"
data-date-only="${headerDateOnly}">
</header>
<ul class="message-list"></ul>
</div>
-->
</div>
<div id="messages-message-tmpl" class="hide">
<!--
<label class="pack-checkbox danger">
<input type="checkbox" value="${id}">
<span></span>
</label>
<section class="bubble">
<h1 class="message-subject">
${subject}
</h1>
<div class="message-content">
<p class="message-content-body" tabindex="-1">${bodyHTML}</p>
<div class="message-details">
${simInformationHTML}
<time data-time-update="true" data-time-only="true" data-time="${timestamp}"></time>
${messageStatusHTML}
</div>
</div>
</section>
-->
</div>
<div id="messages-message-sim-information-tmpl" class="hide">
<!--
<span class="message-sim-information"
data-l10n-id="sim-id-label"
data-l10n-args="${simNumberL10nArgs}">
</span>
-->
</div>
<div id="messages-message-status-tmpl" class="hide">
<!--
<div class="message-status" data-l10n-id="${statusL10nId}"
aria-label="Message status"
role="status">
</div>
-->
</div>
<div id="messages-late-arrival-notice-tmpl" class="hide">
<!--
<div class="late-arrival-icon" data-l10n-id="late-notice-label" aria-label="Late arrival"></div>
<div class="late-arrival-notice"></div>
-->
</div>
<div id="messages-recipient-tmpl" class="hide">
<!--
<span class="${className}" x-inputmode="verbatim"
contenteditable="${editable}" role="${role}"
data-number="${number}" data-source="${source}" data-name="${name}"
data-carrier="${carrier}" data-type="${type}" dir="auto">
${name}
</span>
-->
</div>
<div id="attachment-nopreview-tmpl" class="hide">
<!--
<div class="attachment ${errorClass}" data-attachment-type="${type}">
<div class="thumbnail-placeholder ${type}-placeholder">
</div>
<div class="js-size-indicator size-indicator" data-l10n-id="${sizeL10nId}" data-l10n-args="${sizeL10nArgs}"></div>
</div>
<div class="file-name" dir="auto"></div>
-->
</div>
<div id="attachment-preview-tmpl" class="hide">
<!--
<div class="attachment">
<div class="thumbnail"></div>
<div class="js-size-indicator size-indicator" data-l10n-id="${sizeL10nId}" data-l10n-args="${sizeL10nArgs}"></div>
</div>
<div class="file-name" dir="auto"></div>
-->
</div>
<div id="attachment-draft-tmpl" class="hide">
<!--
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<base href="${baseURL}">
<link rel="stylesheet" type="text/css" href="views/conversation/style/attachment_draft.css">
<link rel="stylesheet" type="text/css" href="views/conversation/style/attachment.css">
</head>
<body class="attachment-draft ${cssClass}">${baseHTML}</body>
-->
</div>
<!--
Please, don't change template markup alignment, it's done this way to
remove any whitespace symbols between inline elements.
-->
<div id="phone-details-tmpl" class="hide">
<!--
<span class="${hasTypeClass} ${hasCarrierClass}"><span
dir="auto" class="phone-type"
data-l10n-id="${type}">${type}</span><span
class="phone-type-separator"
data-l10n-id="phone-type-separator"></span><span
class="phone-number" dir="ltr">${number}</span><span
class="phone-carrier-separator"
data-l10n-id="phone-carrier-separator"></span><span
class="phone-carrier" dir="auto">${carrier}</span>
</span>
-->
</div>
<div id="messages-group-header-tmpl" class="hide">
<!--
<span class="group-header-title"><bdi class="ellipsis-dir-fix">${name}</bdi>&nbsp;<bdi dir="ltr">(+${participantCount})</bdi></span>
-->
</div>
<div id="messages-header-tmpl" class="hide">
<!--
<bdi class="ellipsis-dir-fix">${name}</bdi>
-->
</div>
<article>
<gaia-sim-picker id="sim-picker" tabindex="-1"></gaia-sim-picker>
</article>
</body>
</html>

View file

@ -0,0 +1,5 @@
;(function(exports){'use strict';const ATTACHMENT_FOLDER_PATH='sms-attachments/';function getBaseName(filePath){if(!filePath){throw new Error('Filepath is not defined!');}
return filePath.substring(filePath.lastIndexOf('/')+1);}
function Attachment(blob,options){options=options||{};this.blob=blob;this.isDraft=!!options.isDraft;this.name=blob.name||options.name||'';if(typeof this.name!=='string'){this.name='';}
Object.defineProperty(this,'_renderer',{writable:true});}
Attachment.prototype={_getAttachmentRenderer:function(){this._renderer=this._renderer||AttachmentRenderer.for(this);return this._renderer;},get size(){return this.blob.size;},get type(){return Utils.typeFromMimeType(this.blob.type);},render:function(readyCallback){var attachmentRenderer=this._getAttachmentRenderer();attachmentRenderer.render().catch(function(e){console.error('Error occurred while rendering attachment.',e);}).then(readyCallback);return attachmentRenderer.getAttachmentContainer();},updateFileSize:function(){var attachmentRenderer=this._getAttachmentRenderer();attachmentRenderer.updateFileSize();},view:function(options){var l10nPromise=this.name?Promise.resolve(this.name):document.l10n.formatValue('unnamed-attachment');return l10nPromise.then(name=>{var mimetype=MimeMapper.guessTypeFromFileProperties(name,this.blob.type.toLowerCase());var filename=MimeMapper.ensureFilenameMatchesType(name,mimetype);filename=ATTACHMENT_FOLDER_PATH+getBaseName(filename);var activity=new MozActivity({name:'open',data:{type:mimetype,filename:filename,blob:this.blob,allowSave:options&&options.allowSave}});activity.onerror=function(){console.error('error with open activity',this.error.name);if(this.error.name==='NO_PROVIDER'){Utils.alert('attachmentOpenError');}};});}};exports.Attachment=Attachment;}(this));

View file

@ -0,0 +1,5 @@
;(function(exports){'use strict';const MAX_THUMBNAIL_GENERATION_SIZE=10*1024*1024;const THUMBNAIL_SIZE=100*window.devicePixelRatio;const RENDERERS={draft:{createAttachmentContainer:function(){var container=document.createElement('iframe');container.setAttribute('sandbox','allow-same-origin');return container;},renderTo:function(data,attachmentContainer){attachmentContainer.classList.add(data.cssClass);var template=Template('attachment-draft-tmpl');var markup=template.interpolate({baseURL:location.protocol+'//'+location.host+'/',baseHTML:data.markup,cssClass:data.cssClass},{safe:['baseHTML']});var blob=new Blob([markup],{type:'text/html'});var blobUrl=window.URL.createObjectURL(blob);attachmentContainer.src=blobUrl;return this._whenLoaded(attachmentContainer).then(function onload(){window.URL.revokeObjectURL(blobUrl);var contentDocument=attachmentContainer.contentDocument;var documentElement=contentDocument.documentElement;documentElement.lang=document.documentElement.getAttribute('lang');documentElement.dir=document.documentElement.getAttribute('dir');document.l10n.translateFragment(documentElement);contentDocument.addEventListener('click',()=>attachmentContainer.click());return contentDocument.body;});},_whenLoaded:function(iframe){var innerDocument=iframe.contentDocument;if(innerDocument&&innerDocument.body&&innerDocument.body.classList.contains('attachment-draft')){return Promise.resolve();}
return new Promise((resolve)=>{iframe.addEventListener('load',function onload(){this.removeEventListener('load',onload);resolve();});});},getContentNode:function(attachmentContainer){return this._whenLoaded(attachmentContainer).then(()=>attachmentContainer.contentDocument.documentElement);},setL10nAttributes:function(element,l10nId,l10nArgs){document.l10n.setAttributes(element,l10nId,l10nArgs);document.l10n.translateFragment(element);}},base:{createAttachmentContainer:function(){return document.createElement('div');},renderTo:function(data,attachmentContainer){attachmentContainer.classList.add(data.cssClass);attachmentContainer.innerHTML=data.markup;return Promise.resolve(attachmentContainer);},getContentNode:function(attachmentContainer){return Promise.resolve(attachmentContainer);},setL10nAttributes:function(element,l10nId,l10nArgs){document.l10n.setAttributes(element,l10nId,l10nArgs);}}};var AttachmentRenderer=function(attachment){this._attachment=attachment;this._renderer=attachment.isDraft?RENDERERS.draft:RENDERERS.base;this._attachmentContainer=null;};AttachmentRenderer.prototype.getAttachmentContainer=function(){if(!this._attachmentContainer){this._attachmentContainer=this._createAttachmentContainer();}
return this._attachmentContainer;};AttachmentRenderer.prototype.render=function(){var attachmentContainer=this.getAttachmentContainer();var thumbnailPromise=this._attachment.type==='img'&&this._attachment.size<MAX_THUMBNAIL_GENERATION_SIZE?this.getThumbnail():Promise.reject();return thumbnailPromise.then((thumbnail)=>{return{markup:this._getBaseMarkup('attachment-preview-tmpl'),cssClass:'preview',thumbnail:thumbnail};}).catch((error)=>{return{markup:this._getBaseMarkup('attachment-nopreview-tmpl',!!error),cssClass:'nopreview'};}).then((data)=>{return this._renderer.renderTo(data,attachmentContainer).then((contentContainer)=>{var thumbnailNode=contentContainer.querySelector('.thumbnail');if(thumbnailNode){thumbnailNode.style.backgroundImage='url("'+data.thumbnail.url+data.thumbnail.fragment+'")';attachmentContainer.dataset.thumbnail=data.thumbnail.url;}
var fileNode=contentContainer.querySelector('.file-name');if(this._attachment.name){fileNode.removeAttribute('data-l10n-id');fileNode.textContent=this._attachment.name.slice(this._attachment.name.lastIndexOf('/')+1);}else{fileNode.setAttribute('data-l10n-id','unnamed-attachment');}});});};AttachmentRenderer.prototype.updateFileSize=function(){var attachmentContainer=this.getAttachmentContainer();return this._renderer.getContentNode(attachmentContainer).then((contentNode)=>{var sizeIndicator=contentNode.querySelector('.js-size-indicator');if(!sizeIndicator){throw new Error('updateFileSize should be called after a render().');}
var sizeL10n=Utils.getSizeForL10n(this._attachment.size);this._renderer.setL10nAttributes(sizeIndicator,sizeL10n.l10nId,sizeL10n.l10nArgs);});};AttachmentRenderer.prototype.getThumbnail=function(){var blob=this._attachment.blob;return ImageUtils.getSizeAndType(blob).then(function getSizeResolve(data){var fragment=ImageUtils.Downsample.sizeNoMoreThan(THUMBNAIL_SIZE/Math.min(data.width,data.height));return{url:window.URL.createObjectURL(blob),fragment:fragment};});};AttachmentRenderer.prototype._createAttachmentContainer=function(){var attachmentContainer=this._renderer.createAttachmentContainer();attachmentContainer.classList.add('attachment-container');attachmentContainer.dataset.attachmentType=this._attachment.type;return attachmentContainer;};AttachmentRenderer.prototype._getBaseMarkup=function(templateId,hasError){var sizeL10n=Utils.getSizeForL10n(this._attachment.size);return Template(templateId).interpolate({type:this._attachment.type,errorClass:hasError?'corrupted':'',sizeL10nId:sizeL10n.l10nId,sizeL10nArgs:JSON.stringify(sizeL10n.l10nArgs)});};exports.AttachmentRenderer={for:function(attachment){return new AttachmentRenderer(attachment);}};}(this));

View file

@ -0,0 +1,52 @@
;'use strict';var Compose=(function(){const TYPES=['image','audio','video'];const UPDATE_DELAY=500;const MIN_AVAILABLE_CHARS_COUNT=20;var placeholderClass='placeholder';var attachmentClass='attachment-container';var attachments=new Map();var dom={form:null,message:null,sendButton:null,attachButton:null,counter:null};var state={empty:true,maxLength:null,size:null,lastScrollPosition:0,resizing:false,type:'sms',lock:{canSend:null,canEdit:null,forceType:null},segmentInfo:{segments:0,charsAvailableInLastSegment:0}};var subject=null;function getContentFromDOM(domElement){var content=[];var node;for(node=domElement.firstChild;node;node=node.nextSibling){var attachment=attachments.get(node);if(attachment){content.push(attachment);continue;}
var last=content.length-1;var text=node.textContent;if(text){text=text.replace(/\u00A0/g,' ');}
if(node.nodeName=='BR'){if(node===domElement.lastChild){continue;}
text='\n';}
if(text.length){if(typeof content[last]==='string'){content[last]+=text;}else{content.push(text);}}}
return content;}
function onContentChanged(duck){if((duck instanceof Attachment&&duck.type==='img')||(duck&&duck.containsImage)){return imageAttachmentsHandling();}
var isEmptyMessage=!dom.message.textContent.length&&!hasAttachment();if(isEmptyMessage){var brs=dom.message.getElementsByTagName('br');if(brs.length>1){isEmptyMessage=false;}}
var placeholding=dom.message.classList.contains(placeholderClass);if(placeholding&&!isEmptyMessage){dom.message.classList.remove(placeholderClass);}
if(!placeholding&&isEmptyMessage){dom.message.classList.add(placeholderClass);}
state.emptyMessage=isEmptyMessage;Compose.updateEmptyState();Compose.updateType();Compose.updateSendButton();Compose.updateAttachButton();updateSegmentInfoThrottled();Compose.emit('input');}
function onSubjectChanged(){Compose.updateEmptyState();Compose.updateType();Compose.updateSendButton();Compose.updateAttachButton();Compose.emit('subject-change');}
function onSubjectVisibilityChanged(){if(subject.isVisible()){dom.form.classList.add('subject-composer-visible');}else{dom.message.focus();dom.form.classList.remove('subject-composer-visible');}}
function hasAttachment(){return!!dom.message.querySelector('iframe');}
function hasSubject(){return subject.isVisible()&&!!subject.getValue();}
function composeKeyEvents(e){if(!compose.canEdit()&&e.which!==8){e.preventDefault();}else{state.size=null;}}
function insert(item){var fragment=document.createDocumentFragment();if(!item){return null;}
state.size=null;if(item.render){var node=item.render();attachments.set(node,item);fragment.appendChild(node);}else if(item.nodeName==='IFRAME'){fragment.appendChild(item);}else if(typeof item==='string'){item.split('\n').forEach(function(line){fragment.appendChild(document.createTextNode(line));fragment.appendChild(document.createElement('br'));});fragment.lastElementChild.remove();}
return fragment;}
function imageAttachmentsHandling(){if(Compose.size<Settings.mmsSizeLimitation){onContentChanged();return;}
var imgNodes=[];attachments.forEach((attachment,node)=>{if(attachment.type==='img'){imgNodes.push(node);}});var done=0;var images=imgNodes.length;var limit=images>2?Settings.mmsSizeLimitation*0.2:Settings.mmsSizeLimitation*0.4;function imageSized(){if(++done===images){state.resizing=false;onContentChanged();}else{resizedImg(imgNodes[done]);}}
function resizedImg(node){var item=attachments.get(node);if(item.blob.size<limit){imageSized();}else{Utils.getResizedImgBlob(item.blob,limit,function(resizedBlob){state.size=null;item.blob=resizedBlob;item.updateFileSize();imageSized();});}}
state.resizing=true;resizedImg(imgNodes[0]);onContentChanged();}
var segmentInfoTimeout=null;function updateSegmentInfoThrottled(){if(hasAttachment()){return resetSegmentInfo();}
if(segmentInfoTimeout===null){segmentInfoTimeout=setTimeout(updateSegmentInfo,UPDATE_DELAY);}}
function updateSegmentInfo(){segmentInfoTimeout=null;var value=Compose.getText();var segmentInfoPromise=value?MessageManager.getSegmentInfo(value):Promise.reject();segmentInfoPromise.then(function(segmentInfo){state.segmentInfo=segmentInfo;},resetSegmentInfo).then(compose.updateType.bind(Compose)).then(compose.emit.bind(compose,'segmentinfochange'));}
function resetSegmentInfo(){state.segmentInfo={segments:0,charsAvailableInLastSegment:0};}
function disposeAttachmentNode(attachmentNode){var thumbnailURL=attachmentNode.dataset.thumbnail;if(thumbnailURL){window.URL.revokeObjectURL(thumbnailURL);}
attachments.delete(attachmentNode);}
var compose={init:function composeInit(formId){dom.form=document.getElementById(formId);dom.message=document.getElementById('messages-input');dom.sendButton=document.getElementById('messages-send-button');dom.attachButton=document.getElementById('messages-attach-button');dom.counter=dom.form.querySelector('.js-message-counter');dom.messagesAttach=dom.form.querySelector('.messages-attach-container');dom.composerButton=dom.form.querySelector('.composer-button-container');subject=new SubjectComposer(dom.form.querySelector('.js-subject-composer'));subject.on('change',onSubjectChanged);subject.on('visibility-change',onSubjectChanged);subject.on('visibility-change',onSubjectVisibilityChanged);dom.message.addEventListener('input',onContentChanged);dom.message.addEventListener('keydown',composeKeyEvents);dom.message.addEventListener('keypress',composeKeyEvents);dom.message.addEventListener('click',this.onAttachmentClick.bind(this));dom.attachButton.addEventListener('click',this.onAttachClick.bind(this));this.offAll();this.clear();this.on('type',this.onTypeChange.bind(this));this.on('type',this.updateMessageCounter.bind(this));this.on('segmentinfochange',this.updateMessageCounter.bind(this));var onInteracted=this.emit.bind(this,'interact');dom.message.addEventListener('focus',onInteracted);dom.message.addEventListener('click',onInteracted);dom.messagesAttach.addEventListener('click',onInteracted);dom.composerButton.addEventListener('click',onInteracted);subject.on('focus',onInteracted);return this;},getContent:function(){return getContentFromDOM(dom.message);},getSubject:function(){return subject.isVisible()?subject.getValue():null;},setSubject:function(value){return subject.setValue(value);},isSubjectMaxLength:function(){return subject.getValue().length>=subject.getMaxLength();},showSubject:function(){subject.show();subject.focus();},hideSubject:function(){subject.hide();},fromDraft:function(draft){this.clear();if(!draft){return;}
if(draft.subject){this.setSubject(draft.subject);subject.show();}
if(!draft.content){return;}
if(!Array.isArray(draft.content)){throw new Error('If present, the `content` property must be an array.');}
draft.content.forEach(function(fragment){if(fragment.blob){fragment=new Attachment(fragment.blob,{name:fragment.name,isDraft:true});}
Compose.append(fragment);},Compose);},getText:function(){var out=this.getContent().filter(function(elem){return(typeof elem==='string');});return out.join('');},isEmpty:function(){return state.empty;},setupLock({canSend,canEdit,forceType}){state.lock.canSend=canSend;state.lock.canEdit=canEdit;state.lock.forceType=forceType;},refresh(){this.updateType();this.updateSendButton();this.updateAttachButton();},scrollToTarget:function(target){var targetRect=target.getBoundingClientRect();var containerRect=dom.message.getBoundingClientRect();var offset=(targetRect.top+targetRect.height/2)-
(containerRect.top+containerRect.height/2);dom.message.scrollTop+=offset;},scrollMessageContent:function(){if(document.activeElement===dom.message){var range=window.getSelection().getRangeAt(0).cloneRange();if(range.collapsed){range.insertNode(document.createElement('span'));this.scrollToTarget(range);range.deleteContents();}else{this.scrollToTarget(range);}
state.lastScrollPosition=dom.message.scrollTop;}else{dom.message.scrollTop=state.lastScrollPosition;}},prepend:function(item){var fragment=insert(item);if(!fragment){return this;}
if(dom.message.firstChild&&dom.message.firstChild.nodeName==='BR'){dom.message.insertBefore(fragment,dom.message.childNodes[1]);}else{dom.message.insertBefore(fragment,dom.message.childNodes[0]);}
onContentChanged(item);return this;},append:function(item,options){options=options||{};if(Array.isArray(item)){var containsImage=false;item.forEach(function(content){if(content.type==='img'){containsImage=true;}
this.append(content,{ignoreChange:true});},this);onContentChanged({containsImage:containsImage});}else{var fragment=insert(item);if(!fragment){return this;}
if(document.activeElement===dom.message){var range=window.getSelection().getRangeAt(0);var firstNodes=fragment.firstChild;range.deleteContents();range.insertNode(fragment);this.scrollToTarget(range);dom.message.focus();range.setStartAfter(firstNodes);}else{dom.message.insertBefore(fragment,dom.message.lastChild);this.scrollToTarget(dom.message.lastChild);}
if(!options.ignoreChange){onContentChanged(item);}}
return this;},clear:function(){state.type='sms';this.onTypeChange();attachments.forEach((attachment,node)=>disposeAttachmentNode(node));dom.message.innerHTML='<br>';subject.reset();state.resizing=false;state.size=0;resetSegmentInfo();segmentInfoTimeout=null;onContentChanged();return this;},focus:function(){dom.message.focus();var selection=window.getSelection();var range=document.createRange();var lastChild=dom.message.lastChild;if(lastChild.tagName==='BR'){range.setStartBefore(lastChild);}else{range.setStartAfter(lastChild);}
selection.removeAllRanges();selection.addRange(range);return this;},updateType:function(){var newType=this.getType();if(newType!==state.type){state.type=newType;this.emit('type');}},getType(){var typeForcedByLock=state.lock.forceType&&state.lock.forceType();if(typeForcedByLock){return typeForcedByLock;}
var isTextTooLong=state.segmentInfo.segments>Settings.maxConcatenatedMessages;return isTextTooLong||hasSubject()||hasAttachment()?'mms':'sms';},updateEmptyState:function(){state.empty=state.emptyMessage&&!hasSubject();},updateSendButton:function(){dom.sendButton.disabled=!this.canSend();},updateAttachButton:function(){dom.attachButton.disabled=!this.canEdit();},canSend(){var isSendAllowedByLock=!state.lock.canSend||state.lock.canSend();var sizeExceedsLimit=this.type==='mms'&&Settings.mmsSizeLimitation&&this.size>Settings.mmsSizeLimitation;return isSendAllowedByLock&&!sizeExceedsLimit&&!this.isEmpty()&&!this.isResizing;},canEdit(){var isEditAllowedByLock=!state.lock.canEdit||state.lock.canEdit();var sizeExceedsOrEqualToLimit=this.type==='mms'&&Settings.mmsSizeLimitation&&this.size>=Settings.mmsSizeLimitation;return isEditAllowedByLock&&!sizeExceedsOrEqualToLimit;},_onAttachmentRequestError:function c_onAttachmentRequestError(err){var errId=err instanceof DOMError?err.name:err.message;if(errId==='file too large'){Utils.alert({id:'attached-files-too-large',args:{n:1,mmsSize:(Settings.mmsSizeLimitation/1024).toFixed(0)}});}else if(errId!=='ActivityCanceled'&&errId!=='pick cancelled'){console.warn('Unhandled error: ',err);}},onAttachClick:function c_onAttachClick(event){this.requestAttachment().then(this.append.bind(this),this._onAttachmentRequestError);},onAttachmentClick:function c_onAttachmentClick(event){if(!event.target.classList.contains(attachmentClass)||state.resizing){return;}
var currentAttachmentDOM=event.target;var currentAttachment=attachments.get(event.target);var name=currentAttachment.name;var blob=currentAttachment.blob;var options={header:name.substr(name.lastIndexOf('/')+1)};var mimeFirstPart=blob.type.substr(0,blob.type.indexOf('/'));var fileType=(TYPES.indexOf(mimeFirstPart)>-1)?mimeFirstPart:'other';var onView=()=>{currentAttachment.view();};function onRemove(){dom.message.removeChild(currentAttachmentDOM);disposeAttachmentNode(currentAttachmentDOM);state.size=null;onContentChanged();}
var onReplace=()=>{this.requestAttachment().then((newAttachment)=>{var fragment=insert(newAttachment);dom.message.insertBefore(fragment,currentAttachmentDOM);dom.message.removeChild(currentAttachmentDOM);disposeAttachmentNode(currentAttachmentDOM);onContentChanged(newAttachment);},this._onAttachmentRequestError);};options.items=[{l10nId:`view-attachment-${fileType}`,method:onView},{l10nId:`remove-attachment-${fileType}`,method:onRemove},{l10nId:`replace-attachment-${fileType}`,method:onReplace},{l10nId:'cancel'}];new OptionMenu(options).show();},onTypeChange:function c_onTypeChange(){if(this.type==='sms'){dom.message.setAttribute('x-inputmode','-moz-sms');}else{dom.message.removeAttribute('x-inputmode');}
dom.form.dataset.messageType=this.type;},updateMessageCounter:function c_updateMessageCounter(){var counterValue='';if(this.type==='sms'){var segments=state.segmentInfo.segments;var availableChars=state.segmentInfo.charsAvailableInLastSegment;if(segments&&(segments>1||availableChars<=MIN_AVAILABLE_CHARS_COUNT)){counterValue=availableChars+'/'+segments;}}
if(counterValue!==dom.counter.textContent){dom.counter.textContent=counterValue;}},requestAttachment:function(){var activityData={type:['image/*','audio/*','video/*','text/vcard']};var activity;var defer=Utils.Promise.defer();if(Settings.mmsSizeLimitation){activityData.maxFileSizeBytes=Settings.mmsSizeLimitation;}
activity=new MozActivity({name:'pick',data:activityData});activity.onsuccess=function(){var result=activity.result;if(Settings.mmsSizeLimitation&&result.blob.size>Settings.mmsSizeLimitation&&Utils.typeFromMimeType(result.blob.type)!=='img'){defer.reject(new Error('file too large'));}
defer.resolve(new Attachment(result.blob,{name:result.name,isDraft:true}));};activity.onerror=()=>{defer.reject(activity.error);};return defer.promise;}};Object.defineProperty(compose,'type',{get:function composeGetType(){return state.type;}});Object.defineProperty(compose,'size',{get:function composeGetSize(){if(state.size===null){state.size=this.getContent().reduce(function(sum,content){if(typeof content==='string'){return sum+content.length;}else{return sum+content.size;}},0);}
return state.size;}});Object.defineProperty(compose,'segmentInfo',{get:function composeGetSegmentInfo(){return state.segmentInfo;}});Object.defineProperty(compose,'isResizing',{get:function composeGetResizeState(){return state.resizing;}});Object.defineProperty(compose,'isSubjectVisible',{get:function composeIsSubjectVisible(){return subject.isVisible();}});return EventDispatcher.mixin(compose,['input','type','segmentinfochange','interact','subject-change']);}());

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,582 @@
/*global ContactRenderer,
Contacts,
ConversationView,
MessageManager,
MobileOperator,
Navigation,
Settings,
Template,
Threads,
Utils
*/
/*exported Information */
(function(exports) {
'use strict';
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const MONTH = 30 * DAY;
const LATENESS_DISPLAY_LOWER_LIMIT = 5 * MINUTE;
/*
Information view module is a subpanel belongs to TheadUI panel. This
module provide some default method for thiw view module:
show: Reuse the conversation view for information view. Hiding unrelated icon,
reset the header, and render container for showing the view.
reset: Hide the view container, clean up the contact list and resume the
icons/header for ConversationView.
renderContactList(participants, options): Rendering group-view style contact
list inside the page. Participants is an array of contact number and option
is for setting additional information for rendering other lock for contact
information.
*/
var TMPL = function createTemplate(tmpls) {
for (var key in tmpls) {
tmpls[key] = Template(tmpls[key]);
}
return tmpls;
}({
number: 'messages-number-tmpl',
report: 'information-report-tmpl'
});
/*
Summarized single status based on delivery and read status for mms.
The 1st level properties represents delivery status and 2nd level properties
represents read status.
*/
const REPORT_MAP = {
'not-applicable': {
'not-applicable': 'not-applicable',
'pending' : 'pending',
'success' : 'read',
'error' : 'error'
},
'pending' : {
'not-applicable': 'pending',
'pending': 'pending',
'success' : 'read', // should not possible
'error' : 'error'
},
'success' : {
'not-applicable': 'delivered',
'pending': 'delivered',
'success' : 'read',
'error' : 'error'
},
'error' : {
'not-applicable': 'error',
'pending': 'error',
'success' : 'error', // should not possible
'error' : 'error' // should not possible
}
};
// Register the message events we wanted for report view refresh
const MESSAGE_EVENTS = [
'message-failed-to-send',
'message-delivered',
'message-read',
'message-sent',
'message-sending'
];
const reportDateTimeFormatOptions = {
month: 'long',
day: '2-digit',
year: 'numeric',
hour: 'numeric',
minute: 'numeric'
};
function completeLocaleFormat(timestamp) {
var options = Object.assign(
{ hour12: navigator.mozHour12 },
reportDateTimeFormatOptions);
return new Date(+timestamp).toLocaleString(navigator.languages, options);
}
function l10nContainsDateSetup(element, timestamp) {
element.dataset.l10nDate = timestamp;
element.dataset.l10nDateFormat = JSON.stringify(reportDateTimeFormatOptions);
element.textContent = completeLocaleFormat(timestamp);
}
/**
* Generate report Div contains delivery report and read report for showing
* report information within contact list
*/
function createReportDiv(reports) {
var reportDiv = document.createElement('div');
reportDiv.className = 'network-status';
var data = {
titleL10n: '',
reportDateL10n: '',
messageL10nDateFormat: JSON.stringify(reportDateTimeFormatOptions),
timestamp: ''
};
var status;
var deliveryStatus = reports.deliveryStatus;
var readStatus = reports.readStatus;
if (!readStatus) { // sms
status = deliveryStatus === 'success' ?
'delivered' :
deliveryStatus;
} else if (deliveryStatus === 'rejected') {
// Status = 'rejected' when receiver is not allowed to download any mms
status = 'rejected';
} else if (deliveryStatus in REPORT_MAP) {
status = REPORT_MAP[deliveryStatus][readStatus];
} else {
console.error('Invalid message report status: ' + deliveryStatus);
return reportDiv;
}
reportDiv.dataset.deliveryStatus = status;
switch (status) {
case 'not-applicable':
return reportDiv;
case 'delivered':
data.timestamp = '' + reports.deliveryTimestamp;
data.reportDateL10n = completeLocaleFormat(reports.deliveryTimestamp);
break;
case 'read':
data.timestamp = '' + reports.readTimestamp;
data.reportDateL10n = completeLocaleFormat(reports.readTimestamp);
break;
}
data.titleL10n = 'report-status-' + status;
reportDiv.innerHTML = TMPL.report.interpolate(data);
return reportDiv;
}
function showSimInfo(element, iccId) {
var iccManager = navigator.mozIccManager;
// Hide the element when single SIM or no iccManager/mobileConnections
if (!(Settings.hasSeveralSim() && iccId && iccManager)) {
return;
}
var simInfoElement = element.querySelector('.sim-detail');
var simId = Settings.getServiceIdByIccId(iccId);
if (simId === null) {
document.l10n.setAttributes(
simInfoElement,
'dsds-unknown-sim'
);
} else {
var operator = getOperatorByServiceId(simId);
var icc = iccManager.getIccById(iccId);
var number = icc && icc.iccInfo.msisdn;
var data = {};
var l10nId;
var info = [operator, number].filter(function(value) {
return value;
});
var detailString = info.join(', ');
l10nId = info.length ? 'sim-detail' : 'sim-id-label';
data = { id: simId + 1, detailString: detailString };
document.l10n.setAttributes(
simInfoElement,
l10nId,
data
);
}
element.classList.remove('hide');
}
/**
* Incoming message: return array of sender number string;
* Outgoing message: return array of object(number and report div block).
*/
function createListWithMsgInfo(message) {
var list = [];
if (message.delivery === 'received' ||
message.delivery === 'not-downloaded') { // received message
if (message.type === 'mms') {
if (message.receivers) {
list.push(...message.receivers);
}
if (!list.includes(message.sender)) {
list.push(message.sender);
}
} else {
list.push(message.sender);
}
} else if (message.type === 'mms') { // sent mms message
message.deliveryInfo.forEach(function(info) {
list.push({number: info.receiver,
infoBlock: createReportDiv(info)});
});
} else { // sent sms message
var info = {};
info.deliveryStatus = message.deliveryStatus;
info.deliveryTimestamp = message.deliveryTimestamp;
list.push({number: message.receiver,
infoBlock: createReportDiv(info)});
}
return list;
}
/**
* Will return operator name depending on the iccId.
* Will return the empty string in a single SIM scenario.
*/
function getOperatorByServiceId(index) {
var conn = navigator.mozMobileConnections[index];
return MobileOperator.userFacingInfo(conn).operator;
}
var VIEWS = {
group: {
name: 'group-view',
selector: '.panel-GroupView',
contactFlavor: 'group-view',
render: function renderGroup() {
var participants = Threads.get(this.id).participants;
this.renderContactList(participants);
document.l10n.setAttributes(
this.headerText, 'participant', { n:participants.length }
);
},
setEventListener: function setEventListener() {
this.contactList.addEventListener('click', (event) => {
event.stopPropagation();
event.preventDefault();
var target = event.target;
ConversationView.promptContact({
number: target.dataset.number
}).then(
() => Navigation.toPanel('thread', { id: this.id })
);
});
},
elements: ['contact-list', 'header', 'header-text']
},
report: {
name: 'report-view',
selector: '.panel-ReportView',
contactFlavor: 'report-view',
init: function() {
this.onStatusChanged = this.onStatusChanged.bind(this);
},
beforeEnter: function() {
this.constructor.prototype.beforeEnter.apply(this, arguments);
MESSAGE_EVENTS.forEach((event) => {
MessageManager.on(event, this.onStatusChanged);
});
},
afterLeave: function() {
this.constructor.prototype.afterLeave.apply(this, arguments);
MESSAGE_EVENTS.forEach((event) => {
MessageManager.off(event, this.onStatusChanged);
});
},
render: function renderReport() {
var setL10nAttributes = document.l10n.setAttributes;
var request = MessageManager.getMessage(this.id);
// Hide these dynamic fields to avoid incorrect info displayed at first
this.subject.classList.add('hide');
this.sizeBlock.classList.add('hide');
request.onsuccess = (function() {
var message = request.result;
var type = message.type;
var delivery = message.delivery;
var isIncoming = delivery === 'received' ||
delivery === 'not-downloaded';
// Fill in the description/status/size
if (type === 'sms') {
setL10nAttributes(this.type, 'message-type-sms');
} else { //mms
setL10nAttributes(this.type, 'message-type-mms');
// subject text content
var subject = message.subject;
this.subject.classList.toggle('hide', !subject);
if (subject) {
this.subject.querySelector('.detail').textContent = subject;
}
// Message total size show/hide
if (message.attachments && message.attachments.length > 0) {
var size = message.attachments.reduce(function(size, attachment) {
return (size += attachment.content.size);
}, 0);
var params = Utils.getSizeForL10n(size);
setL10nAttributes(this.size, params.l10nId, params.l10nArgs);
this.sizeBlock.classList.remove('hide');
}
}
this.container.dataset.delivery = delivery;
this.container.classList.toggle('incoming-message', isIncoming);
this.container.classList.toggle('outgoing-message', !isIncoming);
// If incoming message is migrated from the database where sentTimestamp
// hadn't been supported yet then we won't have valid value for it.
this.container.classList.toggle(
'no-valid-sent-timestamp',
isIncoming && !message.sentTimestamp
);
setL10nAttributes(
this.contactTitle,
isIncoming ? 'report-from-title' : 'report-to-title'
);
if (isIncoming) {
l10nContainsDateSetup(this.receivedTimestamp, message.timestamp);
l10nContainsDateSetup(this.sentTimestamp, message.sentTimestamp);
setL10nAttributes(this.sentTitle, 'message-sent');
this.renderLateArrivalMsg(message);
} else {
if (delivery === 'sending' || delivery === 'sent') {
setL10nAttributes(this.sentTitle, 'message-' + delivery);
}
if (delivery === 'error' || delivery === 'sent') {
l10nContainsDateSetup(this.sentTimestamp, message.timestamp);
}
}
//show sim information for dual sim device
showSimInfo(this.simInfo, message.iccId);
// Filled in the contact list. Only outgoing message contains detailed
// report information.
this.renderContactList(createListWithMsgInfo(message));
}).bind(this);
},
renderLateArrivalMsg: function report_renderLateArrivalMsg(message) {
var latenessBlock = this.panel.querySelector('.lateness-block');
var lateness = message.timestamp - message.sentTimestamp;
var showLatenessNotice = message.timestamp &&
message.sentTimestamp &&
lateness >= LATENESS_DISPLAY_LOWER_LIMIT;
if (showLatenessNotice) {
var units = [
{ unit: 'month', unitVal: MONTH },
{ unit: 'day', unitVal: DAY },
{ unit: 'hour', unitVal: HOUR },
{ unit: 'minute', unitVal: MINUTE },
{ unit: 'second', unitVal: SECOND }
];
units.forEach(({unit, unitVal}) => {
var output;
var unitElement = this.panel.querySelector(`.lateness-${unit}`);
if (lateness >= unitVal) {
// Compute integer value for the current unit output
// and get the rest for the next smaller unit
output = Math.floor(lateness / unitVal);
lateness = lateness % unitVal;
document.l10n.setAttributes(
unitElement,
`duration-${unit}-narrow`,
{ value: output }
);
unitElement.classList.remove('hide');
} else {
unitElement.classList.add('hide');
}
});
latenessBlock.classList.remove('hide');
} else {
latenessBlock.classList.add('hide');
}
},
// Set this flag to true only when resend is triggered.
messageResending: false,
setEventListener: function report_setEventListener() {
this.resendBtn.addEventListener('click', () => {
this.messageResending = true;
ConversationView.resendMessage(this.id);
});
},
isReportForMessage: function report_isReportForMessage(id) {
return Navigation.isCurrentPanel('report-view', { id: id }) ||
(Navigation.isCurrentPanel('report-view') &&
this.id === id);
},
onStatusChanged: function report_onStatusChanged(e) {
// If we got sending status change in report view after resend clicked
// (messageResending is true), we should change report panel id, reset
// messageResending flag and refresh for new message report.
if (e.message.delivery === 'sending' && this.messageResending) {
this.id = e.message.id;
this.messageResending = false;
}
this.isReportForMessage(e.message.id) && this.refresh();
},
elements: ['contact-list', 'size', 'size-block', 'type', 'sent-title',
'sent-timestamp', 'received-timestamp', 'subject', 'sim-info',
'contact-title', 'resend-btn', 'header', 'container'
]
}
};
var Information = function(type) {
Object.assign(this, VIEWS[type]);
if (this.init) {
this.init();
}
this.panel = document.querySelector(this.selector);
this.elements.forEach(function(name) {
this[Utils.camelCase(name)] = this.panel.querySelector('.' + name);
}, this);
this.header.addEventListener(
'action', this.backOrClose.bind(this)
);
this.setEventListener && this.setEventListener();
this.reset();
};
Information.prototype = {
constructor: Information,
beforeEnter: function(args) {
this.id = +args.id;
this.header.removeAttribute('no-font-fit');
this.show();
},
afterLeave: function() {
this.reset();
this.id = null;
},
show: function() {
this.render();
},
refresh: function() {
if (Navigation.isCurrentPanel(this.name)) {
this.render();
}
},
reset: function() {
// Remove all LIs
if (this.contactList) {
this.contactList.textContent = '';
}
},
backOrClose: function() {
Navigation.back();
},
// Incrementing ID for each rendering request to avoid possible race when next
// renderContactList request earlier than list item appended to contact list
// ul. Ignoring the rendering/appending request if the rendering ID doesn't
// match the latest rendering ID.
renderingId: 0,
// Param participants could be:
// - Array of contact number string or
// - Array of object({ number: number, infoBlock: infoBlock })
// for rendering the contact list.
renderContactList: function(participants) {
var ul = this.contactList;
var renderer = ContactRenderer.flavor(this.contactFlavor);
var currentRenderingId = ++this.renderingId;
ul.textContent = '';
participants.forEach((participant) => {
var number, infoBlock, selector;
if (typeof participant === 'object') {
number = participant.number;
infoBlock = participant.infoBlock;
selector = '.js-contact-info';
} else {
number = participant;
}
Contacts.findByAddress(number).then((contacts) => {
// If the current rendering ID doesn't match the latest ID, skip current
// one and only render for ID which matches latest request ID.
if (currentRenderingId !== this.renderingId) {
return;
}
if (contacts.length > 0) {
renderer.render({
contact: contacts[0],
input: number,
infoBlock: infoBlock,
infoBlockParentSelector: selector,
target: ul
});
} else {
var li = document.createElement('li');
li.role = 'presentation';
li.innerHTML = TMPL.number.interpolate({
number: number
});
var parentBlock = li.querySelector(selector);
if (parentBlock && infoBlock) {
parentBlock.appendChild(infoBlock);
}
ul.appendChild(li);
}
});
});
}
};
Information.initDefaultViews = function() {
// Create group / report information view
exports.GroupView = new Information('group');
exports.ReportView = new Information('report');
};
exports.Information = Information;
// end global closure
}(this));

View file

@ -0,0 +1,49 @@
/*global ActivityPicker, ConversationView */
(function(exports) {
'use strict';
/*
Centralized event handling for various
data-actions url, email, phone in a message
*/
var LinkActionHandler = {
onClick: function lah_onClick(event) {
event.preventDefault();
event.stopPropagation();
var dataset = event.target.dataset;
var action = dataset.action;
if (!action) {
return;
}
if (action === 'email-link') {
ConversationView.prompt({
email: dataset.email,
inMessage: true
});
}
if (action === 'dial-link') {
ConversationView.promptContact({
number: dataset.dial,
inMessage: true
});
}
if (action === 'url-link') {
var type = action.replace('-link', '');
ActivityPicker[type](
dataset[type], this.reset, this.reset
);
}
}
};
exports.LinkActionHandler = LinkActionHandler;
}(this));

View file

@ -0,0 +1,18 @@
;(function(){'use strict';var KNOWN_TLDS=['aero','asia','biz','cat','com','coop','info','int','jobs','mobi','museum','name','net','org','post','pro','tel','travel','xxx','edu','gov','mil','nyc','ac','ad','ae','af','ag','ai','al','am','an','ao','aq','ar','as','at','au','aw','ax','az','ba','bb','bd','be','bf','bg','bh','bi','bj','bm','bn','bo','br','bs','bt','bv','no','bw','by','bz','ca','cc','cd','cf','cg','ch','ci','ck','cl','cm','cn','co','cr','cs','cu','cv','cx','cy','cz','dd','de','dj','dk','dm','do','dz','ec','ee','eg','eh','er','es','et','eu','fi','fj','fk','fm','fo','fr','ga','gb','gd','ge','gf','gg','gh','gi','gl','gm','gn','gp','gq','gr','gs','gt','gu','gw','gy','hk','hm','hn','hr','ht','hu','id','ie','il','im','in','io','iq','ir','is','it','je','jm','jo','jp','ke','kg','kh','ki','km','kn','kp','kr','kw','ky','kz','la','lb','lc','li','lk','lr','ls','lt','lu','lv','ly','ma','mc','md','me','mg','mh','mk','ml','mm','mn','mn','mo','mp','mq','mr','ms','mt','mu','mv','mw','mx','my','mz','na','nc','ne','nf','ng','ni','nl','no','np','nr','nu','nz','om','pa','pe','pf','pg','ph','pk','pl','pm','pn','pr','ps','pt','pw','py','qa','re','ro','rs','ru','su','рф','rw','sa','sb','sc','sd','se','sg','sh','si','sj','no','sk','sl','sm','sn','so','sr','ss','st','su','sv','sx','sy','sz','tc','td','tf','tg','th','tj','tk','tl','tp','tm','tn','to','tp','tl','tr','tt','tv','tw','tz','ua','ug','uk','us','gov','uy','uz','va','vc','ve','vg','vi','vn','vu','wf','ws','ye','yt','yu','za','zm','zw'];var ipv4part='(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)';var ipv4RegExp=new RegExp('^(?:'+ipv4part+'\\.){3}'+ipv4part+'$');var leadingDigitsRegExp=/^[+0-9]+/;function checkDomain(domain){if(ipv4RegExp.test(domain)){return true;}else{var parts=domain.split('.');var lastPart=parts[parts.length-1];return lastPart.length>1&&!isFinite(lastPart);}}
var safeStart=/[\s.,:;\(>\u0080-\uFFFF]/;const MINIMUM_DIGITS_IN_PHONE_NUMBER=5;const LEADING_DIGIT_BREAKPOINT=7;var LINK_TYPES={phone:{regexp:new RegExp(['(?:\\+\\d{1,4}[ \\t.()-]{0,3}|\\()?'+'(?:\\d{1,4}[ \\t.()-]{0,3})?'+'(?:\\d[\\d \\t.()-]{0,12}\\d)'+'\\b'].join(''),'g'),matchFilter:function phoneMatchFilter(phone,link){var onlyDigits=Utils.removeNonDialables(phone);if(onlyDigits.length<MINIMUM_DIGITS_IN_PHONE_NUMBER){return false;}
if(onlyDigits.length===MINIMUM_DIGITS_IN_PHONE_NUMBER&&phone.length!==onlyDigits.length){return false;}
var leadingDigits=leadingDigitsRegExp.exec(phone);if(leadingDigits&&leadingDigits[0].length>=LEADING_DIGIT_BREAKPOINT){link.end=link.start+leadingDigits[0].length;}
return link;},transform:function phoneTransform(phone,link){return'<a dir="ltr" data-dial="'+phone+'" data-action="dial-link">'+phone+'</a>';}},url:{regexp:new RegExp(['(https?://|rtsp://)?','(\\w(?:\\.?[-\\w]){0,255})','(\\.\\w{1,10})','(?::[0-9]{1,5})?','(?:\\.?/','(?:[^\\s.,?:;!]|[.,?:;!](?!\\s|$)){0,2048}',')?'].join(''),'mgi'),matchFilter:function urlMatchFilter(url,linkSpec){var match=linkSpec.match;var scheme,tld;if(!checkDomain(match[2]+match[3])){return false;}
scheme=match[1];tld=match[3]&&match[3].slice(1);if(!scheme&&(!isFinite(tld)&&KNOWN_TLDS.indexOf(tld)===-1)){return false;}
if(url.slice(-1)===')'&&url.indexOf('(')===-1){linkSpec.end--;}
return linkSpec;},transform:function urlTransform(url,linkSpec){var href=url;if(!linkSpec.match[1]){href='http://'+href;}
return'<a data-url="'+href+'" data-action="url-link" >'+url+'</a>';}},email:{regexp:/[\w.+-]+@[\w.-]+\.[a-z.]{2,6}/mgi,transform:function emailTransform(email){return['<a data-email="','" data-action="email-link">','</a>'].join(email);}}};var LINK_TYPES_KEYS=Object.keys(LINK_TYPES);function searchForLinks(type,string){var linkSpecs=[];var spec=LINK_TYPES[type];if(!spec){return linkSpecs;}
var regexp=spec.regexp;var matchFilter=spec.matchFilter;var match,linkSpec;while((match=regexp.exec(string))){var rest=string.slice(match.index-1);if(match.index&&!safeStart.test(rest.charAt(0))){var nextSafe=safeStart.exec(rest);if(nextSafe){regexp.lastIndex=match.index+nextSafe.index;}
continue;}
linkSpec={type:type,start:match.index,length:match[0].length,end:match.index+match[0].length,match:match};if(matchFilter){linkSpec=matchFilter(match[0],linkSpec);}
if(linkSpec){linkSpecs.push(linkSpec);regexp.lastIndex=linkSpec.end;}}
return linkSpecs;}
function linkSort(a,b){return(a.start-b.start)||(b.end-a.end)||(LINK_TYPES_KEYS.indexOf(a.type)-LINK_TYPES_KEYS.indexOf(b.type));}
function removeOverlapping(linkSpecs){linkSpecs.sort(linkSort);for(var index=0;index<linkSpecs.length-1;index++){var end=linkSpecs[index].end;while(linkSpecs[index+1]&&linkSpecs[index+1].start<end){linkSpecs.splice(index+1,1);}}}
var LinkHelper=window.LinkHelper={searchAndLinkUrl:function lh_searchAndLinkUrl(inputText){return LinkHelper.searchAndLinkClickableData(inputText,{url:true});},searchAndLinkEmail:function lh_searchAndLinkEmail(inputText){return LinkHelper.searchAndLinkClickableData(inputText,{email:true});},searchAndLinkPhone:function lh_searchAndLinkPhone(inputText){return LinkHelper.searchAndLinkClickableData(inputText,{phone:true});},searchAndLinkClickableData:function lh_searchAndLinkClickableData(inputText,mode){mode=mode||LINK_TYPES;var linkSpecs=[];var type;for(type in mode){if(mode[type]&&LINK_TYPES[type]){linkSpecs=linkSpecs.concat(searchForLinks(type,inputText));}}
removeOverlapping(linkSpecs);var result='';var lastEnd=0;linkSpecs.forEach(function replaceLink(link){result+=inputText.slice(lastEnd,link.start);var replacing=inputText.slice(link.start,link.end);var replaceWith=LINK_TYPES[link.type].transform(replacing,link);result+=replaceWith;lastEnd=link.end;});result+=inputText.slice(lastEnd);return result;}};})();

View file

@ -0,0 +1,55 @@
;(function(exports){'use strict';var view=new WeakMap();var priv=new WeakMap();var data=new WeakMap();var events=new WeakMap();var relation=new WeakMap();var rdigit=/^\d/;function Recipient(opts){var number;opts=opts||{};this.name=String(opts.name||opts.number||'');this.number=(opts.number||'')+'';this.email=opts.email||'';this.editable=opts.editable||'true';this.role=this.editable?'textbox':'button';this.source=opts.source||'manual';this.type=opts.type||'';this.carrier=opts.carrier||'';this.className='recipient';this.isEmail=Settings.supportEmailRecipient&&Utils.isEmailAddress(this.number);this.isLookupable=opts.isLookupable||false;this.isInvalid=opts.isInvalid||false;this.isQuestionable=opts.isQuestionable||false;number=this.number[0]==='+'?this.number.slice(1):this.number;if(this.isEmail){this.className+=' email';}else if(this.source==='manual'&&!rdigit.test(number)){this.isQuestionable=true;}
if(this.isQuestionable||this.isInvalid){this.className+=' invalid';}}
Recipient.prototype.set=function(dict){var length=Recipient.FIELDS.length;var key;for(var i=0;i<length;i++){key=Recipient.FIELDS[i];this[key]=dict[key];}
return this;};Recipient.prototype.clone=function(){return new Recipient(this);};Recipient.FIELDS=['name','number','email','editable','source','type','carrier','isQuestionable','isInvalid','isLookupable'];function Recipients(setup){if(!setup){throw new Error('`setup` missing');}
if(!setup.template){throw new Error('`setup.template` missing');}
if(!setup.outer){throw new Error('`setup.outer` missing');}
if(!setup.inner){throw new Error('`setup.inner` missing');}
var list=[];Object.defineProperties(this,{length:{get:function(){return list.length;},set:function(value){var oldLength=list.length;list.length=value;if(value<oldLength){this.render();}
return value;}},list:{get:function(){return list.slice();}},numbers:{get:function(){return list.reduce(function(unique,recipient){var value=recipient.number||recipient.email;if(!recipient.isInvalid&&unique.indexOf(value)===-1){unique.push(value);}
return unique;},[]);}},inputValue:{get:function(){var node=document.getElementById(setup.inner).lastElementChild;return(node&&node.isPlaceholder)?node.textContent.trim():'';}}});data.set(this,list);events.set(this,{add:[],remove:[],modechange:[]});view.set(this,new Recipients.View(this,setup));}
Recipients.prototype.on=function(type,handler){var handlers=events.get(this);if(!handlers[type]){throw new Error('Invalid event type: '+type);}
handlers[type].push(handler);return this;};Recipients.prototype.off=function(type,handler){var handlers=events.get(this);if(!handlers[type]){throw new Error('Invalid event type: '+type);}
if(!handler){handlers[type].length=0;}else{handlers[type].splice(handlers[type].indexOf(handler),1);}
return this;};Recipients.prototype.emit=function(type){var handlers=events.get(this);var args=[].slice.call(arguments,1);var handler,stack;if(!handlers[type]){throw new Error('Invalid event type: '+type);}
stack=handlers[type].slice();if(stack.length){while((handler=stack.pop())){handler.apply(null,args);}}
return this;};Recipients.prototype.add=function(entry){var list=data.get(this);var added;if(entry.number===undefined){throw new Error('recipient entry missing number');}
entry.editable=entry.editable||'false';['name','number','email'].forEach(function(field){if(entry[field]){entry[field]=(entry[field]+'').trim();}});list.push(added=new Recipient(entry));this.emit('add',list.length,added.clone());this.render();return this;};Recipients.prototype.update=function(recipOrIndex,entry){var list=data.get(this);var index=typeof recipOrIndex==='number'?recipOrIndex:list.indexOf(recipOrIndex);if(index>-1){list[index].set(new Recipient(entry));}
return this.render();};Recipients.prototype.remove=function(recipOrIndex){var list=data.get(this);var index=typeof recipOrIndex==='number'?recipOrIndex:list.indexOf(recipOrIndex);if(index===-1){return this;}
list.splice(index,1);this.emit('remove',list.length);return this.render(index);};Recipients.prototype.render=function(){view.get(this).reset();return this;};Recipients.prototype.focus=function(){view.get(this).focus();return this;};Recipients.prototype.visible=function(type,opts){view.get(this).visible(type,opts||{});return this;};Recipients.View=function(owner,setup){var inner=document.getElementById(setup.inner);var outer=document.getElementById(setup.outer);var template=setup.template;var nodes=[];var clone;var outerCss=window.getComputedStyle(outer);priv.set(this,{owner:owner,inner:inner,outer:outer,template:template,active:null,nodes:nodes,state:{isTransitioning:false,visible:'singleline'},minHeight:parseInt(outerCss.getPropertyValue('min-height'),10),mode:'singleline-mode'});clone=inner.cloneNode(true);clone.innerHTML=template.interpolate(new Recipient());clone.firstElementChild.innerHTML='';Object.defineProperties(this,{last:{get:function(){return nodes[nodes.length-1];}},placeholder:{get:function(){var node=clone.firstElementChild.cloneNode(true);node.isPlaceholder=true;return node;}}});this.updateMode=Utils.debounce(this.updateMode,100);['click','keypress','keyup','blur','pan'].forEach(function(type){outer.addEventListener(type,this,false);},this);new GestureDetector(outer).startDetecting();this.reset();};Recipients.View.isFocusable=true;Recipients.View.prototype.reset=function(){this.clear().render();return this;};Recipients.View.prototype.clear=function(){priv.get(this).inner.textContent='';return this;};Recipients.View.prototype.render=function(opts){var view=priv.get(this);var nodes=view.nodes;var inner=view.inner;var template=view.template;var list=view.owner.list;var length=list.length;var html='';opts=opts||{};opts.addPlaceholder=opts.addPlaceholder===undefined?true:opts.addPlaceholder;if(inner.textContent){this.clear();}
for(var i=0;i<length;i++){html+=template.interpolate(list[i]);}
if(opts.editable){opts.addPlaceholder=false;html+=template.interpolate(opts.editable);}
if(html){inner.innerHTML=html;}
if(opts.addPlaceholder){inner.appendChild(this.placeholder);}
nodes.length=0;if(!list.length){nodes.push.apply(nodes,inner.children[0]);}else{nodes.push.apply(nodes,inner.children);}
nodes.forEach(function forEachNode(node,i){node.isPlaceholder=false;node.contentEditable=false;node.setAttribute('role','button');if(i===nodes.length-1){node.isPlaceholder=true;node.contentEditable=true;node.setAttribute('role','textbox');}else{relation.set(node,list[i]);}});if(view.state.visible==='singleline'&&nodes.length){inner.querySelector(':last-child').scrollIntoView(false);}
this.updateMode();return this;};Recipients.View.prototype.focus=function(node){var view=priv.get(this);var range=document.createRange();var selection=window.getSelection();if(Recipients.View.isFocusable){if(!node){node=view.inner.lastElementChild;if(!node.isPlaceholder){node=view.inner.appendChild(this.placeholder);}}
if(node&&node.isPlaceholder){node.contentEditable=true;node.focus();}
range.selectNodeContents(node);range.collapse(false);selection.removeAllRanges();selection.addRange(range);view.inner.scrollTop=view.inner.scrollHeight;}
this.updateMode();return this;};Recipients.View.prototype.updateMode=function(){var view=priv.get(this);var mode=view.inner.scrollHeight>view.minHeight?'multiline-mode':'singleline-mode';if(view.mode!==mode){view.mode=mode;if(mode==='singleline-mode'&&view.state.visible==='multiline'){this.visible('singleline',{refocus:this});}
view.owner.emit('modechange',mode);}};var rtype=/^(multi|single)line$/;Recipients.View.prototype.visible=function(type,opts){var view=priv.get(this);var state=view.state;var error='visible "type" (multiline or singleline)';opts=opts||{};if(!type){throw new Error('Missing '+error);}
if(!rtype.test(type)){throw new Error('Invalid '+error);}
if(state.visible!==type){state.isTransitioning=false;}
if(state.isTransitioning){return this;}
if(state.visible===type){return this;}
view.outer.addEventListener('transitionend',function te(){this.removeEventListener('transitionend',te);var last=view.inner.lastElementChild;var previous;if(Navigation.isCurrentPanel('composer')&&state.visible==='singleline'){while(last!==null&&last.isPlaceholder){previous=last.previousElementSibling;if(!last.textContent){last.parentNode.removeChild(last);}
last=previous;}
if(opts.refocus){opts.refocus.focus();}}else if(state.visible==='multiline'&&last!==null){last.scrollIntoView(true);}
state.isTransitioning=false;});state.visible=type;state.isTransitioning=true;view.outer.className=type;view.inner.className=type;return this;};Recipients.View.prototype.handleEvent=function(event){var view=priv.get(this);var owner=view.owner;var isPreventingDefault=false;var isAcceptedRecipient=false;var isEdittingRecipient=false;var isDeletingRecipient=false;var isLookupable=false;var target=event.target;var keyCode=event.keyCode;var editable='false';var lastElement=view.inner.lastElementChild;var typed,recipient,length,previous;Recipients.View.isFocusable=true;if(event.type==='keypress'||event.type==='keyup'){typed=target.textContent.trim();length=typed.length;}
switch(event.type){case'pan':if(view.state.visible==='singleline'&&view.inner.scrollHeight>view.minHeight){if(event.detail.absolute.dy>0){this.visible('multiline');}
event.preventDefault();event.stopPropagation();return;}
break;case'click':if(target.parentNode===view.inner){if(target.isPlaceholder){return;}
typed=lastElement&&lastElement.isPlaceholder?lastElement.textContent.trim():'';if(typed){owner.add({name:typed,number:typed,source:'manual'}).focus();}
recipient=relation.get(target);if(recipient){if(recipient.source==='contacts'){Recipients.View.prompts.remove(recipient,function(isConfirmed){if(isConfirmed){owner.remove(relation.get(target));this.reset();}
Recipients.View.isFocusable=true;this.focus();}.bind(this));}else{isEdittingRecipient=true;typed=recipient.number||recipient.name;editable='true';owner.remove(recipient);target=view.inner.lastElementChild;target.textContent=typed;this.visible('singleline',{refocus:target});}}}else{if(view.state.visible!=='singleline'){this.visible('singleline',{refocus:this});}else{this.focus();}
return;}
break;case'keyup':if(typed&&(typed[length-1]===';'||typed[length-1]===',')){isAcceptedRecipient=true;isPreventingDefault=true;typed=typed.replace(/[;,]$/,'');target.textContent=typed;}
if(!isAcceptedRecipient&&(typed&&typed.length>=1)){this.visible('singleline',{refocus:target});}
if(!typed&&keyCode===event.DOM_VK_BACK_SPACE){isPreventingDefault=true;isDeletingRecipient=true;}
this.updateMode();break;case'keypress':if(keyCode===event.DOM_VK_BACK_SPACE){previous=target.previousSibling;if(!typed&&previous){recipient=relation.get(previous);if(previous.dataset.source==='contacts'){isPreventingDefault=true;isDeletingRecipient=true;view.owner.remove(recipient);}else if(previous.dataset.source==='manual'){isEdittingRecipient=true;isPreventingDefault=true;target=previous;view.inner.removeChild(view.inner.lastElementChild);data.get(view.owner).pop();view.owner.emit('remove',data.get(view.owner).length);}}}
if((keyCode===13||keyCode===event.DOM_VK_ENTER)||(keyCode===event.DOM_VK_TAB)){if(typed){isPreventingDefault=true;if(target.parentNode===view.inner){if(keyCode!==event.DOM_VK_TAB){isAcceptedRecipient=true;isLookupable=true;target.textContent=typed;}}}}
break;}
if(isAcceptedRecipient){if(typed){view.owner.add({name:typed,number:typed,editable:editable,source:'manual',role:editable?'textbox':'button',isLookupable:isLookupable});this.render().focus();}}
if(isDeletingRecipient){this.render().focus();}
if(isEdittingRecipient){target.contentEditable=true;target.setAttribute('role','textbox');target.isPlaceholder=true;this.focus(target);}
if(isPreventingDefault){event.preventDefault();}};Recipients.View.prompts={remove:function(recipient,callback){callback=typeof callback==='function'?callback:()=>{};var bodyTemplate=SharedComponents.phoneDetails({number:recipient.number,type:recipient.type,carrier:recipient.carrier});var title=document.createElement('bdi');title.textContent=recipient.name||recipient.number;Utils.confirm({raw:bodyTemplate.toDocumentFragment()},{raw:title},{text:'remove',className:'danger'}).then(()=>callback(true),()=>callback(false));Recipients.View.isFocusable=false;}};exports.Recipients=Recipients;}(this));

View file

@ -0,0 +1,4 @@
;(function(exports){'use strict';const LAZY_DEPENDENCIES=['/services/js/messaging/messaging_client.js','/services/js/moz_mobile_connections/moz_mobile_connections_client.js','/shared/js/settings_listener.js','/shared/js/mime_mapper.js','/shared/js/option_menu.js','/shared/js/mobile_operator.js','/shared/elements/gaia_sim_picker/script.js','/views/shared/js/waiting_screen.js','/views/shared/js/errors.js','/views/shared/js/dialog.js','/views/shared/js/error_dialog.js','/views/conversation/js/link_action_handler.js','/views/shared/js/contact_renderer.js','/views/shared/js/activity_picker.js','/views/conversation/js/information.js','/views/shared/js/localization_helper.js'];function initLazyDependencies(){return LazyLoader.load(LAZY_DEPENDENCIES).then(()=>{LocalizationHelper.init();TimeHeaders.init();Information.initDefaultViews();Settings.init();MessagingClient.init(App.instanceId);MozMobileConnectionsClient.init(App.instanceId);Navigation.setReady();InterInstanceEventDispatcher.connect();});}
function initShims(){exports.InboxView=Object.freeze({markReadUnread:()=>{},updateThread:()=>{}});var deferred=Utils.Promise.defer();MessageManager.getThreads({each:(thread)=>Threads.set(thread.id,thread),done:deferred.resolve});return deferred.promise;}
exports.Startup={init(){Utils.initializeShimHost(App.instanceId);MessageManager.init();ConversationView.init();if(Navigation.isDefaultPanel()){ConversationView.once('visually-loaded',initLazyDependencies);}else{initLazyDependencies();}
initShims().then(()=>Navigation.init());}};})(window);

View file

@ -0,0 +1,11 @@
;(function(exports){'use strict';const MAX_LENGTH=40;const privateMembers=new WeakMap();function onKeyDown(e){var priv=privateMembers.get(this);if(e.keyCode===e.DOM_VK_BACK_SPACE){if(!priv.isHoldingBackspace){priv.isEmptyOnBackspace=!this.getValue();priv.isHoldingBackspace=true;}}else{priv.isHoldingBackspace=false;if(this.getValue().length>=MAX_LENGTH||e.keyCode===e.DOM_VK_RETURN){e.preventDefault();e.stopPropagation();}}}
function onKeyUp(e){var priv=privateMembers.get(this);if(e.keyCode===e.DOM_VK_BACK_SPACE&&priv.isHoldingBackspace&&priv.isEmptyOnBackspace){this.hide();}
priv.isEmptyOnBackspace=false;priv.isHoldingBackspace=false;}
function onInput(e){privateMembers.get(this).updateValue(e.target.textContent);}
function updatePlaceholder(){var priv=privateMembers.get(this);if(priv.value){priv.placeholder.remove();}else{priv.node.appendChild(priv.placeholder);}}
function updateValue(newValue){var priv=privateMembers.get(this);if(newValue){newValue=newValue.replace(/\n\s*|\u00A0/g,' ');}
if(priv.value!==newValue){priv.value=newValue;priv.updatePlaceholder();this.emit('change');}
return newValue;}
var SubjectComposer=function(node){EventDispatcher.mixin(this,['focus','change','visibility-change']);if(!node){throw new Error('Subject node is required');}
var priv={isHoldingBackspace:false,isEmptyOnBackspace:false,isVisible:false,value:'',node:node,input:null,placeholder:null,updateValue:updateValue.bind(this),updatePlaceholder:updatePlaceholder.bind(this),};privateMembers.set(this,priv);priv.input=priv.node.querySelector('.subject-composer-input');priv.placeholder=priv.node.querySelector('.subject-composer-placeholder');priv.input.addEventListener('keydown',onKeyDown.bind(this));priv.input.addEventListener('keyup',onKeyUp.bind(this));priv.input.addEventListener('input',onInput.bind(this));priv.input.addEventListener('focus',this.emit.bind(this,'focus'));};SubjectComposer.prototype.show=function ms_show(){this.toggle(true);};SubjectComposer.prototype.hide=function ms_hide(){this.toggle(false);};SubjectComposer.prototype.toggle=function ms_toggle(toggle){var priv=privateMembers.get(this);priv.node.classList.toggle('hide',!toggle);priv.isVisible=toggle;this.emit('visibility-change');};SubjectComposer.prototype.isVisible=function ms_is_visible(){return privateMembers.get(this).isVisible;};SubjectComposer.prototype.focus=function ms_focus(){privateMembers.get(this).input.focus();};SubjectComposer.prototype.getMaxLength=function ms_get_maxLength(){return MAX_LENGTH;};SubjectComposer.prototype.getValue=function ms_get_value(){return privateMembers.get(this).value;};SubjectComposer.prototype.setValue=function ms_set_value(value){var priv=privateMembers.get(this);if(typeof value!=='string'){throw new Error('Value should be a valid string!');}
priv.input.textContent=priv.updateValue(value);};SubjectComposer.prototype.reset=function ms_reset(){var priv=privateMembers.get(this);priv.value=priv.input.textContent='';priv.node.classList.add('hide');priv.isVisible=false;priv.updatePlaceholder();};exports.SubjectComposer=SubjectComposer;})(window);

View file

@ -0,0 +1,214 @@
/**
* Attachment containers are either:
* - <div> elements, in the message thread;
* - <iframe> elements, in the Compose area (draft attachments).
* Both containers have an `attachment-container' class, but only <iframe>
* containers have an `attachment-draft' class.
*/
.attachment-container {
position: relative;
margin-bottom: 0.5rem;
display: block;
border: none;
height: 10rem;
width: 10rem;
-moz-user-select: none;
}
.attachment-draft {
margin: 0;
padding: 0;
font-family: sans-serif;
}
/* make sure all pointer events are sent to the main container */
.attachment-draft *,
.attachment-container * {
pointer-events: none;
}
.attachment-container.nopreview { /* 100x100 px placeholder + filename below */
height: 12.7rem; /* font-size = 1.7rem + 1rem margin */
}
/* Internal height of the iframe that contains the attachment draft */
.attachment-container.nopreview[data-attachment-type="vcard"] {
/* 40x40 px placeholder + filename below */
height: 6.7rem; /* font-size = 1.7rem + 1rem margin */
}
/* The internal height of the attachment itself */
.attachment[data-attachment-type="vcard"] {
height: 100%;
}
/* Allow to display the filename properly */
#messages-input > [data-attachment-type="vcard"] {
width: 20rem;
}
.article-list[data-type="list"] .message .attachment-container {
margin: 1rem 0;
}
/**
* Attachments that occur at the bounds of an MMS message do not need
* additional spacing.
*/
#messages-container[data-type="list"] .message .attachment-container:first-child {
margin-top: 0;
}
#messages-container[data-type="list"] .message .attachment-container:last-child {
margin-bottom: 0;
}
.attachment {
position: absolute;
top: 0;
offset-inline-start: 0;
width: 10rem;
height: 10rem;
}
.outgoing .attachment {
offset-inline-start: auto;
offset-inline-end: 0;
}
.preview .attachment {
box-sizing: border-box;
width: 100%;
height: 100%;
background-color: #ececec;
border: 2px solid #fff;
}
.preview .attachment:active,
.preview .attachment:hover {
background-color: #b2f2ff;
}
.size-indicator {
position: absolute;
bottom: 0;
right: 0;
left: 0;
padding: 0 0.4rem;
display: none;
background: rgba(255, 255, 255, 0.9);
color: #333;
font-size: 1.5rem;
}
.attachment-draft > :not([data-attachment-type="vcard"]) .size-indicator {
display: block;
}
.file-name {
position: absolute;
bottom: 0.5rem;
height: 2rem;
display: none;
line-height: 2rem;
font-size: 1.6rem;
font-style: italic;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* .file-name has a dir="auto". Moreover, sms.css in not included in attachment
* iframe, so we cannot leverage the general alignment rules. */
html[dir="ltr"] .file-name {
text-align: left;
left: 1px; /* Avoid issues related to being too much on the left */
right: 0;
}
html[dir="rtl"] .file-name {
text-align: right;
left: 0;
right: 1px;
}
.attachment-draft .file-name {
color: #4a4a4a;
}
.nopreview .file-name {
display: block;
unicode-bidi: -moz-plaintext;
}
.thumbnail {
width: 100%;
height: 100%;
background: none no-repeat center center / cover;
}
.thumbnail-placeholder {
width: 100%;
height: 100%;
/* BiDi-proof: CSS sprites */
background: url('images/attachments.png') no-repeat left top / 10rem;
}
.attachment-container .thumbnail,
.attachment-container .thumbnail-placeholder {
position: absolute;
top: 0
}
.attachment-draft:hover .thumbnail-placeholder:not(.vcard-placeholder),
.attachment-draft:active .thumbnail-placeholder:not(.vcard-placeholder),
.attachment-container:not([data-attachment-type="vcard"]):hover .thumbnail-placeholder,
.attachment-container:not([data-attachment-type="vcard"]):active .thumbnail-placeholder {
background-color: #33cccc;
background-blend-mode: luminosity;
}
.img-placeholder {
background-position: left -10rem; /* BiDi proof: CSS sprites */
}
.audio-placeholder {
background-position: left -20rem; /* BiDi proof: CSS sprites */
}
.video-placeholder {
background-position: left -30rem; /* BiDi proof: CSS sprites */
}
.vcard-placeholder {
background: url('images/attachment-vcard.png') no-repeat;
background-size: 4rem;
}
html[dir="ltr"] .vcard-placeholder { background-position: left top; }
html[dir="rtl"] .vcard-placeholder { background-position: right top; }
.incoming.received .vcard-placeholder {
background-image: url('images/attachment-vcard-incoming.png');
}
.attachment.corrupted .thumbnail-placeholder:after {
display: block;
position: absolute;
top: 0;
offset-inline-start: 0;
height: 100%;
width: 100%;
content: '';
background: url('icons/corrupted.png') no-repeat center center / 10rem;
}

View file

@ -0,0 +1,6 @@
html, body {
height: 100%;
width: 100%;
font-size: 10px;
}

View file

@ -0,0 +1,159 @@
#messages-container {
/* We'll set flex-basis with a shrink factor of 0 to do min-height.
* formula: flex-basis = a % + b)
* a = (gap2 - gap1) / keyboard_height
* b = gap2 - (gap2 - gap1) * (buri_height - header) / keyboard_height + subheader + time_header
*
* with :
* gap1 = 4rem
* minimal visible part of a thread when the keyboard is displayed, on buri
* gap2 = 8rem
* minimal visible part of a thread when the keyboard is hidden, on a buri
* keyboard_height = 26rem
* measured on a buri, standard english keyboard with suggestions
* header = 5rem
* subheader = 2rem
* buri_height = 48rem
* time_header = 2rem
*
* See https://bugzilla.mozilla.org/show_bug.cgi?id=1015867#c12 for more
* information
*/
flex: 1 0 calc(15.4% + 5.38rem);
overflow: auto;
}
#messages-new-message-notice {
display: flex;
flex-direction: row;
padding: 1rem;
padding-inline-start: 2rem;
padding-inline-end: 1.5rem;
overflow: hidden;
white-space: nowrap;
line-height: 2rem;
font-size: 1.8rem;
}
#messages-new-message-notice a {
margin: 0;
}
#messages-new-message-notice .contact {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#messages-new-message-notice > .text-container {
flex: auto;
overflow: hidden;
}
#messages-new-message-notice > .button-container {
flex: none;
padding: 0.2rem;
padding-inline-start: 1.5rem;
padding-inline-end: 0;
}
/* remove margin for notice-button class of button */
.notice-button {
margin: 0;
}
.group-header-title {
display: flex;
justify-content: center;
}
.carrier-wrapper {
display: none;
box-sizing: border-box;
height: 2.2rem;
padding: 0 1.5rem;
font-size: 1.4rem;
line-height: 2.2rem;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
background: rgba(255, 255, 255, 0.95);
color: #333;
}
.carrier-wrapper .phone-carrier {
color: #008787;
}
.has-carrier .carrier-wrapper {
display: block;
}
#messages-container .messages-date-group-header {
position: sticky;
top: 0;
z-index: 1;
height: 2.2rem;
margin: 0;
padding: 0.6rem 3rem;
font-size: 1.4rem;
line-height: 2.2rem;
text-transform: uppercase;
background: rgba(225, 240, 236, 0.85);
color: #008787;
}
/* If has carrier change top position */
.has-carrier #messages-container .messages-date-group-header {
top: 2.2rem;
}
.has-carrier #messages-container > .messages-date-group:first-child > .messages-date-group-header {
margin-top: 2.2rem;
}
/* TODO: check whether we need this rule */
#messages-container .messages-date-group-header.hidden {
display: none;
}
.article-list[data-type="list"] .message {
z-index: 0; /* bug 970422 */
overflow: hidden;
width: 100%;
height: auto;
padding-bottom: 1rem;
border-bottom: none;
}
.article-list[data-type="list"] .message.hidden {
display: none;
}
#messages-container[data-type="list"] .message.mms .bubble img {
height: auto;
width: auto;
}
/* transition in and out edit mode, both for LTR and RTL contexts */
.message .bubble {
transition: transform var(--transition-duration) ease 0s;
}
/* RTL View */
#messages-header-text .ellipsis-dir-fix {
width: auto;
}

View file

@ -0,0 +1,33 @@
/*
* This file contains styles for Conversation view edit mode. They complement
* styles from views/shared/style/edit-mode.css that are applied as well.
* View edit mode is activated via Options -> Select messages.
*/
.edit #messages-container {
margin-bottom: 5.5rem;
}
.edit .article-list[data-type="list"] .message {
border-color: #e7e7e7;
}
.edit [data-type="list"] .message .bubble {
pointer-events: none;
}
.edit .message .bubble {
pointer-events: none;
}
html[dir="ltr"] .edit .message.incoming .bubble {
transform: translateX(2.5rem);
}
html[dir="rtl"] .edit .message.incoming .bubble {
transform: translateX(-2.5rem);
}
.edit #messages-compose-form {
display: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

View file

@ -0,0 +1,238 @@
.message.incoming {
padding-inline-start: 1.5rem;
padding-inline-end: 4rem;
}
.message.outgoing {
padding-inline-start: 4rem;
padding-inline-end: 1.5rem;
}
.message .bubble {
box-sizing: border-box;
height: 100%;
max-width: 100%;
padding: 1rem 1.5rem;
overflow: visible;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
}
.bubble .message-content-body:focus {
-moz-user-select: text;
}
.message.outgoing .bubble {
background: #fff;
}
html[dir="ltr"] .message.outgoing .bubble {
float: right;
border-radius: 1.5rem 0 1.5rem 1.5rem;
}
html[dir="rtl"] .message.outgoing .bubble {
float: left;
border-radius: 0 1.5rem 1.5rem 1.5rem;
}
.message.outgoing .bubble,
.message.outgoing a:active {
color: #333;
}
.message.incoming .bubble {
background: #39c3c2;
}
html[dir="ltr"] .message.incoming .bubble {
float: left;
border-radius: 0 1.5rem 1.5rem 1.5rem;
}
html[dir="rtl"] .message.incoming .bubble {
float: right;
border-radius: 1.5rem 0 1.5rem 1.5rem;
}
.message.incoming .bubble,
.message.incoming a:active {
color: #fff;
}
.message p.message-content-body {
white-space: pre-wrap;
outline: none;
}
/* BB overrides */
.message.outgoing p:first-of-type:last-of-type,
.message.incoming p:first-of-type:last-of-type {
padding-top: 0.5rem;
line-height: 2rem;
}
.message.incoming p:first-of-type {
color: #fff;
}
.message.sending p:first-of-type {
color: #6e6e6e;
}
/* End of BB overrides */
.outgoing.error .bubble {
background: #fae1df;
}
.incoming.error .bubble {
background: #ff9898;
}
.message.sending .bubble,
.message.sending a,
.message.sending a:active {
color: #6e6e6e;
}
.message.sending .message-subject,
.message.sending .message-content-body {
opacity: 0.5;
}
/* Message Subject */
.message .message-subject {
display: none;
margin-bottom: 1rem;
font-size: 1.4rem;
font-style: italic;
line-height: 1.7rem;
unicode-bidi: -moz-plaintext;
}
.message.has-subject .message-subject {
display: block;
}
/* Message content styles */
.message-content a {
color: #004eff;
text-decoration: underline;
}
.message-content > p {
unicode-bidi: -moz-plaintext;
}
.message-details {
display: flex;
margin-top: 0.5rem;
font-size: 1.4rem;
line-height: 2rem;
}
.message.sending time,
.message.sending .message-sim-information {
display: none;
}
.message.outgoing .message-details {
color: #858585;
}
.message.outgoing.error .message-details {
color: #e97f79;
}
.message.incoming .message-details {
color: #d1eeee;
}
.message.incoming .late-arrival-notice {
color: #026a69;
}
.message.incoming .late-arrival-icon {
margin-inline-start: 1rem;
margin-inline-end: 0.1rem;
min-width: 1.5rem;
height: 2rem;
background: no-repeat center center / 1.5rem;
background-image: url('images/icons/late_clock.png');
}
.message.incoming.error .message-details {
color: #ffe0dc;
}
.message-sim-information {
margin-inline-end: 0.4rem;
}
.message-sim-information::after {
content: ' ‧ ';
}
/* Message status aside panel */
.message .message-status {
height: 2rem;
min-width: 3rem;
background: no-repeat center center / 1.6rem;
font-size: 1.4rem;
}
.message.error .message-status {
background-image: url('images/icons/exclamation.png');
}
.message.delivered .message-status {
background-image: url('images/icons/deliveredtick.png');
}
.message.read .message-status {
background-image: url('images/icons/message_read.png');
}
.message.pending .message-status,
.message.sending .message-status {
background: none;
}
/* Message download button */
.message .download {
display: block;
min-height: initial;
width: auto;
padding: 0;
/* Eliminating container paragraph line-height */
margin: -1.5rem 0 0;
background: none;
color: #fff;
border: none;
/* We're using border-bottom instead of just underline style as line is too
close to the text and doesn't look similar to visual spec. */
border-bottom: 1px solid #fff;
}
.message .download:active {
opacity: 0.85;
}
/* hide download button via css on expired messages */
.message.expired .download {
display: none;
}
.message.pending .download {
border-color: transparent;
opacity: 0.8;
}

View file

@ -0,0 +1,167 @@
/* Message information page for report details*/
.report-view {
display: flex;
flex-direction: column;
background-color: #fff;
}
.report-view > .description {
padding-top: 2rem;
padding-inline-end: 1.5rem;
padding-bottom: 3rem;
padding-inline-start: 3rem;
}
.report-view p,
.report-view span {
font-size: 1.5rem;
line-height: 1rem;
color: #858585;
}
.report-view p {
padding-top: 1.5rem;
}
.report-view .description > .type {
padding-top: 0;
font-size: 1.6rem;
color: #ff4e00;
}
.report-view .description .detail {
color: #333;
}
.report-view .description > .type[data-l10n-id="message-type-sms"] {
color: #000;
}
/* Hiding some elements for some states */
.report-view[data-delivery="error"] .sent-title,
.report-view[data-delivery="sending"] .sent-timestamp,
.outgoing-message .received-block,
.outgoing-message .lateness-block {
display: none;
}
/* Hide sent timestamp label if we don't have valid value to display */
.report-view.no-valid-sent-timestamp .sent-block {
display: none;
}
.report-view .contact-title {
color: #008787;
font-size: 1.6rem;
padding-inline-start: 3rem;
}
.report-view > ul.contact-list {
/* Showing 4 complete contacts and part of the contact name if list
contains more than 4 contacts */
position: static;
margin-top: 0.5rem;
flex-grow: 1;
}
.report-view > .contact-list > li {
height: auto;
min-height: 9rem;
padding-top: 0.7rem;
padding-bottom: 1rem;
padding-inline-end: 0;
padding-inline-start: 1.5rem;
border-top: solid 0.1rem #e7e7e7;
border-bottom: medium none;
}
.report-view > .contact-list p,
.report-view > .contact-list span {
font-size: 1.4rem;
line-height: 1.6rem;
}
.report-view > .contact-list .name {
padding-top: 0;
font-size: 1.8rem;
font-weight: 500;
line-height: 2.6rem;
}
.report-view > .contact-list aside {
/* margin-top = (li - padding - aside) / 2 = (9rem - 2rem - 5.5rem) / 2 */
margin-top: 0.8rem;
}
.report-view > .contact-list .number {
padding-top: 0.6rem;
}
.report-view .network-status {
padding-top: 0.7rem;
}
.report-view .name + .network-status {
padding-top: 1rem;
}
.report-view .network-status,
.report-view .network-status span {
line-height: 1.4rem;
}
.contact-list p.number .phone-carrier {
color: #008787;
}
.report-view > .contact-list .icon {
width: 1.6rem;
height: 1.6rem;
margin: -0.1rem 0 0.1rem;
margin-inline-end: 0.7rem;
background: no-repeat center center / 1.6rem transparent;
}
html[dir="ltr"] .report-view > .contact-list .icon { float: left; }
html[dir="rtl"] .report-view > .contact-list .icon { float: right; }
.contact-list [data-delivery-status="delivered"] .icon {
background-image: url('images/icons/deliveredtick.png');
}
.contact-list [data-delivery-status="read"] .icon {
background-image: url('images/icons/message_read.png');
}
.contact-list [data-delivery-status="error"] .icon {
background-image: url('images/icons/exclamation.png');
}
.contact-list [data-delivery-status="not-applicable"],
.contact-list [data-delivery-status="pending"] .icon,
.contact-list [data-delivery-status="pending"] .timestamp,
.contact-list [data-delivery-status="error"] .timestamp,
.contact-list [data-delivery-status="rejected"] .icon,
.contact-list [data-delivery-status="rejected"] .timestamp{
display: none;
}
.report-view .resend-btn {
display: none;
width: calc(100% - 3rem);
flex-shrink: 0;
margin: 0 1.5rem 2rem;
background-color: #00caf2;
color: #fff;
}
.report-view[data-delivery="error"] .resend-btn {
display: block;
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,183 @@
<!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>Inbox</title>
<!--App Style root.css-->
<link rel="stylesheet" type="text/css" href="/views/shared/style/root.css">
<!-- Stable Building blocks -->
<link rel="stylesheet" type="text/css" href="/shared/style/animations.css">
<link rel="stylesheet" type="text/css" href="/shared/style/action_menu.css">
<link rel="stylesheet" type="text/css" href="/shared/style/option_menu.css">
<link rel="stylesheet" type="text/css" href="/shared/style/buttons.css">
<link rel="stylesheet" type="text/css" href="/shared/style/confirm.css">
<link rel="stylesheet" type="text/css" href="/shared/style/edit_mode.css">
<link rel="stylesheet" type="text/css" href="/shared/style/switches.css">
<link rel="stylesheet" type="text/css" href="/shared/style/lists.css">
<link rel="stylesheet" type="text/css" href="/shared/style/tabs.css">
<link rel="stylesheet" type="text/css" href="/shared/style/progress_activity.css">
<link rel="stylesheet" type="text/css" href="/shared/style/status.css">
<link rel="stylesheet" type="text/css" href="/shared/elements/gaia-icons/gaia-icons.css">
<!-- App styles -->
<link rel="stylesheet" type="text/css" href="/views/shared/style/sms.css">
<link rel="stylesheet" type="text/css" href="/views/shared/style/panel.css">
<link rel="stylesheet" type="text/css" href="/views/shared/style/edit-mode.css">
<link rel="stylesheet" type="text/css" href="/views/shared/style/notification.css">
<link rel="stylesheet" type="text/css" href="/views/shared/style/common.css">
<link rel="stylesheet" type="text/css" href="/views/inbox/style/inbox.css">
<link rel="stylesheet" type="text/css" href="/views/inbox/style/edit-mode.css">
<!-- Localization -->
<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/views.inbox.index.{locale}.json">
<!-- <script defer="" src="/mocks/navigator_moz_icc_manager.js"></script> -->
<!-- Firefox Desktop shims -->
<!-- Web Components -->
<!-- <script defer="" src="/shared/elements/config.js"></script> -->
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
<script async="" src="/shared/elements/gaia-header/dist/gaia-header.js"></script>
<!-- Shared code -->
<!-- <script defer="" src="/shared/js/intl/l20n.js"></script> -->
<!-- Should be removed while gecko support navigator.mozHour12 API -->
<!-- <script defer="" src="/shared/js/date_time_helper.js"></script> -->
<!-- <script defer="" src="/shared/js/template.js"></script> -->
<!-- <script defer="" src="/shared/js/async_storage.js"></script> -->
<!-- <script defer="" src="/shared/js/lazy_loader.js"></script> -->
<!-- <script defer="" src="/shared/js/sticky_header.js"></script> -->
<!-- <script defer="" src="/shared/js/contact_photo_helper.js"></script> -->
<!-- <script defer="" src="/shared/js/fb/fb_request.js"></script> -->
<!-- <script defer="" src="/shared/js/fb/fb_data_reader.js"></script> -->
<!-- <script defer="" src="/shared/js/fb/fb_reader_utils.js"></script> -->
<!-- <script defer="" src="/shared/js/event_dispatcher.js"></script> -->
<!-- <script defer="" src="/views/shared/js/utils.js"></script> -->
<!-- <script defer="" src="/views/shared/js/navigation.js"></script> -->
<!-- <script defer="" src="/views/shared/js/inter_instance_event_dispatcher.js"></script> -->
<!-- <script defer="" src="/views/shared/js/time_headers.js"></script> -->
<!-- <script defer="" src="/views/shared/js/contacts.js"></script> -->
<!-- <script defer="" src="/services/js/drafts.js"></script> -->
<!-- <script defer="" src="/services/js/threads.js"></script> -->
<!-- <script defer="" src="/views/inbox/js/inbox.js"></script> -->
<!-- <script defer="" src="/views/shared/js/settings.js"></script> -->
<!-- <script defer="" src="/lib/bridge/client.js"></script> -->
<!-- <script defer="" src="/lib/bridge/plugins/stream/client.js"></script> -->
<!-- <script defer="" src="/services/js/conversation/conversation_client.js"></script> -->
<!-- <script defer="" src="/services/js/message_manager.js"></script> -->
<!-- <script defer="" src="/views/shared/js/app.js"></script> -->
<!-- <script defer="" src="/views/inbox/js/startup.js"></script> -->
<!-- <script defer="" src="/views/shared/js/bootstrap.js"></script> --><script src="./gaia_build_defer_index.js" defer="" type="text/javascript"></script>
<!-- Lazy load scripts
<script defer src="/shared/js/option_menu.js"></script>
<script defer src="/views/shared/js/dialog.js"></script>
<script defer src="/views/shared/js/waiting_screen.js"></script>
<script defer src="/views/shared/js/activity_picker.js"></script>
-->
<meta content="2.5" name="appVersion"></head>
<body role="application" class="skin-comms theme-communications">
<iframe src="/views/shared/shim_host.html" class="shim-host" hidden=""></iframe>
<article id="main-wrapper" class="wrapper">
<section class="panel panel-InboxView" role="region" aria-hidden="true">
<!-- title-start and title-end indicates the button widths respectively
at the start and end of the line -->
<!-- see https://github.com/gaia-components/gaia-header/ for more information -->
<!-- WARNING: the actual value depends on the actual CSS being used in gaia-header -->
<gaia-header class="view-header regular-header" title-start="0" title-end="100">
<h1 data-l10n-id="messages">Messages</h1>
<a aria-label="New message" href="#/composer" id="threads-composer-link" data-icon="compose" data-l10n-id="composeLink"></a>
<a aria-label="Actions" id="threads-options-button" data-icon="more" data-l10n-id="actionsButton"></a>
<a aria-label="Settings" id="threads-settings-button" data-icon="settings" data-l10n-id="settingsButton"></a>
</gaia-header>
<article id="threads-container" class="view-body edit-container" data-type="list">
</article>
<div id="threads-no-messages">
<div id="no-result-message">
<p data-l10n-id="noMessages-title">No messages</p>
<p data-l10n-id="noMessages-text-2">Start a conversation now</p>
</div>
</div>
<form id="threads-edit-form" role="dialog" data-type="edit">
<section role="region" class="theme-settings">
<gaia-header id="threads-edit-header" action="close" no-font-fit="">
<h1 id="threads-edit-mode" data-l10n-id="selectThreads-title">Select</h1>
<button id="threads-check-uncheck-all-button" class="edit-button" data-l10n-id="select-all">Select all</button>
</gaia-header>
</section>
<div class="bottom-tab" role="region">
<ul class="bb-tablist">
<li role="presentation">
<button id="threads-delete-button" class="disable-when-dialog-animates" data-action="delete-button"></button>
</li>
<li role="presentation">
<button id="threads-read-unread-button" data-action="mark-as-read"></button>
</li>
</ul>
</div>
</form>
<section id="threads-draft-saved-banner" class="hide" role="status">
<p data-l10n-id="message-draft-saved">Message saved as draft</p>
</section>
<div id="sticky"></div>
</section>
</article>
<article id="loading">
<form role="dialog" data-type="confirm" class="loading-container">
<section>
<p class="loading-header" data-l10n-id="deleting-messages">Deleting…</p>
<div role="status">
<progress class="small"></progress>
</div>
</section>
</form>
</article>
<!--
- Templates
-->
<div id="messages-thread-tmpl" class="hide">
<!--
<label class="pack-checkbox negative">
<input type="checkbox" value="${id}" data-mode="${mode}">
<span></span>
</label>
<a href="${hash}">
<aside role="note" class="icon icon-unread icon-draft js-conversation-state"></aside>
<aside class="pack-end threadlist-item-picture"
role="img" aria-label="Contact photo"
data-l10n-id="contact-photo">
<span data-type="img"></span>
</aside>
<p class="threadlist-item-title"><bdi class="ellipsis-dir-fix"></bdi></p>
<p class="summary">
<time data-time-update="true" data-time-only="true" data-time="${timestamp}"></time>
<span class="mms-icon" data-l10n-id="mms-label">MMS</span>
<span dir="auto" class="body-text js-conversation-body ellipsis-dir-fix"></span>
</p>
</a>
-->
</div>
</body>
</html>

View file

@ -0,0 +1,53 @@
;;(function(exports){'use strict';const privateMembers=new WeakMap();function createBdiNode(content){var bdi=document.createElement('bdi');bdi.textContent=content;return bdi;}
var InboxView={DRAFT_SAVED_DURATION:5000,FIRST_PANEL_THREAD_COUNT:9,timeouts:{onDraftSaved:null},count:0,inEditMode:false,notifyAboutSavedDraftWithId:null,init:function inbox_init(){this.tmpl={thread:Template('messages-thread-tmpl')};['container','no-messages','read-unread-button','check-uncheck-all-button','composer-link','delete-button','edit-header','options-button','settings-button','edit-mode','edit-form','draft-saved-banner'].forEach(function(id){this[Utils.camelCase(id)]=document.getElementById('threads-'+id);},this);this.mainWrapper=document.getElementById('main-wrapper');this.composerLink.addEventListener('click',this.launchComposer.bind(this));this.readUnreadButton.addEventListener('click',()=>{this.markReadUnread(this.selectionHandler.selectedList,this.readUnreadButton.dataset.action==='mark-as-read');});this.deleteButton.addEventListener('click',()=>{this.delete(this.selectionHandler.selectedList);});this.editHeader.addEventListener('action',this.cancelEdit.bind(this));this.optionsButton.addEventListener('click',this.showOptions.bind(this));this.settingsButton.addEventListener('click',function oSettings(){ActivityPicker.openSettings();});this.container.addEventListener('click',this);this.container.addEventListener('contextmenu',this);this.editForm.addEventListener('submit',this);navigator.mozContacts.addEventListener('contactchange',this.updateContactsInfo.bind(this));MessageManager.on('message-sending',this.onMessageSending.bind(this));MessageManager.on('message-received',this.onMessageReceived.bind(this));MessageManager.on('threads-deleted',this.onThreadsDeleted.bind(this));Drafts.on('deleted',this.onDraftDeleted.bind(this));Drafts.on('saved',this.onDraftSaved.bind(this));privateMembers.set(this,{groupThreadTitleMaxLength:(window.innerWidth-100)/10});this.once('fully-loaded',()=>{this.ensureReadAheadSetting();});this.once('visually-loaded',()=>{this.initStickyHeader();});this.sticky=null;},initStickyHeader:function inbox_initStickyHeader(){if(!this.sticky){this.sticky=new StickyHeader(this.container,document.getElementById('sticky'));}},beforeEnter:function inbox_beforeEnter(args={}){if(this.notifyAboutSavedDraftWithId){this.showDraftSavedBanner();this.notifyAboutSavedDraftWithId=null;}},beforeLeave:function inbox_beforeLeave(){this.cancelEdit();},getIdIterator:function inbox_getIdIterator(){return Threads.keys();},setContact:function inbox_setContact(node){var threadId=node.dataset.threadId;var draftId=node.dataset.draftId;var threadOrDraft=draftId?Drafts.byDraftId(+draftId):Threads.get(+threadId);if(!threadOrDraft){throw new Error('Thread node is invalid!');}
var threadNumbers=threadOrDraft.participants||threadOrDraft.recipients;var titleContainer=node.querySelector('.threadlist-item-title');var title=titleContainer.firstElementChild;var picture=node.querySelector('.threadlist-item-picture');if(!threadNumbers||!threadNumbers.length){title.setAttribute('data-l10n-id','no-recipient');return;}
function*updateThreadNode(number){var contact=yield InboxView.findContact(number,{photoURL:true});var isContact=!!contact.isContact;picture.classList.toggle('has-picture',isContact);picture.classList.toggle('default-picture',isContact&&!contact.photoURL);title.textContent=contact.title||number;var photoUrl=node.dataset.photoUrl;if(photoUrl){window.URL.revokeObjectURL(photoUrl);}
if(contact.photoURL){node.dataset.photoUrl=contact.photoURL;}else if(photoUrl){node.dataset.photoUrl='';}
if(contact.photoURL){picture.firstElementChild.style.backgroundImage=['url('+contact.photoURL+')','url(/views/inbox/style/images/default_contact_image.png)'].join(', ');}else{picture.firstElementChild.style.backgroundImage=null;}}
function*updateGroupThreadNode(numbers,titleMaxLength){var contactTitle,number;var i=0;var threadTitleLength=0;var groupTitle=document.createElement('span');var separatorNode=document.createElement('span');separatorNode.setAttribute('data-l10n-id','thread-participant-separator');picture.firstElementChild.textContent=numbers.length;picture.classList.add('has-picture','group-picture');while(i<numbers.length&&threadTitleLength<titleMaxLength){number=numbers[i++];contactTitle=(yield InboxView.findContact(number)).title||number;if(threadTitleLength>0){groupTitle.appendChild(separatorNode.cloneNode(true));}
groupTitle.appendChild(createBdiNode(contactTitle));threadTitleLength+=contactTitle.length;}
titleContainer.replaceChild(groupTitle,title);}
if(!title.textContent){title.textContent=threadNumbers[0];}
if(threadNumbers.length===1){return Utils.Promise.async(updateThreadNode)(threadNumbers[0]);}
return Utils.Promise.async(updateGroupThreadNode)(threadNumbers,privateMembers.get(this).groupThreadTitleMaxLength);},findContact:function(number,options){return Contacts.findByAddress(number).then(function(contacts){var details=Utils.getContactDetails(number,contacts,options);if(!details.isContact){Contacts.addUnknown(number);}
return details;});},handleEvent:function inbox_handleEvent(event){var parent=event.target.parentNode;var threadId=parent.dataset.threadId;switch(event.type){case'click':if(this.inEditMode){return;}
if(threadId){event.preventDefault();var draftId=parent.dataset.draftId;if(draftId){Navigation.toPanel('composer',{draftId:+draftId,});}else{Navigation.toPanel('thread',{id:+threadId});}}
break;case'contextmenu':if(this.inEditMode||!threadId){return;}
event.preventDefault();event.stopPropagation();var params={type:'action',header:{l10nId:'thread-options'},items:[{l10nId:'delete-thread',method:this.delete.bind(this,[threadId])}]};var thread=Threads.get(+threadId);if(!thread.isDraft){var isRead=thread.unreadCount>0;var l10nKey=isRead?'mark-as-read':'mark-as-unread';params.items.push({l10nId:l10nKey,method:this.markReadUnread.bind(this,[threadId],isRead)});}
params.items.push({l10nId:'cancel'});var options=new OptionMenu(params);options.show();break;case'submit':event.preventDefault();break;}},launchComposer:function inbox_launchComposer(e){e.preventDefault();Navigation.toPanel('composer');},updateSelectionStatus:function inbox_updateSelectionStatus(){var selected=this.selectionHandler;if(this.selectionHandler.allSelected()){this.checkUncheckAllButton.setAttribute('data-l10n-id','deselect-all');}else{this.checkUncheckAllButton.setAttribute('data-l10n-id','select-all');}
if(selected.selectedCount){this.deleteButton.disabled=false;document.l10n.setAttributes(this.editMode,'selected-threads',{n:selected.selectedCount});var hasUnreadselected=selected.selectedList.some((id)=>{var thread=Threads.get(id);if(thread&&thread.unreadCount){return thread.unreadCount>0;}
return false;});var allDraft=selected.selectedList.every((id)=>{return(Threads.get(id).isDraft);});if(allDraft){this.readUnreadButton.disabled=true;}else{if(!hasUnreadselected){this.readUnreadButton.dataset.action='mark-as-unread';}else{this.readUnreadButton.dataset.action='mark-as-read';}
this.readUnreadButton.disabled=false;}}else{this.deleteButton.disabled=true;this.readUnreadButton.disabled=true;document.l10n.setAttributes(this.editMode,'selectThreads-title');}},markReadUnread:function inbox_markReadUnread(selected,isRead){selected.forEach((id)=>{var thread=Threads.get(+id);var readStatusMatched=!!thread.unreadCount^isRead;var markable=thread&&!thread.isDraft&&(isRead||!thread.getDraft());if(markable&&!readStatusMatched){thread.unreadCount=isRead?0:1;this.mark(thread.id,isRead?'read':'unread');MessageManager.markThreadRead(thread.id,isRead);}});this.cancelEdit();},removeConversationDOM(conversationId){var li=document.getElementById('thread-'+conversationId);var parent;var photoUrl=li&&li.dataset.photoUrl;if(photoUrl){window.URL.revokeObjectURL(photoUrl);}
if(li){parent=li.parentNode;li.remove();}
if(parent&&!parent.firstElementChild){parent.previousSibling.remove();parent.remove();this.sticky&&this.sticky.refresh();if(!this.container.querySelector('li')){this.setEmpty(true);}}},delete:function inbox_delete(selected){function performDeletion(){var threadIdsToDelete=[],messageIdsToDelete=[],threadCountToDelete=0;function exitEditMode(){InboxView.cancelEdit();WaitingScreen.hide();}
function onAllThreadMessagesRetrieved(){if(!--threadCountToDelete){MessageManager.deleteMessages(messageIdsToDelete);threadIdsToDelete.forEach(function(threadId){InboxView.deleteThread(threadId);});messageIdsToDelete=threadIdsToDelete=null;exitEditMode();}}
function onThreadMessageRetrieved(message){messageIdsToDelete.push(message.id);return true;}
WaitingScreen.show();threadIdsToDelete=selected.reduce(function(list,value){var threadId=+value;if(Threads.get(threadId).isDraft){InboxView.deleteThread(threadId);}else{list.push(threadId);}
return list;},[]);if(!threadIdsToDelete.length){exitEditMode();return;}
threadCountToDelete=threadIdsToDelete.length;threadIdsToDelete.forEach(function(threadId){MessageManager.getMessages({filter:{threadId:threadId},each:onThreadMessageRetrieved,end:onAllThreadMessagesRetrieved});});}
return Utils.confirm({id:'deleteThreads-confirmation-message',args:{n:selected.length}},null,{text:'delete',className:'danger'}).then(performDeletion.bind(this));},setEmpty:function inbox_setEmpty(empty){var panel=document.querySelector('.panel-InboxView');panel.classList.toggle('threadlist-is-empty',!!empty);},showOptions:function inbox_options(){var params={items:[{l10nId:'selectThreads-label',method:this.startEdit.bind(this)},{l10nId:'settings',method:function oSettings(){ActivityPicker.openSettings();}},{l10nId:'cancel',incomplete:true}]};new OptionMenu(params).show();},startEdit:function inbox_edit(){function editModeSetup(){this.inEditMode=true;this.selectionHandler.cleanForm();this.mainWrapper.classList.toggle('edit');}
this.editHeader.removeAttribute('no-font-fit');if(!this.selectionHandler){LazyLoader.load(['/views/shared/js/selection_handler.js','/shared/style/edit_mode.css','/shared/style/switches.css','/views/shared/style/edit-mode.css','/shared/style/tabs.css','/views/inbox/style/edit-mode.css'],()=>{this.selectionHandler=new SelectionHandler({container:this.container,checkUncheckAllButton:this.checkUncheckAllButton,updateSelectionStatus:this.updateSelectionStatus.bind(this),getIdIterator:this.getIdIterator.bind(this),isInEditMode:this.isInEditMode.bind(this)});this.editForm.classList.remove('hide');editModeSetup.call(this);});}else{editModeSetup.call(this);}},isInEditMode:function inbox_isInEditMode(){return this.inEditMode;},cancelEdit:function inbox_cancelEdit(){this.inEditMode=false;this.mainWrapper.classList.remove('edit');},renderDrafts:function inbox_renderDrafts(){return Drafts.request().then(()=>{for(var draft of Drafts.getAll()){if(draft.threadId){if(document.getElementById(`thread-${draft.threadId}`)){this.updateThread(Threads.get(draft.threadId),{conversationDraft:true});}}else{this.setEmpty(false);if(!Threads.has(draft.id)){var thread=Thread.create(draft);Threads.set(draft.id,thread);this.appendThread(thread);}}}
this.sticky&&this.sticky.refresh();});},prepareRendering:function inbox_prepareRendering(){this.container.innerHTML='';this.renderDrafts();},startRendering:function inbox_startRenderingThreads(){this.setEmpty(false);},finalizeRendering:function inbox_finalizeRendering(empty){if(empty){this.setEmpty(true);}
if(!empty){TimeHeaders.updateAll('header[data-time-update]');}
this.sticky&&this.sticky.refresh();},ensureReadAheadSetting:function inbox_ensureReadAheadSettting(){Settings.setReadAheadThreadRetrieval(this.FIRST_PANEL_THREAD_COUNT);},renderThreads:function inbox_renderThreads(){window.performance.mark('willRenderThreads');var hasThreads=false;var firstPanelCount=this.FIRST_PANEL_THREAD_COUNT;this.prepareRendering();function onRenderThread(thread){Threads.set(thread.id,thread);if(!hasThreads){hasThreads=true;this.startRendering();}
this.appendThread(thread);if(--firstPanelCount===0){this.emit('visually-loaded');}}
function onThreadsRendered(){this.finalizeRendering(!(hasThreads||Drafts.size));if(firstPanelCount>0){this.emit('visually-loaded');}}
MessageManager.getThreads({each:onRenderThread.bind(this),end:onThreadsRendered.bind(this),done:()=>Drafts.request().then(()=>this.emit('fully-loaded'))});},updateConversationContent(node,record){var id=record.id;var thread=Threads.get(id);var isDraft=thread.isDraft;var draft=thread.getDraft();var body=record.body;var type=record.lastMessageType;var iconLabel='';if(draft&&draft.timestamp>=record.timestamp){body=draft.content.find((content)=>typeof content==='string');type=draft.type;}
node.dataset.lastMessageType=type;if(draft){node.classList.add('draft');if(isDraft){node.dataset.draftId=draft.id;node.classList.add('is-draft');iconLabel='is-draft';}else{node.classList.add('has-draft');iconLabel='has-draft';}}else{node.classList.remove('draft','has-draft','is-draft');}
if(record.unreadCount>0){node.classList.add('unread');iconLabel='unread-thread';}else{node.classList.remove('unread');}
node.querySelector('.js-conversation-body').textContent=body||'';var stateIndicator=node.querySelector('.js-conversation-state');if(iconLabel){document.l10n.setAttributes(stateIndicator,iconLabel);}else{stateIndicator.removeAttribute('data-l10n-id');}},createThread:function inbox_createThread(record){var li=document.createElement('li');var timestamp=+record.timestamp;var id=record.id;var thread=Threads.get(id);var isDraft=thread.isDraft;var draft=thread.getDraft();li.id='thread-'+id;li.dataset.threadId=id;li.dataset.time=timestamp;li.classList.add('threadlist-item');li.innerHTML=this.tmpl.thread.interpolate({hash:isDraft?'#/composer':'#/thread?id='+id,mode:isDraft?'drafts':'threads',id:String(isDraft?draft.id:id),timestamp:String(timestamp)},{safe:['id']});this.updateConversationContent(li,record);TimeHeaders.update(li.querySelector('time'));return li;},deleteThread:function(threadId){Threads.delete(threadId);this.removeConversationDOM(threadId);Utils.closeNotificationsForThread(threadId);},insertThreadContainer:function inbox_insertThreadContainer(group,timestamp){var headers=InboxView.container.getElementsByTagName('header');var groupFound=false;for(var i=0;i<headers.length;i++){if(timestamp>=headers[i].dataset.time){groupFound=true;InboxView.container.insertBefore(group,headers[i].parentNode);break;}}
if(!groupFound){InboxView.container.appendChild(group);}},updateThread(record,options={}){var thread=Thread.create(record,options);var threadUINode=document.getElementById('thread-'+thread.id);var threadUITime=threadUINode?+threadUINode.dataset.time:NaN;var recordTime=+thread.timestamp;Threads.set(thread.id,thread);var newMessageReceived=options.unread;if(newMessageReceived&&threadUITime>recordTime){this.mark(thread.id,'unread');return;}
if(options.deleted&&threadUITime===recordTime){return;}
if(threadUINode&&options.conversationDraft){this.updateConversationContent(threadUINode,record);return;}
if(threadUINode){this.removeConversationDOM(thread.id);}
this.setEmpty(false);if(this.appendThread(thread)){this.sticky&&this.sticky.refresh();}},onMessageSending:function inbox_onMessageSending(e){this.updateThread(e.message);},onMessageReceived:function inbox_onMessageReceived(e){var markAsRead=Navigation.isCurrentPanel('thread',{id:e.message.threadId});this.updateThread(e.message,{unread:!markAsRead});},onThreadsDeleted:function inbox_onThreadDeleted(e){e.ids.forEach(function(threadId){if(Threads.has(threadId)){this.deleteThread(threadId);}},this);},appendThread:function inbox_appendThread(thread){var timestamp=+thread.timestamp;var firstThreadInContainer=false;var node=this.createThread(thread);this.setContact(node);var threadsContainerID='threadsContainer_'+
Utils.getDayDate(timestamp);var threadsContainer=document.getElementById(threadsContainerID);if(!threadsContainer){var threadsContainerWrapper=InboxView.createThreadContainer(timestamp);threadsContainer=threadsContainerWrapper.childNodes[1];InboxView.insertThreadContainer(threadsContainerWrapper,timestamp);firstThreadInContainer=true;}
var threads=threadsContainer.getElementsByTagName('li');var threadFound=false;for(var i=0,l=threads.length;i<l;i++){if(timestamp>threads[i].dataset.time){threadFound=true;threadsContainer.insertBefore(node,threads[i]);break;}}
if(!threadFound){threadsContainer.appendChild(node);}
if(this.inEditMode){this.selectionHandler.unselect(thread.id);this.updateSelectionStatus();}
return firstThreadInContainer;},createThreadContainer:function inbox_createThreadContainer(timestamp){var threadContainer=document.createElement('div');var headerDOM=document.createElement('header');headerDOM.id='header_'+timestamp;headerDOM.dataset.timeUpdate='repeat';headerDOM.dataset.time=timestamp;headerDOM.dataset.dateOnly=true;var threadsContainerDOM=document.createElement('ul');threadsContainerDOM.id='threadsContainer_'+
Utils.getDayDate(timestamp);Utils.setHeaderDate({time:timestamp,element:headerDOM});threadContainer.appendChild(headerDOM);threadContainer.appendChild(threadsContainerDOM);return threadContainer;},updateContactsInfo:function inbox_updateContactsInfo(){Contacts.clearUnknown();if(!this.container){return;}
var threads=this.container.getElementsByTagName('li');[].forEach.call(threads,this.setContact.bind(this));},mark:function inbox_mark(id,current){var li=document.getElementById('thread-'+id);if(li){li.classList.toggle('unread',current==='unread');}},onDraftDeleted:function inbox_onDraftDeleted(draft){var thread=Threads.get(draft.threadId||draft.id);if(!thread){return;}
if(thread.isDraft){this.deleteThread(thread.id);}else{this.updateThread(thread,{conversationDraft:true});}
if(draft.id===this.notifyAboutSavedDraftWithId){this.notifyAboutSavedDraftWithId=null;}},onDraftSaved:function inbox_onDraftSaved(draft){if(draft.threadId){this.updateThread(Threads.get(draft.threadId),{conversationDraft:true});}else{this.updateThread(draft);}
if(!Navigation.isCurrentPanel('thread-list')){this.notifyAboutSavedDraftWithId=draft.id;}},showDraftSavedBanner:function(){this.draftSavedBanner.classList.remove('hide');clearTimeout(this.timeouts.onDraftSaved);this.timeouts.onDraftSaved=null;this.timeouts.onDraftSaved=setTimeout(()=>this.draftSavedBanner.classList.add('hide'),this.DRAFT_SAVED_DURATION);}};exports.InboxView=EventDispatcher.mixin(InboxView,['visually-loaded','fully-loaded']);}(this));

View file

@ -0,0 +1,2 @@
;(function(exports){'use strict';const LAZY_DEPENDENCIES=['/shared/js/option_menu.js','/views/shared/js/waiting_screen.js','/views/shared/js/dialog.js','/views/shared/js/error_dialog.js','/views/shared/js/activity_picker.js'];function initLazyDependencies(){return LazyLoader.load(LAZY_DEPENDENCIES).then(()=>{TimeHeaders.init();Settings.init();Navigation.setReady();InterInstanceEventDispatcher.connect();});}
exports.Startup={init(){Utils.initializeShimHost(App.instanceId);ConversationClient.init(App.instanceId);MessageManager.init();Navigation.init();Drafts.init();InboxView.init();InboxView.once('visually-loaded',()=>{initLazyDependencies();});InboxView.once('fully-loaded',()=>{App.setReady();});InboxView.renderThreads();}};})(window);

View file

@ -0,0 +1,36 @@
/*
* This file contains styles for Inbox view edit mode. They complement styles
* from views/shared/style/edit-mode.css that are applied as well.
* View edit mode is activated via Options -> Select threads.
*/
.bb-tablist > li button[data-action="mark-as-read"] {
background-image: url("images/icons/ico_in_markasread_30x30.png");
}
.bb-tablist > li button[data-action="mark-as-unread"] {
background-image: url("images/icons/ico_in_markasunread_30x30.png");
}
.edit #threads-container[data-type="list"] {
height: calc(100% - 10rem);
}
html[dir="ltr"] .edit [data-type="list"] li > a > aside.icon-unread {
transform: translateX(5rem);
}
html[dir="rtl"] .edit [data-type="list"] li > a > aside.icon-unread {
transform: translateX(-5rem);
}
.edit #threads-container aside.pack-end {
margin-inline-start: 4rem;
}
html[dir="ltr"] .edit #threads-container[data-type="list"] aside.pack-end span {
transform: translateX(1.5rem);
}
html[dir="rtl"] .edit #threads-container[data-type="list"] aside.pack-end span {
transform: translateX(-1.5rem);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

View file

@ -0,0 +1,191 @@
#sticky {
position: absolute;
/* The sticky element is positioned at the beginning of the list. */
top: 5rem;
/* The width is not 100% so the scrollbar is visible. */
width: calc(100% - 2rem);
/* The height needs to be kept in sync with the header height. */
height: 3.1rem;
/* Leaving 1rem margin for both side to makes scrollbar visible in LTR/RTL */
margin: 0 1rem;
/* The sticky element needs to be above the list. */
z-index: 2;
/* Hide sticky header when content is not ready */
visibility: hidden;
/* Those style will affect the backgroundImage: -moz-element. */
background: white no-repeat center top;
}
#sticky.has-content {
/* Set sticky header visible only when content is ready */
visibility: visible;
}
#threads-container[data-type="list"] header {
background: #fff;
}
#threads-container[data-type="list"] ul {
padding: 0 1.0rem;
background-color: #fff;
}
#threads-container[data-type="list"] .mms-icon {
display: none;
font-size: 1.5rem;
color: #ff6622;
}
#threads-container[data-type="list"] [data-last-message-type="mms"] .mms-icon {
display: inline;
}
#threads-container[data-type="list"] [data-last-message-type="mms"] .body-text {
display: none;
}
.threadlist-item-picture {
display: none;
}
.threadlist-item-picture.has-picture {
display: block;
}
.threadlist-item .default-picture > span[data-type=img],
.threadlist-item .group-picture > span[data-type=img] {
/* Please keep this image in sync with what is used in js/inbox.js */
background: url('images/default_contact_image.png') top left; /* BiDi proof: CSS sprites */
}
.threadlist-item:nth-child(even) .default-picture > span[data-type=img],
.threadlist-item:nth-child(even) .group-picture > span[data-type=img] {
background-position: bottom right; /* BiDi proof: CSS sprites */
}
.threadlist-item:nth-child(3n+1) .default-picture > span[data-type=img],
.threadlist-item:nth-child(3n+1) .group-picture > span[data-type=img] {
background-position: top right; /* BiDi proof: CSS sprites */
}
.threadlist-item:nth-child(4n+4) .default-picture > span[data-type=img],
.threadlist-item:nth-child(4n+4) .group-picture > span[data-type=img] {
background-position: center center;
}
.threadlist-item-picture.group-picture > span[data-type=img] {
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 2.5rem;
}
/**
* Bug 967878, 979018 -[Messages] Background doesn't
* scroll with the threads causing expensive over-invalidation
*/
#threads-container > div {
background-color: #fff;
}
[data-type="list"] aside.icon-unread {
margin: 0;
width: 1.9rem;
visibility: hidden;
}
[data-type="list"] .unread aside.icon-unread {
background: url("images/unread.png") no-repeat;
background-size: 1rem;
visibility: visible;
}
[data-type="list"] .threadlist-item a * {
pointer-events: none;
}
[data-type="list"] .summary {
display: flex;
}
[data-type="list"] .draft .icon-draft {
background: url("images/draft.png") no-repeat;
background-size: 1.2rem;
visibility: visible;
}
[data-type="list"] .is-draft .pack-end {
opacity: 0.5;
}
[data-type="list"] .is-draft .threadlist-item-title {
font-style: italic;
color: #5f5f5f;
}
/* Override time margin for flex layout */
[data-type="list"] .summary time {
display: inline-block;
margin-inline-end: 1rem;
}
[data-type="list"] .unread .summary time {
color: #1D8399;
}
/* Empty Inbox layout */
#threads-no-messages {
background: url("images/SMS_200x200_bubble.png")
no-repeat center;
background-size: 20rem;
overflow-y: auto;
overflow-x: hidden;
height: calc(100% - 5rem);
width: 100%;
}
#no-result-message {
position: relative;
top: 50%;
width: 27rem;
padding: 2rem 0.5rem;
margin: -8rem auto;
border-top: .1rem solid rgba(96,96,96,.5);
line-height: 3rem;
text-align: start;
font-weight: 300;
font-size: 2.2rem;
color: #606060;
}
#threads-no-messages, .threadlist-is-empty #threads-container {
display: none;
}
/* Using !important to override scoped style in gaia-header component. */
#threads-settings-button, .threadlist-is-empty #threads-options-button {
display: none !important;
}
/* Using flex to center the content. */
.threadlist-is-empty #threads-settings-button {
display: flex !important;
}
.threadlist-is-empty #threads-no-messages {
display: block;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

View file

@ -0,0 +1,134 @@
/*
Styles for new message
*/
section[role="region"].new > gaia-header h1 {
pointer-events: none;
}
.new .article-list {
z-index: 5;
padding: 0;
}
.new .flex-composer {
position: relative;
top: 4rem; /* 4rem is the default size of the recipient panel */
height: calc(100% - 4rem);
}
.subheader menu {
position: relative;
flex-shrink: 0;
width: 5rem;
height: 4rem;
}
/* Render this container outside of the document flow so that it does not
* effect the height of the containing subheader, and therefore does not effect
* the height of the Compose field. */
.subheader .overlay {
position: absolute;
width: 100%;
}
.multiline-recipients-mode.new .flex-composer {
top: 5.5rem;
height: calc(100% - 5.5rem);
}
.new #messages-container {
flex-basis: 0;
}
/* TODO: Remove this once panel markup split */
#messages-contact-pick-button {
display: none;
}
.new #messages-contact-pick-button {
display: block;
width: 5rem;
height: 4rem;
}
#messages-contact-pick-button .icon-user {
position: absolute;
top: 0.6rem;
offset-inline-end: 1rem;
display: block;
width: 2.8rem;
height: 2.8rem;
border-radius: 50%;
color: #575757;
background-color: #FFF;
}
#messages-contact-pick-button .icon-user:after {
content: "+";
position: absolute;
display: block;
width: 2.8rem;
height: 2.8rem;
line-height: 2.8rem;
font-size: 2.1rem;
font-weight: 500;
text-align: center;
}
#messages-contact-pick-button .icon-user:active {
color: #FFF;
background-color: #29bbb5;
}
section[role="region"].new .subheader {
/* The subheader contains the recipients part and because of the fact that it
* has "overflow: visible" we can see it. We should revise it.
*/
height: 0;
}
/** To be seen below the To: field when we are creating a new message **/
.new .overlay {
/* recipient's panel default size; be careful as this value depends on To:
* field height */
top: 4rem;
}
.new.multiline-recipients-mode .overlay {
top: 5.5rem;
}
#messages-recipient-suggestions {
position: absolute;
/* 9rem (5 rem from app header + 4rem from "To" panel heights) */
top: 9rem;
overflow-y: auto;
max-height: calc(100% - 9rem);
width: 100%;
}
.recipient-suggestions-list {
border-bottom: 0.1rem solid #E7E7E7;
}
.multiline-recipients-mode #messages-recipient-suggestions {
top: 10.5rem;
max-height: calc(100% - 10.5rem);
}
#messages-recipient-suggestions li {
pointer-events: auto;
}

View file

@ -0,0 +1,180 @@
html[dir="ltr"] [dir] { text-align: left; }
html[dir="rtl"] [dir] { text-align: right; }
#messages-to-field {
position: relative;
/* To be above .article-list's with z-index = 5 */
z-index: 6;
display: none;
min-height: 5.5rem;
color: black;
font-size: 1.465rem;
background-color: #f2f2f2;
transition: transform 0.2s ease-in-out 0s;
transform: translateY(calc(-100% + 4rem));
}
.multiline-recipients-mode #messages-to-field {
transform: translateY(calc(-100% + 5.5rem));
}
section[role="region"].new #messages-to-field {
display: flex;
align-items: flex-end;
}
section[role="region"].new #messages-to-field.multiline {
transform: translateY(0);
}
#to-label {
flex-shrink: 0;
height: 4rem;
padding-inline-start: 1.5rem;
padding-inline-end: 0.7rem;
line-height: 4rem;
font-weight: 200;
font-size: 1.465rem;
color: #000;
}
#messages-recipients-list-container {
min-height: 4rem;
min-width: 0;
box-sizing: border-box;
flex-grow: 1;
}
#messages-recipients-list {
box-sizing: border-box;
min-height: 4rem;
padding-top: 0;
padding-inline-end: 0.6rem;
padding-bottom: 0.6rem;
padding-inline-start: 0;
display: flex;
align-items: center;
flex-wrap: wrap;
}
/*
Used by Recipients.View to display multi or single line.
Do not remove.
*/
#messages-recipients-list.singleline {
overflow-y: hidden;
}
#messages-recipients-list.multiline {
/* max-height = viewport - header - minimum composer - list margin */
max-height: calc(100vh - 5rem - 4rem - 0.6rem);
margin-bottom: 0.6rem;
padding-bottom: 0;
overflow-y: scroll;
}
/**
* BiDi note: all [contenteditable] blocks must carry a `dir="auto"` attribute,
* so we cannot use *-inline-{start,end} properties on them, because the
* direction of a dir="auto" element (and therefore the position of start/end)
* depends on its content. We want our margin/padding to depend on the document
* direction, not element's content.
*/
.recipient[contenteditable] {
overflow: hidden;
margin: 0.6rem 0 0;
display: block;
/* override BB selector */
height: auto;
width: auto;
}
html[dir="ltr"] .recipient[contenteditable] { margin-right: 0.6rem; }
html[dir="rtl"] .recipient[contenteditable] { margin-left: 0.6rem; }
.recipient[contenteditable].email {
background: #fff url(images/icons/icon_sms_compose_email.png) no-repeat;
background-size: 1.7rem;
}
/* see BiDi note above */
html[dir="ltr"] .recipient[contenteditable].email {
padding-left: 3.8rem;
background-position: left 1.5rem center;
}
html[dir="rtl"] .recipient[contenteditable].email {
padding-right: 3.8rem;
background-position: right 1.5rem center;
}
#messages-recipients-list > .recipient[contenteditable=true] {
flex-grow: 1;
height: 2.8rem;
line-height: 2.8rem;
border-radius: 0;
word-wrap: unset;
/* Building blocks override (bug xxx) */
background: none;
color: #000;
}
#messages-recipients-list > .recipient[contenteditable=true]:focus {
/**
* To workaround bug 1103011, we just collapse editable recipient on focus so
* that caret position looks correct for the user, while in fact it is not
* true - caret is still in wrong position, but zero-width editable element is
* aligned correctly inside parent.
*/
flex-grow: unset;
}
.recipient[contenteditable=true]:empty:not(:last-of-type) {
display: none;
}
/**
* Disable padding and give negative margin-end on empty contenteditable
* recipients when they do not have focus. This allows the Recipients List
* to intelligently collapse when the final row contains no "assimilated"
* (i.e. non-contenteditable) recipients. */
.recipient[contenteditable=true]:empty {
padding: 0;
}
/**
* We don't use margin-inline-end here because of the bug 1103011, as currently
* directionality of empty contenteditable isn't inherited from parent, but
* always LTR, so margin-inline-end equals to margin-right in both LTR and RTL
* modes. So to align it correctly we should override margin depending on text
* direction of parent element manually.
*/
html[dir="ltr"] .recipient[contenteditable=true]:empty {
margin-right: -0.5rem;
}
html[dir="rtl"] .recipient[contenteditable=true]:empty {
margin-left: -0.5rem;
}
/* see BiDi note above */
html[dir="ltr"] .recipient[contenteditable=true]:focus,
html[dir="ltr"] .recipient[contenteditable=true]:hover,
html[dir="ltr"] .recipient[contenteditable=true]:empty:focus {
margin-right: 0.5rem;
padding-right: 0.3rem;
}
html[dir="rtl"] .recipient[contenteditable=true]:focus,
html[dir="rtl"] .recipient[contenteditable=true]:hover,
html[dir="rtl"] .recipient[contenteditable=true]:empty:focus {
margin-left: 0.5rem;
padding-left: 0.3rem;
}

View file

@ -0,0 +1,196 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*global ActivityClient,
App,
Attachment,
Draft,
Drafts,
MessageManager,
Navigation,
Settings,
Utils
*/
/*exported ActivityHandler */
'use strict';
/**
* Describes available data types that can be associated with the activities.
* @enum {string}
*/
const ActivityDataType = {
IMAGE: 'image/*',
AUDIO: 'audio/*',
VIDEO: 'video/*',
URL: 'url',
VCARD: 'text/vcard'
};
var ActivityHandler = {
init: function() {
ActivityClient.init(App.instanceId);
ActivityClient.on(
'new-activity-request', this._onNewActivity.bind(this)
);
ActivityClient.on(
'share-activity-request', this._onShareActivity.bind(this)
);
},
_onNewActivity: function newHandler(activityData) {
var viewInfo = {
body: activityData.body,
number: activityData.target || activityData.number,
threadId: null
};
var threadPromise;
var focus = '';
if (viewInfo.number) {
// It's reasonable to focus on composer if we already have some phone
// number or even contact to fill recipients input
focus = 'composer';
/* If we have a body, we're always redirecting to the new message view,
* because we don't want to overwrite any possible draft from an existing
* conversation. */
if (!viewInfo.body) {
// try to get a thread from number
// if no thread, promise is rejected and we try to find a contact
threadPromise = MessageManager.findThreadFromNumber(viewInfo.number)
.then((threadId) => viewInfo.threadId = threadId)
// case no contact and no thread id: gobble the error
.catch(() => {});
}
}
return (threadPromise || Promise.resolve()).then(
() => this.toView(viewInfo, focus)
);
},
_onShareActivity: function shareHandler(activityData) {
var dataToShare = null;
switch(activityData.type) {
case ActivityDataType.AUDIO:
case ActivityDataType.VIDEO:
case ActivityDataType.IMAGE:
var attachments = activityData.blobs.map(function(blob, idx) {
var attachment = new Attachment(blob, {
name: activityData.filenames[idx],
isDraft: true
});
return attachment;
});
var size = attachments.reduce(function(size, attachment) {
if (attachment.type !== 'img') {
size += attachment.size;
}
return size;
}, 0);
if (size > Settings.mmsSizeLimitation) {
Utils.alert({
id: 'attached-files-too-large',
args: {
n: activityData.blobs.length,
mmsSize: (Settings.mmsSizeLimitation / 1024).toFixed(0)
}
}).then(() => ActivityClient.postResult());
return;
}
dataToShare = attachments;
break;
case ActivityDataType.URL:
dataToShare = activityData.url;
break;
// As for the moment we only allow to share one vcard we treat this case
// in an specific block
case ActivityDataType.VCARD:
dataToShare = new Attachment(activityData.blobs[0], {
name: activityData.filenames[0],
isDraft: true
});
break;
default:
ActivityClient.postError(
'Unsupported activity data type: ' + activityData.type
);
return;
}
if (!dataToShare) {
ActivityClient.postError('No data to share found!');
return;
}
return this.toView({ body: dataToShare });
},
/**
* Store a draft for this message, and returns a draftId.
*
* @param {Object} message Message to save
* @param {Number} message.threadId Conversation where this messages is
* located.
* @param {String} message.number The recipient as a phone number or email
* address.
* @param {(String|Attachment|Array.<Attachment>)} [message.body] The data to
* add to the message's body.
* @returns {Promise.<Number>} The new draft id.
*/
_storeDraftFromMessage(message) {
var content = message.body || null;
var isSimpleContent = !content || typeof content === 'string';
var isEmailAddress = message.number && Utils.isEmailAddress(message.number);
var draft = new Draft({
threadId: message.threadId || null,
recipients: message.number && [message.number] || null,
content: !content || Array.isArray(content) ? content : [content],
type: isSimpleContent && !isEmailAddress ? 'sms' : 'mms'
});
return Drafts.request().then(
() => Drafts.add(draft).store()
).then(() => draft.id);
},
/**
* Delivers the user to the correct view based on the params provided in the
* "message" parameter.
* @param {{number: string, body: string, threadId: number}} message It's
* either a message object that belongs to a thread, or a message object from
* the system. "number" is a string phone number to pre-populate the
* recipients list with, "body" is an optional body to preset the compose
* input with, "threadId" is an optional threadId corresponding to a new or
* existing thread.
* @param {string?} focus indicates which of 'composer' or 'recipients' field
* we need to focus when we navigate to Thread panel.
*/
toView: function ah_toView(message, focus) {
return Utils.onceDocumentIsVisible().then(() => {
// If we have appropriate thread then let's forward user there, otherwise
// open new message composer.
if (message.threadId) {
return Navigation.toPanel('thread', {
id: message.threadId,
focus
});
}
return this._storeDraftFromMessage(message).then(
(draftId) => Navigation.toPanel('composer', { draftId, focus })
);
});
}
};

View file

@ -0,0 +1,89 @@
/*global MozActivity */
(function(exports) {
'use strict';
function handleActivity(activity, onsuccess, onerror) {
// Note: the MozActivity is intentionally constructed in the caller and
// passed down to this function in order to make it possible to pass an
// inline option to its constructor in the caller for the sake of static
// analysis tools. Please do not change that!
if (typeof onsuccess === 'function') {
activity.onsuccess = onsuccess;
}
activity.onerror = typeof onerror === 'function' ? onerror : function(error) {
console.warn('Unhandled error spawning activity; ' + error.message);
};
}
var ActivityPicker = {
dial: function ap_call(number, onsuccess, onerror) {
handleActivity(new MozActivity({
name: 'dial',
data: {
type: 'webtelephony/number',
number: number
}
}), onsuccess, onerror);
},
email: function ap_email(email, onsuccess, onerror) {
handleActivity(new MozActivity({
name: 'new',
data: {
type: 'mail',
URI: 'mailto:' + email
}
}), onsuccess, onerror);
},
url: function ap_browse(url, onsuccess, onerror) {
handleActivity(new MozActivity({
name: 'view',
data: {
type: 'url',
url: url
}
}), onsuccess, onerror);
},
createNewContact: function ap_createNewContact(contactProps) {
handleActivity(new MozActivity({
name: 'new',
data: {
type: 'webcontacts/contact',
params: contactProps
}
}));
},
addToExistingContact: function ap_addToExistingContact(contactProps) {
handleActivity(new MozActivity({
name: 'update',
data: {
type: 'webcontacts/contact',
params: contactProps
}
}));
},
viewContact: function ap_viewContact(contactProps) {
handleActivity(new MozActivity({
name: 'open',
data: {
type: 'webcontacts/contact',
params: contactProps
}
}));
},
openSettings: function ap_openSettings(onsuccess, onerror) {
handleActivity(new MozActivity({
name: 'configure',
data: {
target: 'device',
section: 'messaging'
}
}), onsuccess, onerror);
}
};
exports.ActivityPicker = ActivityPicker;
}(this));

View file

@ -0,0 +1,3 @@
;;;(function(exports){'use strict';const APPLICATION_READY_CLASS_NAME='js-app-ready';const INSTANCE_ID=Date.now();var app={isReady:function app_isReady(){return document.body.classList.contains(APPLICATION_READY_CLASS_NAME);},setReady:function app_setReady(){if(this.isReady()){throw new Error('Application should be marked as ready only once!');}
document.body.classList.add(APPLICATION_READY_CLASS_NAME);if(navigator.mozAudioChannelManager){navigator.mozAudioChannelManager.volumeControlChannel='notification';}},whenReady:function whenReady(){if(this.isReady()){return Promise.resolve();}
return new Promise(function(resolve,reject){var bodyClassObserver=new MutationObserver(function(){try{if(this.isReady()){bodyClassObserver.disconnect();resolve();}}catch(e){reject(e);}}.bind(this));bodyClassObserver.observe(document.body,{attributes:true,attributeFilter:['class']});}.bind(this));},get instanceId(){return INSTANCE_ID;}};exports.App=app;})(this);

View file

@ -0,0 +1 @@
;;(function(){'use strict';Startup.init();})(self);

View file

@ -0,0 +1,308 @@
/*global SharedComponents, Template, Utils, Settings */
'use strict';
(function(exports) {
var FLAVORS = {
/*
* renderAll controls whether all phone numbers sohuld be rendered for this
* contact
*
* shouldHighlight controls whether we should generate a markup allowing
* highlighting depending on the input
*
* template "main" will get the following parameters:
* - name: the contact's title
* - number: the phone number
* - nameHTML: same than name if shouldHighlight is false, otherwise contains
* markup with the highlighting
* - phoneDetailsHTML: phone details label (type, number, carrier), also if
* shouldHighlight is true, then number is wrapped into highlighting markup
* - photoHTML: result of the "photo" template, if present
* - type: phone number type information
* - carrier: phone number carrier information
*
* template "photo" will get the following parameters:
* - photoURL: the URL of the contact's first photo
*
*/
suggestion: {
renderAll: true,
shouldHighlight: true,
templates: {
main: 'contact-suggestion-tmpl'
}
},
suggestionUnknown: {
renderAll: true,
shouldHighlight: true,
templates: {
main: 'contact-suggestion-unknown-tmpl'
}
},
prompt: {
renderAll: false,
shouldHighlight: false,
templates: {
main: 'contact-prompt-tmpl',
photo: 'contact-photo-tmpl'
}
},
'group-view': {
renderAll: false,
shouldHighlight: false,
templates: {
main: 'contact-suggestion-tmpl',
photo: 'contact-photo-tmpl'
}
},
'report-view': {
renderAll: false,
shouldHighlight: false,
templates: {
main: 'contact-report-tmpl',
photo: 'contact-photo-tmpl'
}
}
};
/* let's lazy load generic templates */
var templates = {};
function getTemplate(id) {
if (!templates[id]) {
templates[id] = Template(id);
}
return templates[id];
}
var highlightTemplateId = 'contact-highlight-tmpl';
// Warning: always feed this function an already escaped data
function highlight(escapedData, regexpArray) {
// When rendering a suggestion, we highlight the matched substring.
// The approach is to escape the html and the search string, and
// then replace on all "words" (whitespace bounded strings) with
// the substring run through the highlight template.
// this variable will allow skipping to the next regexp
var matchFound;
var template = getTemplate(highlightTemplateId);
function loopReplaceFn(match) {
matchFound = true;
// The match is safe, because splitData[i] is derived from
// escapedData
return template.interpolate({
str: match
}, {
safe: ['str']
});
}
var splitData = escapedData.split(/\s+/);
// For each "word"
for (var i = 0; i < splitData.length; i++) {
matchFound = false;
// Loop over search term regexes
for (var k = 0; !matchFound && k < regexpArray.length; k++) {
splitData[i] = splitData[i].replace(regexpArray[k], loopReplaceFn);
}
}
return splitData.join(' ');
}
var ContactRenderer = exports.ContactRenderer = function constructor(opts) {
if (!opts) {
throw new Error('ContactRenderer constructor needs an options object');
}
this.opts = opts;
this.templates = {};
for (var key in opts.templates) {
var id = opts.templates[key];
this.templates[key] = Template(id);
}
if (this.templates.photo) {
this.opts.renderPhoto = true;
}
};
ContactRenderer.prototype = {
// Returns true when a contact has been rendered
// Returns false when no contact has been rendered
render: function cr_render(renderOpts) {
/**
*
* params {
* contact:
* A contact object.
*
* input:
* Any input value associated with the contact,
* possibly from a search or similar operation.
*
* skip:
* An array of phone numbers that should not be rendered
*
* target:
* Parent node to append the rendered node to
*
* infoBlock:
* Node that will be appended to the node selected by
* infoBlockParentSelector
*
* infoBlockParentSelector:
* Parent element selector for appending infoBlock as child
*
* }
*
*/
var contact = renderOpts.contact;
var input = renderOpts.input;
input = input && input.trim();
var target = renderOpts.target;
var shouldHighlight = this.opts.shouldHighlight;
var renderAll = this.opts.renderAll;
var renderPhoto = this.opts.renderPhoto;
var skip = renderOpts.skip || [];
var block = renderOpts.infoBlock;
var parentSelector = renderOpts.infoBlockParentSelector;
// we can't do much without a contact
if (!contact) {
return false;
}
// don't render if there is no phone number
// TODO: Add email checking support for MMS
var hasTel = contact.tel && contact.tel.length;
var hasEmail = contact.email && contact.email.length;
if (!hasTel && !(Settings.supportEmailRecipient && hasEmail)) {
return false;
}
// We search on the escaped HTML via a regular expression
var escaped = Utils.escapeRegex(Template.escape(input));
var escsubs = escaped.split(/\s+/);
// Build a list of regexes used for highlighting suggestions
var regexps = {
name: escsubs.map(function(k) {
// String matches occur on the beginning of a "word" to
// maintain parity with the contact search algorithm which
// only considers left aligned exact matches on words
return new RegExp('^' + k, 'gi');
}),
number: escsubs.map(function(k) {
// Match any of the search terms with the number
return new RegExp(k, 'ig');
}),
email: escsubs.map(function(k) {
// Match any of the search terms with the email
return new RegExp('^' + k, 'gi');
})
};
var include = renderPhoto ? { photoURL: true } : null;
var addresses = [];
if (contact.tel && contact.tel.length) {
addresses = addresses.concat(contact.tel);
}
if (Settings.supportEmailRecipient &&
contact.email && contact.email.length) {
addresses = addresses.concat(contact.email);
}
var details = Utils.getContactDetails(
addresses[0].value, contact, include
);
var tempDiv = document.createElement('div');
addresses.forEach(function(current) {
// Only render a contact's tel value entry for the _specified_
// input value when not rendering all values. If the tel
// record value _doesn't_ match, then continue.
//
if (!renderAll && !Utils.probablyMatches(current.value, input)) {
return;
}
// If rendering for contact search result suggestions, don't
// render contact tel records for values that are already
// selected as recipients. This comparison should be safe,
// as the value in this.recipients.numbers comes from the same
// source that current.value comes from.
if (renderAll && skip.indexOf(current.value) > -1) {
return;
}
var data = Utils.getDisplayObject(details.title, current);
var props = ['name', 'number'];
if (Settings.supportEmailRecipient) {
props.push('email');
}
props.forEach(function(key) {
var escapedData = Template.escape(data[key]);
if (shouldHighlight) {
escapedData = highlight(escapedData, regexps[key]);
}
data[key + 'HTML'] = escapedData;
});
data.phoneDetailsHTML = SharedComponents.phoneDetails({
number: data.numberHTML,
type: data.type,
carrier: data.carrier
}, {
safe: ['number']
}).toString();
// Render contact photo only for specific flavor
data.photoHTML = renderPhoto && details.photoURL ?
this.templates.photo.interpolate() : '';
// Interpolate HTML template with data and inject.
// Known "safe" HTML values will not be re-sanitized.
tempDiv.innerHTML = this.templates.main.interpolate(data, {
safe: ['nameHTML', 'phoneDetailsHTML', 'srcAttr', 'photoHTML']
});
var element = tempDiv.firstElementChild;
var blockParent = element.querySelector(parentSelector);
if (blockParent) {
blockParent.appendChild(block);
}
if (data.photoHTML) {
var contactPhoto = element.querySelector('.contact-photo');
contactPhoto.style.backgroundImage =
'url("' + encodeURI(details.photoURL) + '")';
Utils.asyncLoadRevokeURL(details.photoURL);
}
target.appendChild(element);
tempDiv.textContent = '';
}, this);
return true;
}
};
ContactRenderer.flavor = function(flavor) {
// we could possibly cache instances here, if we need more performance
return new ContactRenderer(FLAVORS[flavor]);
};
})(window);

View file

@ -0,0 +1,15 @@
;;;(function(exports){'use strict';var unknownNumbers=[];var filterFns={contains:function(a,b){a=a.toLowerCase();b=b.toLowerCase();return a.includes(b);},equality:function(a,b){a=a.toLowerCase();b=b.toLowerCase();return a===b;}};function isMatch(contact,criteria,filterFn){var found={};outer:for(var i=0,ilen=criteria.terms.length;i<ilen;i++){var term=criteria.terms[i];for(var j=0,jlen=criteria.fields.length;j<jlen;j++){var field=criteria.fields[j];if(!contact[field]){continue;}
for(var k=0,klen=contact[field].length;k<klen;k++){var value=contact[field][k];if(typeof value.value!=='undefined'){value=value.value;}
if((found[term]=filterFn(value.trim(),term))){continue outer;}}}}
return Object.keys(found).every(function(key){return found[key];});}
var rspaces=/\s+/;var Contacts={findBy:function contacts_findBy(filter,callback){var lower=[];var filterValue=(filter.filterValue||'').trim();var terms;if(!navigator.mozContacts||!filterValue.length){return Promise.resolve([]);}
terms=filterValue.split(rspaces);filter.filterValue=terms.length===1?terms[0]:terms.reduce(function(initial,term){lower.push(term.toLowerCase());return term.length>initial.length?term:initial;},'');if(filter.filterValue.length<3){filter.filterLimit=10;}
lower.splice(lower.indexOf(filter.filterValue.toLowerCase()),1);lower.push.apply(lower,terms);return navigator.mozContacts.find(filter).then((contacts)=>{var results;if(terms.length>1){var fields=['tel','givenName','familyName'];if(Settings.supportEmailRecipient){fields.push('email');}
var criteria={fields:fields,terms:lower};results=contacts.filter((contact)=>isMatch(contact,criteria,filterFns.contains));}else{results=contacts;}
return results;}).catch((e)=>{console.error('Got an error while looking up contacts.',e.name,e);return[];});},findByString:function contacts_findByString(filterValue){return Promise.all([this.findContactByString(filterValue),this.findByUnknown(filterValue)]).then(([known,unknown])=>{return(known||[]).concat(unknown);});},findContactByString:function contacts_findContactByString(filterValue){var props=['tel','givenName','familyName'];if(Settings.supportEmailRecipient){props.push('email');}
return this.findBy({filterBy:props,filterOp:'contains',filterValue:filterValue});},findByUnknown:function contacts_findByUnknown(filterValue){var list=[];for(var i=0,length=unknownNumbers.length;i<length;i++){if(list.length>2){break;}
var num=unknownNumbers[i];if(num.includes(filterValue)){var obj={name:[num],tel:[{value:num}],source:'unknown'};list.push(obj);}}
return Promise.resolve(list);},findExact:function contacts_findExact(filterValue){return this.findBy({filterBy:['givenName','familyName'],filterOp:'contains',filterValue:filterValue}).then((contacts)=>{var contact=contacts.length?contacts[0]:null;var criteria={fields:['name'],terms:[filterValue]};var isExact=false;if(contact){isExact=isMatch(contact,criteria,filterFns.equality);}
return isExact?[contact]:[];});},findByPhoneNumber:function contacts_findByPhone(filterValue){return this.findBy({filterBy:['tel'],filterOp:'match',filterValue:filterValue.replace(/\s+/g,'')}).then((contacts)=>{if(contacts.length){return contacts;}
var defer=Utils.Promise.defer();fb.getContactByNumber(filterValue,function fbByPhone(fbContact){defer.resolve(fbContact?[fbContact]:[]);},function error_fbByPhone(err){if(err.name!=='DatastoreNotFound'){console.error('Error while retrieving fb by phone: ',err.name);}
defer.resolve([]);});return defer.promise;});},findByAddress:function contacts_findByAddress(fValue){if(Settings.supportEmailRecipient&&Utils.isEmailAddress(fValue)){return this.findExactByEmail(fValue);}else{return this.findByPhoneNumber(fValue);}},findExactByEmail:function contacts_findExactByEmail(fValue){return this.findBy({filterBy:['email'],filterOp:'equals',filterValue:fValue});},addUnknown:function addUnknown(number){var index=unknownNumbers.indexOf(number);if(index===-1){unknownNumbers.push(number);}},clearUnknown:function clearUnknown(){unknownNumbers.length=0;},getunknownLength:function getunknownLength(){return unknownNumbers.length;}};exports.Contacts=Contacts;}(this));

View file

@ -0,0 +1,165 @@
/*global WeakMap */
/* exported Dialog */
(function(exports) {
'use strict';
/*
Generic confirm screen. Only 'cancel/default' is mandatory.
Text fields are localized using commonly defined parameter structure:
https://developer.mozilla.org/en-US/Firefox_OS/Developing_Gaia/
localization_code_best_practices#Writing_APIs_that_operate_on_L10nIDs + 'raw'
can be Node aw well.
The constructor parameter should follow the following structure:
{
title: { raw: 'Non localizable title' },
body: {
id: 'localizableStringWithArgument',
args: { n: count }
},
classes: ['specific-class'], // optional
options: {
// Cancel is a mandatory option. You need to define at least the text.
cancel: {
text: 'localizableCancelLabel'
},
confirm: {
text: 'localizableRemoveLabel',
method: function(params) {
fooFunction(params);
},
params: [arg1, arg2,....],
className: 'optionalClassName'
}
}
*/
// helper to localize an element given parameters
function createLocalizedElement(tagName, valueL10n) {
var element = document.createElement(tagName);
// if we passed an l10nId, use the l10n `setAttributes' method
if (typeof valueL10n === 'string') {
element.setAttribute('data-l10n-id', valueL10n);
} else if (valueL10n.id) {
document.l10n.setAttributes(element, valueL10n.id, valueL10n.args);
// if we passed in a HTML Fragment, it is already localized
} else if (valueL10n.raw && valueL10n.raw.nodeType) {
element.appendChild(valueL10n.raw);
// otherwise - stuff text in here...
} else {
element.textContent = valueL10n.raw;
}
return element;
}
var Dialog = function(params) {
// We need, at least, one cancel option string
if (!params || !params.options || !params.options.cancel) {
return;
}
var handlers = new WeakMap();
// Create the structure
this.form = document.createElement('form');
this.form.dataset.type = 'confirm';
this.form.setAttribute('role', 'dialog');
this.form.tabIndex = -1;
// Pick up option_menu.css styling
this.form.dataset.subtype = 'menu';
if (params.classes) {
this.form.classList.add(...params.classes);
}
var infoSection = document.createElement('section');
infoSection.appendChild(createLocalizedElement('h1', params.title));
infoSection.appendChild(createLocalizedElement('p', params.body));
this.form.appendChild(infoSection);
// Adding options. In this case we have a maximum of 2, with different styles
// per button
var menu = document.createElement('menu');
// Default button (Cancel button). It's mandatory
var cancelOption = params.options.cancel;
var cancelButton = createLocalizedElement('button', cancelOption.text);
handlers.set(cancelButton, cancelOption);
// If we have only button, let's mark it as recommended action.
if (!params.options.confirm) {
cancelButton.className = 'recommend';
}
menu.appendChild(cancelButton);
if (params.options.confirm) {
var confirmOption = params.options.confirm;
var confirmButton = createLocalizedElement('button', confirmOption.text);
confirmButton.className = params.options.confirm.className || 'recommend';
handlers.set(confirmButton, confirmOption);
menu.appendChild(confirmButton);
}
this.form.addEventListener('submit', function(event) {
event.preventDefault();
});
this.form.addEventListener('transitionend', function(event) {
var form = event.target;
if (!form.classList.contains('visible') && form.parentNode) {
form.remove();
} else {
// Focus form for accessibility
form.focus();
}
document.body.classList.remove('dialog-animating');
});
menu.addEventListener('click', function(event) {
var action = handlers.get(event.target);
if (!action) {
return;
}
if (action.method) {
// Delegate operation to target method. This allows
// for a custom "Cancel" to be provided by calling program
action.method.apply(null, action.params || []);
}
// Hide action menu when click is received
this.hide();
}.bind(this));
// Appending the action menu to the form
this.form.appendChild(menu);
};
// We prototype functions to show/hide the UI of action-menu
Dialog.prototype.show = function() {
// Remove the focus to hide the keyboard asap
document.activeElement && document.activeElement.blur();
if (!this.form.parentNode) {
document.body.appendChild(this.form);
// Flush style on form so that the show transition plays once we add
// the visible class.
this.form.clientTop;
}
this.form.classList.add('visible');
document.body.classList.add('dialog-animating');
};
Dialog.prototype.hide = function() {
this.form.classList.remove('visible');
};
exports.Dialog = Dialog;
// end global closure
}(this));

View file

@ -0,0 +1,111 @@
/*global Dialog, Settings*/
/* exported ErrorDialog */
(function(exports) {
'use strict';
/*
Error confirm screen. Handling all the message error code and corresponding
description/behavior.
Options object can contain the following properties:
* recipients: ['foo', 'bar'] - to show the recipient information, used only
if errorDescription.showRecipient is equal to "true";
* confirmHandler: function() - to be executed if user selects confirm
action in the error dialog, used only if errorDescription.executeHandler
is equal to "true".
}
*/
function ErrorDialog(errorDescription, options) {
var prefix = errorDescription.prefix;
if (!prefix) {
throw new Error('Prefix is required!');
}
var dialogOptions = {
cancel: {
text: prefix + 'BtnOk'
}
};
if (options && options.confirmHandler && errorDescription.executeHandler) {
dialogOptions.confirm = {
text: prefix + 'Confirm',
method: options.confirmHandler
};
}
var bodyL10nId = prefix + 'Body';
var body = null;
var additionalClasses = [];
if (errorDescription.showRecipient) {
var fragment = document.createDocumentFragment();
var mainText = document.createElement('span');
document.l10n.setAttributes(
mainText, bodyL10nId,
{ n: options.recipients.length }
);
fragment.appendChild(mainText);
var list = options.recipients.reduce((list, recipient) => {
var li = document.createElement('li');
li.textContent = recipient;
list.appendChild(li);
return list;
}, document.createElement('ul'));
fragment.appendChild(list);
body = { raw: fragment };
additionalClasses.push('error-dialog-show-recipient');
} else if (errorDescription.showDsdsStatus) {
var messageBodyParams = null;
var mmsServiceId = Settings.mmsServiceId;
if (mmsServiceId !== null) {
// mmsServiceId = 0 => default Service is SIM 1
// mmsServiceId = 1 => default Service is SIM 2
switch (mmsServiceId) {
case 0:
messageBodyParams = {
activeSimId: '1',
nonActiveSimId: '2'
};
break;
case 1:
messageBodyParams = {
activeSimId: '2',
nonActiveSimId: '1'
};
break;
default:
console.error(
'MMS ServiceId(' + mmsServiceId + ') is not matched!'
);
}
} else {
console.error('Settings unavailable');
}
body = {
id: bodyL10nId,
args: messageBodyParams
};
}
Dialog.call(this, {
title: prefix + 'Title',
body: body || bodyL10nId,
options: dialogOptions,
classes: ['error-dialog-' + prefix, 'error-dialog', ...additionalClasses]
});
}
ErrorDialog.prototype = Object.create(Dialog.prototype);
exports.ErrorDialog = ErrorDialog;
}(this));

View file

@ -0,0 +1,81 @@
/* exported Errors */
(function(exports) {
'use strict';
/**
* Maps error code to error description object:
* prefix: used as prefix for error message text/title l10nId,
* showRecipient: indicates whether recipients info should be included into
* error message displayed to the user;
* showDsdsStatus: indicates whether DSDS status should be included into
* error message displayed to the user;
* executeHandler: indicates whether confirmation handler passed to the
* error dialog should be executed once user selects confirm action.
* @type {Map.<string, Object>}
*/
const ERRORS = new Map();
function initErrorsMap() {
ERRORS.set('NoSimCardError', {
prefix: 'sendMissingSimCard'
});
ERRORS.set('RadioDisabledError', {
prefix: 'sendFlightMode'
});
ERRORS.set('FdnCheckError',{
prefix: 'fdnError',
showRecipient: true
});
ERRORS.set('NonActiveSimCardError', {
prefix: 'switchSimToRetrieve',
showDsdsStatus: true,
executeHandler: true
});
ERRORS.set('NoSignalError', {
prefix: 'sendNoSignalError'
});
ERRORS.set('NotFoundError', {
prefix: 'sendNotFoundError'
});
ERRORS.set('InvalidAddressError', {
prefix: 'sendInvalidAddressError'
});
ERRORS.set('SimNotMatchedError', {
prefix: 'simNotMatchedError'
});
ERRORS.set('NonActiveSimCardToSendError', {
prefix: 'switchSimToSend',
showDsdsStatus: true,
executeHandler: true
});
ERRORS.set('RadioDisabledToDownloadError', {
prefix: 'radioDisabledToDownload'
});
// The error type below will be represented as "GeneralError" in dialog for
// the rest of error codes (InternalError and etc.)
ERRORS.set('UnknownError', {
prefix: 'sendDefaultError'
});
}
exports.Errors = {
get: function(errorCode) {
if (!ERRORS.size) {
initErrorsMap();
}
return ERRORS.get(errorCode) || ERRORS.get('UnknownError');
}
};
})(window);

View file

@ -0,0 +1,8 @@
;;;(function(exports){'use strict';var channel=null;const BROADCAST_CHANNEL_NAME='messages-channel';const ALLOWED_EVENTS=['draft-saved','draft-deleted'];function postMessage(name,parameters){if(!channel){throw new Error('Channel is not created!');}
if(!name){throw new Error('Name should be defined!');}
channel.postMessage(parameters!==undefined?{name:name,parameters:parameters}:{name:name});}
const Dispatcher=EventDispatcher.mixin({connect:function(){if(channel){return;}
channel=new BroadcastChannel(BROADCAST_CHANNEL_NAME);channel.addEventListener('message',onMessage);exports.addEventListener('unload',()=>this.disconnect());},disconnect:function(){if(!channel){return;}
channel.close();channel.removeEventListener('message',onMessage);channel=null;}},ALLOWED_EVENTS);const originalEmitter=Dispatcher.emit.bind(Dispatcher);Dispatcher.emit=function(eventName,parameters){if(ALLOWED_EVENTS.indexOf(eventName)<0){throw new Error('Event "'+eventName+'" is not allowed!');}
postMessage(eventName,parameters);};function onMessage(message){try{originalEmitter(message.data.name,message.data.parameters);}catch(error){console.error('Error while emitting event "%s" with parameters "%s"',message.data&&message.data.name,JSON.stringify(message.data&&message.data.parameters),error);}}
exports.InterInstanceEventDispatcher=Dispatcher;})(window);

View file

@ -0,0 +1,93 @@
/* exported LocalizationHelper */
(function(exports) {
'use strict';
/**
* Look for attachment iframes and localize them - l20n.js doesn't do this
* XXX: remove once bug 1020130 is fixed
*/
function localizeAttachmentIFrames() {
var attachmentContainers = Array.from(document.querySelectorAll(
'iframe.attachment-container'
));
return Promise.all(
attachmentContainers.map(localizeAttachmentIFrame)
);
}
/**
* Localize an attachment iframe
*/
function localizeAttachmentIFrame(attachmentContainer) {
var doc = attachmentContainer.contentDocument;
doc.documentElement.lang = document.documentElement.getAttribute('lang');
doc.documentElement.dir = document.documentElement.getAttribute('dir');
return document.l10n.translateFragment(doc.body);
}
/**
* Look for time elements with format and re-translate the date message.
* More complex because the argument to the l10n string is itself a formatted
* date using l10n.
* @param {boolean} isTimeFormatChanged Indicates that time format has been
* changed and we need to update only elements with time aware formats.
*/
function localizeDateTime(isTimeFormatChanged) {
var dateTimeElements = document.querySelectorAll('.l10n-contains-date');
Array.forEach(dateTimeElements, function(element) {
if (!element.dataset.l10nDate) {
return;
}
var formatL10nId = element.dataset.l10nDateFormat;
if (!formatL10nId) {
return;
}
formatL10nId = JSON.parse(formatL10nId);
if (isTimeFormatChanged && !formatL10nId.hour) {
return;
}
formatL10nId.hour12 = navigator.mozHour12;
var formatter = new Intl.DateTimeFormat(
navigator.languages, formatL10nId
);
var localeData = formatter.format(new Date(+element.dataset.l10nDate));
if (element.hasAttribute('data-l10n-id') &&
element.hasAttribute('data-l10n-args')) {
var l10nAttrs = document.l10n.getAttributes(element);
l10nAttrs.args.date = localeData;
document.l10n.setAttributes(element, l10nAttrs.id, l10nAttrs.args);
} else {
element.textContent = localeData;
}
});
}
exports.LocalizationHelper = {
init: function () {
function localize() {
localizeDateTime();
return localizeAttachmentIFrames();
}
// After startup, call localize every time the language changes
document.addEventListener('DOMRetranslated', localize);
// This event is fired when time format (12h/24h) is changed
window.addEventListener(
'timeformatchange',
localizeDateTime.bind(null, /* isTimeFormatChanged */ true)
);
// Call localize during startup
return document.l10n.ready.then(localize);
}
};
})(window);

View file

@ -0,0 +1,58 @@
;;;(function(window){'use strict';var NavigationFactory=window.NavigationFactory=function(window){const debug=[()=>{},(arg1,...args)=>console.log('[Navigation] '+arg1,...args),(arg1,...args)=>console.log('[Navigation] (%s) '+arg1,Date.now(),...args),][0];const NGA_VIEWS=Object.freeze([Object.freeze({name:'thread',url:'views/conversation/index.html',behavior:'ConversationView',previous:'thread-list'}),Object.freeze({name:'thread-list',url:'views/inbox/index.html',behavior:'InboxView'}),Object.freeze({name:'report-view',behavior:'ReportView',previous:'thread',partOf:'thread'}),Object.freeze({name:'composer',behavior:'ConversationView',previous:'thread-list',partOf:'thread'}),Object.freeze({name:'group-view',behavior:'GroupView',previous:'thread',partOf:'thread'})]);const OA_VIEWS=Object.freeze([Object.freeze({name:'thread-list',url:'index.html',behavior:'InboxView'}),Object.freeze({name:'thread',behavior:'ConversationView',previous:'thread-list',partOf:'thread-list'}),Object.freeze({name:'composer',behavior:'ConversationView',previous:'thread-list',partOf:'thread-list'}),Object.freeze({name:'group-view',behavior:'GroupView',previous:'thread',partOf:'thread-list'}),Object.freeze({name:'report-view',behavior:'ReportView',previous:'thread',partOf:'thread-list'})]);const VIEWS=isUsingOldArchitecture()?OA_VIEWS:NGA_VIEWS;const priv={pendingInit:Symbol('pendingInit')};var currentState;var navigationTransition;function setLocation(url){debug('Setting location',url);window.location.assign(url);}
function isUsingOldArchitecture(){debug('isUsingOldArchitecture()');var pathname=window.location.pathname;var result=(pathname==='/'||pathname==='/index.html');debug('isUsingOldArchitecture() =>',result);return result;}
function findViewNameFromHash(hash){debug('findViewNameFromHash(%s)',hash);if(!hash||!hash.startsWith('#/')){return null;}
var endOfViewName=hash.indexOf('?');if(endOfViewName<0){endOfViewName=undefined;}
return hash.slice(2,endOfViewName);}
function findViewFromLocation(location){debug('findViewFromLocation(%s)',location.href);var pathName=location.pathname;if(pathName.endsWith('/')){pathName+='index.html';}
var hash=location.hash;var viewNameFromHash=findViewNameFromHash(hash);var viewFromHash=findViewFromName(viewNameFromHash);if(viewNameFromHash&&!viewFromHash){console.error('We found no view from the hash %s',hash);return null;}
var viewFromPath=VIEWS.find((view)=>pathName.endsWith('/'+view.url))||null;if(!viewFromPath){console.error('We found no view from the path %s',pathName);return null;}
if(viewFromHash&&viewFromHash.partOf&&viewFromHash.partOf!==viewFromPath.name){console.error('The view %s found from the hash %s is not part of view %s',viewFromHash.name,hash,viewFromPath.name);viewFromHash=null;}
return viewFromHash||viewFromPath;}
function findViewFromName(name){debug('findViewFromName(%s)',name);var view=VIEWS.find((view)=>view.name===name)||null;debug('findViewFromName() => %s',view&&view.name);return view;}
function findContainerView(view){debug('findContainerView(%s)',view.name);if(view.partOf){debug('findContainerView: %s is part of view %s',view.name,view.partOf);view=findViewFromName(view.partOf);}
debug('findContainerView() => %s',view.name);return view;}
var readyDefer=Utils.Promise.defer();function waitForReady(){if(!readyDefer){return Promise.reject(new Error('waitForReady was called but status is ready already.'));}
return readyDefer.promise;}
function startNavigationFromCurrentLocation(){var location=window.location;debug('startNavigationFromCurrentLocation(), location=%s',location);var newPanel=findViewFromLocation(location);if(!newPanel){return Promise.reject(new Error('Couldn\'t find a view from location '+location.href));}
var args=Utils.params(decodeURIComponent(location.hash));debug('startNavigationFromCurrentLocation:','found panel',newPanel.name,'with args',args);return startNavigation({newPanel,args});}
function startNavigation(opts){var{newPanel,args,partial}=opts;args=args||{};debug('startNavigation(newPanel=%s, partial=%s)',newPanel&&newPanel.name,partial);if(navigationTransition&&(!navigationTransition.partial||partial)){debug('startNavigation: previous navigation exists, deferring.');return navigationTransition.defer.promise.then(()=>startNavigation(opts));}
var newContainerView=newPanel&&findContainerView(newPanel);var containerView=currentState&&findContainerView(currentState.panel);var leaveDocument=currentState&&containerView!==newContainerView;if(newPanel&&!leaveDocument){var behaviorObject=window[newPanel.behavior];var viewReady=behaviorObject&&(typeof behaviorObject.isReady!=='function'||behaviorObject.isReady());if(!viewReady){return waitForReady().then(()=>startNavigation(opts));}}
document.activeElement.blur();if(!navigationTransition){navigationTransition={stepsRun:[],defer:Utils.Promise.defer()};}
navigationTransition.partial=partial;navigationTransition.leaveDocument=leaveDocument;if('meta'in args){throw new Error('Arguments cannot have a `meta` property.');}
navigationTransition.transitionArgs={meta:{next:newPanel?{panel:newPanel.name,args}:null,prev:currentState?{panel:currentState.panel.name,args:currentState.args}:null}};Object.assign(navigationTransition.transitionArgs,args);navigationTransition.states={from:currentState,to:{panel:newPanel,args}};return Promise.resolve();}
function setFutureState(){debug('setFutureState()');currentState=navigationTransition.states.to;}
function endNavigation(){debug('endNavigation()');if(navigationTransition){navigationTransition.defer.resolve();navigationTransition=null;}}
function onNavigationError(e=new Error('RejectedPromise')){console.error('Navigation error',e);endNavigation();return Promise.reject(e);}
function catchStepError(e){if(e){console.error('[Navigation] got an exception: (%s) %s',e,e.stack&&e.stack.replace(/\n/g,'|'));}else{debug('[Navigation] got a rejected promise');}}
function executeNavigationStep(stepName){debug('executeNavigationStep(%s)',stepName);if(!navigationTransition){return Promise.reject(new Error('Trying to execute the navigation step '+stepName+' but no transition is started yet.'));}
if(navigationTransition.stepsRun.indexOf(stepName)>=0){debug('executeNavigationStep: step %s has already run.',stepName);return Promise.resolve();}
var stateKey=stepName.endsWith('Leave')?'from':'to';var state=navigationTransition.states[stateKey];if(!state){debug('Nothing to execute for step',stepName);return Promise.resolve();}
var panel=state.panel;var resultPromise=Promise.resolve();var viewObject=window[panel.behavior];if(viewObject[stepName]){resultPromise=resultPromise.then(()=>viewObject[stepName](navigationTransition.transitionArgs));}
return resultPromise.then(()=>{navigationTransition.stepsRun.push(stepName);debug(`Finished executeNavigationStep(${stepName})`);});}
var previousAnimationDefer;function waitForPanelAnimation(panelElement){if(previousAnimationDefer){previousAnimationDefer.reject(new Error('A new animation started'));}
var defer=Utils.Promise.defer();previousAnimationDefer=defer;var timeout;function onAnimationEnd(e){panelElement.removeEventListener('animationend',onAnimationEnd);clearTimeout(timeout);debug('animationend',e&&e.type);defer.resolve();}
panelElement.addEventListener('animationend',onAnimationEnd);timeout=setTimeout(onAnimationEnd,500);return defer.promise.then(()=>previousAnimationDefer=null);}
function switchPanel(){debug('switchPanel()');if(!navigationTransition){throw new Error('Trying to switch panel but no transition is started yet.');}
var from=navigationTransition.states.from;var to=navigationTransition.states.to;var oldView;var oldPanelElement;if(from){oldView=from.panel;oldPanelElement=document.querySelector(`.panel-${oldView.behavior}`);if(!oldPanelElement){throw new Error('Couldn\'t find the container element for view '+oldView.name);}}
var newView=to.panel;if(!newView){throw new Error('Trying to switch panel, but no new view is defined!');}
var newPanelElement=document.querySelector(`.panel-${newView.behavior}`);if(!newPanelElement){return Promise.reject(new Error('Couldn\'t find the container element for view '+newView.name));}
if(newPanelElement===oldPanelElement){return Promise.resolve();}
var isGoingBack=oldView&&oldView.previous===newView.name;newPanelElement.style='';newPanelElement.classList.remove('panel-hidden');newPanelElement.classList.add('panel-active');var doSlideAnimation=from&&from.panel&&from.panel.behavior!==newView.behavior;var doFadeInAnimation=!from||!from.panel;var animationPromise;if(doSlideAnimation){var shouldGoRight=isGoingBack^(document.dir==='rtl');newPanelElement.style.animationName=shouldGoRight?'left-to-current':'right-to-current';oldPanelElement.style.animationName=shouldGoRight?'current-to-right':'current-to-left';}else if(doFadeInAnimation){newPanelElement.style.animationName='fade-in';}
if(doFadeInAnimation||doSlideAnimation){animationPromise=waitForPanelAnimation(newPanelElement).catch(()=>{}).then(()=>{if(oldPanelElement){oldPanelElement.style='';}
newPanelElement.style='';});}
return(animationPromise||Promise.resolve()).then(()=>{if(oldPanelElement){oldPanelElement.classList.remove('panel-active');oldPanelElement.setAttribute('aria-hidden','true');}
newPanelElement.setAttribute('aria-hidden','false');});}
function onHashChange(){debug('onHashChange, location=',window.location);startNavigationFromCurrentLocation().then(()=>executeNavigationStep('beforeLeave')).then(()=>executeNavigationStep('beforeEnter').catch(catchStepError)).then(setFutureState).then(switchPanel).then(()=>executeNavigationStep('afterEnter').catch(catchStepError)).then(()=>executeNavigationStep('afterLeave').catch(catchStepError)).then(endNavigation).then(()=>Navigation.emit('navigated')).catch(onNavigationError);}
function attachHistoryListener(){window.addEventListener('hashchange',onHashChange);}
function detachHistoryListener(){window.removeEventListener('hashchange',onHashChange);}
var Navigation={back(){debug('back()');return startNavigation({partial:true}).then(()=>executeNavigationStep('beforeLeave')).then(()=>window.history.back()).then(()=>navigationTransition.defer.promise,onNavigationError);},toPanel(viewName,args){debug('toPanel(%s)',viewName);var view=findViewFromName(viewName);if(!view){return Promise.reject(new Error(`Couldn't find view ${viewName}`));}
if(this.isCurrentPanel(viewName,args)){debug('View %s is the current view.',viewName);return Promise.resolve();}
if(navigationTransition){debug('toPanel: previous navigation exists, deferring.');return navigationTransition.defer.promise.then(()=>this.toPanel(viewName,args));}
this[priv.pendingInit]=false;var hash='#';if(view.partOf){hash+='/'+viewName;}
hash=Utils.url(hash,args);return startNavigation({newPanel:view,args,partial:true}).then(()=>executeNavigationStep('beforeLeave')).then(()=>{var nextLocation;if(navigationTransition.leaveDocument){var containerView=findContainerView(view);nextLocation='/'+containerView.url+hash;}else{nextLocation=hash;}
setLocation(nextLocation);}).then(()=>navigationTransition.defer.promise,onNavigationError);},init(){debug('init()');attachHistoryListener();if(navigator.mozHasPendingMessage('notification')){this[priv.pendingInit]=true;return Promise.reject();}
this[priv.pendingInit]=false;return startNavigationFromCurrentLocation().then(setFutureState).then(()=>executeNavigationStep('beforeEnter').catch(catchStepError)).then(switchPanel).then(()=>executeNavigationStep('afterEnter').catch(catchStepError)).then(endNavigation).then(()=>Navigation.emit('navigated')).catch(onNavigationError);},[priv.pendingInit]:false,hasPendingInit(){return this[priv.pendingInit];},cleanup(){detachHistoryListener();},isDefaultPanel(){return currentState?!currentState.panel.partOf:!findViewNameFromHash(window.location.hash);},isCurrentPanel(panelName,args){debug('isCurrentPanel(%s)',panelName);if(!currentState||!panelName){return false;}
if(panelName!==currentState.panel.name){return false;}
if(!args){return true;}
var currentArgs=currentState.args||{};for(var arg in args){if(args[arg]!=currentArgs[arg]){return false;}}
return true;},setReady(){debug('setReady()');Array.from(document.querySelectorAll('.panel-hidden')).forEach((panel)=>{panel.classList.remove('panel-hidden');});readyDefer.resolve();readyDefer=null;}};return EventDispatcher.mixin(Navigation,['navigated']);};window.Navigation=NavigationFactory(window);})(window);

View file

@ -0,0 +1,101 @@
/*global SettingsURL */
(function(exports) {
'use strict';
var SETTINGS = {
notificationVolume: 'audio.volume.notification',
vibration: 'vibration.enabled',
ringtone: 'notification.ringtone'
};
function entry(key, value) {
if (urls[key]) {
return new SettingsURL().set(value);
}
return value;
}
// one day we'll be able to use ES6 initializers instead
var urls = {};
urls[SETTINGS.ringtone] = 1;
function getSetting(key) {
if (!navigator.mozSettings) {
return Promise.reject(new Error('The mozSettings API is not available.'));
}
var request = navigator.mozSettings.createLock().get(key);
return new Promise((resolve, reject) => {
request.onsuccess = function() {
resolve(entry(key, request.result[key]));
};
request.onerror = function() {
reject(new Error('Error while retrieving ' + key));
};
});
}
function getSettings(settings) {
return Promise.all(
settings.map(getSetting)
).catch((e) => {
// catch and log errors
console.error('Error while retrieving settings', e.message, e);
return settings.map(() => null);
}).then((results) => {
return settings.reduce((result, setting, i) => {
result[setting] = results[i];
return result;
}, {});
});
}
function ringtone(ringtoneFile) {
var ringtonePlayer = new Audio();
ringtonePlayer.src = ringtoneFile;
ringtonePlayer.mozAudioChannelType = 'notification';
ringtonePlayer.play();
window.setTimeout(function smsRingtoneEnder() {
ringtonePlayer.pause();
ringtonePlayer.removeAttribute('src');
ringtonePlayer.load();
}, 2000);
}
function vibrate() {
// vibration only works when App is in the foreground
if (document.hidden) {
window.addEventListener('visibilitychange', function waitOn() {
window.removeEventListener('visibilitychange', waitOn);
navigator.vibrate([200, 200, 200, 200]);
});
} else {
navigator.vibrate([200, 200, 200, 200]);
}
}
var Notify = {
ringtone: function notification_ringtone() {
return getSettings(
[SETTINGS.notificationVolume, SETTINGS.ringtone]
).then((settings) => {
if (settings[SETTINGS.notificationVolume] &&
settings[SETTINGS.ringtone]) {
ringtone(settings[SETTINGS.ringtone]);
}
});
},
vibrate: function notification_vibrate() {
return getSettings([SETTINGS.vibration]).then((settings) => {
if (settings[SETTINGS.vibration]) {
vibrate();
}
});
}
};
exports.Notify = Notify;
}(this));

View file

@ -0,0 +1,138 @@
/*exported SelectionHandler */
(function(exports) {
'use strict';
const REQUIRED_KEYS = [
// elements
'container',
'checkUncheckAllButton',
// methods
'updateSelectionStatus',
'getIdIterator',
'isInEditMode'
];
var SelectionHandler = function constructor(options) {
// Set the necessary properties and method for selection module and throw
// error if not existed in options.
if (typeof options !== 'object') {
throw new Error('options should be a valid object');
}
REQUIRED_KEYS.forEach((key) => {
if (options[key] === undefined) {
throw new Error('Selection options does not provide required key ' + key);
}
this[key] = options[key];
});
this.selected = new Set();
this.container.addEventListener(
'click', this.onSelected.bind(this)
);
this.checkUncheckAllButton.addEventListener(
'click', this.toggleCheckedAll.bind(this)
);
};
SelectionHandler.prototype = {
get selectedCount() {
return this.selected.size;
},
get selectedList() {
return Array.from(this.selected);
},
get inputIdSet() {
var itemIdSet = new Set();
for(var id of this.getIdIterator()) {
itemIdSet.add(id.toString());
}
return itemIdSet;
},
allSelected: function sel_allSelected() {
/*jshint unused: false */
var allItemCount = 0;
var selectedCount = this.selectedCount;
for (var id of this.getIdIterator()) {
if (++allItemCount > selectedCount) {
return false;
}
}
return true;
},
onSelected: function sel_onSelected(event) {
if (!this.isInEditMode()) {
return;
}
var target = event.target;
var value = target.value;
var existed = this.selected.has(value);
if (target.checked && !existed) {
this.selected.add(value);
} else if (!target.checked && existed) {
this.selected.delete(value);
} else {
// Don't emit event if no selection change
return;
}
this.updateSelectionStatus();
},
select: function sel_select(id) {
this.selected.add('' + id);
},
unselect: function sel_unselect(id) {
this.selected.delete('' + id);
},
// Update check status of input elements in the container
updateCheckboxes: function sel_updateCheckboxes() {
var inputs = this.container.querySelectorAll('input[type=checkbox]');
var length = inputs.length;
for (var i = 0; i < length; i++) {
inputs[i].checked = this.selected.has(inputs[i].value);
}
},
// if no message or few are checked : select all the messages
// and if all messages are checked : deselect them all.
toggleCheckedAll: function sel_toggleCheckedAll() {
var allInputs = this.inputIdSet;
var allSelected = (this.selected.size === allInputs.size);
if (allSelected) {
this.selected.clear();
} else {
this.selected = allInputs;
}
this.updateCheckboxes();
this.updateSelectionStatus();
},
cleanForm: function sel_cleanForm() {
// Reset all inputs
this.selected.clear();
this.updateCheckboxes();
// Reset vars for deleting methods
this.updateSelectionStatus();
}
};
exports.SelectionHandler = SelectionHandler;
}(this));

View file

@ -0,0 +1,9 @@
;;;'use strict';var Settings={SERVICE_ID_KEYS:{mmsServiceId:'ril.mms.defaultServiceId',smsServiceId:'ril.sms.defaultServiceId'},READ_AHEAD_THREADS_KEY:'ril.sms.maxReadAheadEntries',MMS_SIZE_OVERHEAD:5*1024,_serviceIds:null,supportEmailRecipient:true,maxConcatenatedMessages:10,mmsSizeLimitation:295*1024,mmsServiceId:null,smsServiceId:null,init:function settings_init(){var keyHandlerSet={'dom.mms.operatorSizeLimitation':this.initMmsSizeLimitation.bind(this),'operatorResource.sms.maxConcat':this.initSmsMaxConcatenatedMsg.bind(this)};var settings=navigator.mozSettings;var conns=navigator.mozMobileConnections;function setHandlerMap(key){var req=settings.createLock().get(key);req.onsuccess=function settings_getSizeSuccess(){var handler=keyHandlerSet[key];handler(req.result[key]);};}
this._serviceIds=[];if(!settings){return;}
if(conns&&conns.length>1){for(var prop in this.SERVICE_ID_KEYS){var setting=this.SERVICE_ID_KEYS[prop];keyHandlerSet[setting]=this.initServiceId.bind(this,setting,prop);}
for(var i=0,l=conns.length;i<l;i++){this._serviceIds.push(conns[i].iccId);}}
for(var key in keyHandlerSet){setHandlerMap(key);}},initSmsMaxConcatenatedMsg:function initSmsMaxConcatenatedMsg(num){if(num&&!isNaN(num)){this.maxConcatenatedMessages=num;}},initMmsSizeLimitation:function initMmsSizeLimitation(size){if(size&&!isNaN(size)){this.mmsSizeLimitation=size-this.MMS_SIZE_OVERHEAD;}},initServiceId:function initMmsServiceId(settingName,propName,id){if(id!==undefined){Settings[propName]=id;}
navigator.mozSettings.addObserver(settingName,function(e){Settings[propName]=e.settingValue;});},setMmsSimServiceId:function setSimServiceId(id){navigator.mozSettings.createLock().set({'ril.mms.defaultServiceId':id,'ril.data.defaultServiceId':id});},switchMmsSimHandler:function switchSimHandler(targetId){var conn=window.navigator.mozMobileConnections[targetId];return new Promise(function(resolve,reject){if(conn){if(conn.data.state==='registered'){resolve();}else{conn.addEventListener('datachange',function onDataChange(){if(conn.data.state==='registered'){conn.removeEventListener('datachange',onDataChange);resolve();}});this.setMmsSimServiceId(targetId);}}else{reject('Invalid connection');}}.bind(this));},isDualSimDevice:function isDualSimDevice(){return this._serviceIds&&this._serviceIds.length>1;},hasSeveralSim:function hasSeveralSim(){if(!this.isDualSimDevice()){return false;}
var simCount=this._serviceIds.reduce(function(simCount,iccId){return iccId===null?simCount:simCount+1;},0);return simCount>1;},getServiceIdByIccId:function getServiceIdByIccId(iccId){if(!this._serviceIds){return null;}
var index=this._serviceIds.indexOf(iccId);return index>-1?index:null;},setReadAheadThreadRetrieval:function(value){if(!navigator.mozSettings){return;}
var setting={};setting[this.READ_AHEAD_THREADS_KEY]=value;navigator.mozSettings.createLock().set(setting);}};

View file

@ -0,0 +1,3 @@
;'use strict';(function(exports){var cachedTemplates=new Map();var getTemplate=function(id){if(!cachedTemplates.has(id)){cachedTemplates.set(id,Template(id));}
return cachedTemplates.get(id);};var SharedComponents={phoneDetails:function(details,interpolationOptions){if(!details){throw new Error('Phone details are required!');}
return getTemplate('phone-details-tmpl').prepare({type:details.type||'',number:details.number||'',carrier:details.carrier||'',hasTypeClass:!!details.type?'has-phone-type':'',hasCarrierClass:!!details.carrier?'has-phone-carrier':''},interpolationOptions);}};exports.SharedComponents=SharedComponents;})(window);

View file

@ -0,0 +1,18 @@
/* global ActivityShim,
MozMobileConnectionsShim,
MozMobileMessageShim,
MozSettingsShim
*/
(function(exports) {
'use strict';
exports.bootstrap = function(appInstanceId) {
// We can't handle system messages inside iframe right now, so we access
// parent window here. This should be removed once bug 818000 is resolved.
ActivityShim.init(appInstanceId, parent.mozSetMessageHandler);
MozSettingsShim.init(navigator.mozSettings);
MozMobileMessageShim.init(appInstanceId, navigator.mozMobileMessage);
MozMobileConnectionsShim.init(appInstanceId, navigator.mozMobileConnections);
};
})(self);

View file

@ -0,0 +1,165 @@
/* global asyncStorage, Promise, TaskRunner */
/* exported SilentSms */
'use strict';
var SilentSms = (function () {
var NO_ALARM_ID = -1;
// The maximum amount of time that the SmsManager waits for a message before
// disabling automatically the silent mode is five minutes.
var SERVICE_TIMEOUT = 5 * 60 * 1000;
var taskQueue;
function init() {
taskQueue = new TaskRunner();
if (!window.navigator.mozSetMessageHandler) {
// bailout if we're in Firefox
return;
}
// Msg from Usage (activation / deactivation)
window.navigator.mozSetMessageHandler('connection',
function onConnected(request) {
if (request.keyword !== 'costcontrolSmsQuery') {
return;
}
var port = request.port;
port.onmessage = function onReceivedMessage(evt) {
var data = evt.data;
switch (data.action) {
case 'enable':
taskQueue.push(function() {
return SilentSms.enableSilentModeFor(data.smsNumbers, data.type)
.then(updateUsageSilentSms)
.then(port.postMessage.bind(port, 'silentSMSEnabled'));
});
break;
case 'disable':
taskQueue.push(function() {
return SilentSms.disableSilentModeFor(data.type)
.then(updateUsageSilentSms)
.then(port.postMessage.bind(port, 'silentSMSDisabled'));
});
break;
}
};
}
);
// Deactivated silentSms mode by alarm
window.navigator.mozSetMessageHandler('alarm', function _onAlarm(alarm) {
if (alarm.data.alarmType === 'silentSmsTimeout') {
SilentSms.disableSilentModeFor(alarm.data.type)
.then(updateUsageSilentSms);
}
});
}
function getUsageSilentSms() {
return new Promise(function(resolve, reject) {
asyncStorage.getItem('usageSilentSms', function(usageSilentSms) {
resolve(usageSilentSms || {});
});
});
}
function removeAlarmTimeout(alarmId) {
// There is already an alarm, remove it
if (alarmId && alarmId !== NO_ALARM_ID) {
navigator.mozAlarms.remove(alarmId);
}
}
function addAlarmTimeout(type, delay) {
return new Promise(function doEnablingSilentMode(resolve, reject) {
var date = new Date();
date.setTime(date.getTime() + delay);
var request = navigator.mozAlarms.add(date, 'ignoreTimezone',
{alarmType: 'silentSmsTimeout',
type: type });
request.onsuccess = function _alarmSet(evt) {
var alarmId = evt.target.result;
resolve(alarmId);
};
request.onerror = function _onError() {
var errorMsg = 'Failed to set timeout for ' + type + 'request';
reject(new Error(errorMsg));
};
});
}
// The enabling silentMode process consist on two steps:
// 1. Enabling silentMode for the provided number
// 2. Activating alarm for TimeOut
function enableSilentModeFor(smsNumbers, type) {
function _removeAlarmTimeout(usageSilentSms) {
if (usageSilentSms && usageSilentSms[type] &&
usageSilentSms[type].alarmId !== NO_ALARM_ID) {
removeAlarmTimeout(usageSilentSms[type].alarmId);
}
}
function handleErrorAddingAlarm(error) {
console.error(error.toString());
return NO_ALARM_ID;
}
return getUsageSilentSms().then(function(usageSilentSms) {
function refreshUsageSilentSms(alarmId) {
usageSilentSms[type] = {
smsNumbers: smsNumbers,
alarmId: alarmId
};
return usageSilentSms;
}
_removeAlarmTimeout(usageSilentSms);
return addAlarmTimeout(type, SERVICE_TIMEOUT)
.catch(handleErrorAddingAlarm)
.then(refreshUsageSilentSms);
});
}
function disableSilentModeFor(type) {
return getUsageSilentSms().then(function(usageSilentSms) {
if (usageSilentSms && usageSilentSms[type] &&
usageSilentSms[type].alarmId !== NO_ALARM_ID) {
removeAlarmTimeout(usageSilentSms[type].alarmId);
}
usageSilentSms[type] = null;
return usageSilentSms;
});
}
function updateUsageSilentSms(usageSilentSms) {
usageSilentSms.smsIndex = getIndex(usageSilentSms);
asyncStorage.setItem('usageSilentSms', usageSilentSms);
}
function getIndex(usageSilentSms) {
var newIndex = [];
Object.keys(usageSilentSms).forEach(function(silentRequest) {
var silentSmsRequest = usageSilentSms[silentRequest];
if (silentSmsRequest && silentSmsRequest.hasOwnProperty('smsNumbers')) {
newIndex = newIndex.concat(silentSmsRequest.smsNumbers);
}
});
return newIndex;
}
function checkSilentModeFor(smsNumber) {
return taskQueue.push(function() {
function checkSilentMode(usageSilentSms) {
return usageSilentSms && usageSilentSms.smsIndex &&
usageSilentSms.smsIndex.indexOf(smsNumber) !== -1;
}
return getUsageSilentSms().then(checkSilentMode);
});
}
return {
init: init,
enableSilentModeFor: enableSilentModeFor,
disableSilentModeFor: disableSilentModeFor,
checkSilentModeFor: checkSilentModeFor
};
}());

View file

@ -0,0 +1,29 @@
;(function(){'use strict';var unsafeFilenamePattern=/[^a-zA-Z0-9_#.()?&%-]/g;var encoder=new TextEncoder('UTF-8');function SMIL_generateSlides(data,slide,slideIndex){const DURATION=5000;var id;var blobType;var media='';var text='';var name='';if(slide.blob){blobType=Utils.typeFromMimeType(slide.blob.type);if(blobType){var tagName=tagNameFromBlobType(blobType);var region='region="Image"';if(tagName==='ref'){region='';}
name=slide.name.substr(slide.name.lastIndexOf('/')+1);name=name.replace(unsafeFilenamePattern,'#');name=SMIL_generateUniqueLocation(data,name);media='<'+tagName+' src="'+name+'" '+region+'/>';data.attachments.push({id:'<'+name+'>',location:name,content:slide.blob});}}
if(slide.text){id='text_'+slideIndex+'.txt';text='<text src="'+id+'" region="Text"/>';data.attachments.push({id:'<'+id+'>',location:id,content:new Blob([encoder.encode(slide.text)],{type:'text/plain'})});}
data.parts.push('<par dur="'+DURATION+'ms">'+media+text+'</par>');return data;}
function tagNameFromBlobType(blobType){var out;switch(blobType){case'vcard':out='ref';break;default:out=blobType;}
return out;}
function SMIL_generateUniqueLocation(data,location){var extension,name,result;var FILENAME_LIMIT=40;function SMIL_uniqueLocationMatches(attachment){return attachment.location===result;}
var index=location.lastIndexOf('.');if(index===-1){name=location;extension='';}else{extension=location.slice(index);name=location.slice(0,index);}
if(name.length+extension.length>FILENAME_LIMIT){name=name.slice(0,FILENAME_LIMIT-extension.length);}
result=name+extension;var duplicateIndex=2;while(data.attachments.some(SMIL_uniqueLocationMatches)){var truncIndex=0;result=name+'_'+duplicateIndex++ +extension;while(result.length>FILENAME_LIMIT){duplicateIndex=2;name=name.slice(0,--truncIndex);result=name+extension;}}
return result;}
window.SMIL={parse:function SMIL_parse(message){var smil=message.smil;var attachments=message.attachments;var slides=[];var attachmentsNotFound=false;var smilMismatched=false;var doc;var parTags;function readTextBlob(blob){if(!blob){return Promise.resolve('');}
var defer=Utils.Promise.defer();var textReader=new FileReader();textReader.onload=function(event){defer.resolve(event.target.result);};textReader.onerror=function(event){console.error('Error reading text blob');defer.resolve('');};textReader.readAsText(blob,'UTF-8');return defer.promise;}
function findAttachment(name){var index=0;var length=attachments.length;name=name.replace(/^cid:/,'');for(;index<length;index++){if(attachments[index].location===name||attachments[index].id==='<'+name+'>'){return attachments[index];}}
return null;}
function convertWbmpToPng(slide){var defer=Utils.Promise.defer();var reader;slide.name=slide.name.slice(0,-5)+'.png';reader=new FileReader();reader.onload=function(event){WBMP.decode(event.target.result,function callback(blob){slide.blob=blob;defer.resolve();});};reader.onerror=function(event){console.error('Error reading text blob');defer.resolve();};reader.readAsArrayBuffer(slide.blob);return defer.promise;}
function SMIL_parseWithoutSMIL(attachment,idx){var blob=attachment.content;if(!blob){return Promise.resolve();}
var result;var type=Utils.typeFromMimeType(blob.type);if(type==='text'&&blob.type==='text/plain'){result=readTextBlob(blob).then((text)=>{slides[idx]={text:text};});}else if(type){var slide={name:attachment.location,blob:attachment.content};if(slide.name&&slide.name.slice(-5)==='.wbmp'){result=convertWbmpToPng(slide);}
slides[idx]=slide;}
return result||Promise.resolve();}
function SMIL_parseHandleParTag(par,index){if(attachmentsNotFound){return;}
var mediaElements=par.querySelectorAll('img, video, audio, ref');var textElement=par.querySelector('text');var attachment,src;var promises=Array.from(mediaElements).map((element)=>{var result;var slide={};src=element.getAttribute('src');attachment=findAttachment(src);if(attachment){slide={name:attachment.location,blob:attachment.content};slides.push(slide);if(slide.name&&slide.name.slice(-5)==='.wbmp'){result=convertWbmpToPng(slide);}}else{attachmentsNotFound=true;}
return result||Promise.resolve();});if(textElement){src=textElement.getAttribute('src');attachment=findAttachment(src);if(attachment){var slide=slides[slides.length-1];if(!slide||typeof slide.text!=='undefined'){slide={};slides.push(slide);}
slide.text='';promises.push(readTextBlob(attachment.content).then((text)=>slide.text=text));}else{attachmentsNotFound=true;}}
return Promise.all(promises);}
var result;if(smil){doc=(new DOMParser()).parseFromString(smil,'application/xml');parTags=doc.documentElement.getElementsByTagName('par');var elements=Array.reduce(parTags,(count,par)=>count+par.childElementCount,0);if(elements!==attachments.length){smilMismatched=true;}else{result=Promise.all(Array.from(parTags).map(SMIL_parseHandleParTag));}}
if(!smil||attachmentsNotFound||!slides.length||smilMismatched){slides=Array(attachments.length);result=Promise.all(attachments.map(SMIL_parseWithoutSMIL));}
return(result||Promise.resolve()).then(()=>slides);},generate:function SMIL_generate(slides){const HEADER='<head><layout>'+'<root-layout width="320px" height="480px"/>'+'<region id="Image" left="0px" top="0px"'+' width="320px" height="320px" fit="meet"/>'+'<region id="Text" left="0px" top="320px"'+' width="320px" height="160px" fit="meet"/>'+'</layout></head>';var data=slides.reduce(SMIL_generateSlides,{attachments:[],parts:[]});data.smil='<smil>'+HEADER+'<body>'+
data.parts.join('')+'</body></smil>';delete data.parts;return data;}};})();

View file

@ -0,0 +1,3 @@
;'use strict';(function(exports){var debug=0?(arg1,...args)=>console.log('[Startup] '+arg1,...args):()=>{};var Startup=exports.Startup={_lazyLoadScripts:['/shared/js/settings_listener.js','/shared/js/mime_mapper.js','/shared/js/notification_helper.js','/shared/js/option_menu.js','/shared/js/gesture_detector.js','/shared/js/settings_url.js','/shared/js/mobile_operator.js','/shared/js/multi_sim_action_button.js','/shared/js/image_utils.js','/shared/elements/gaia_sim_picker/script.js','/views/shared/js/waiting_screen.js','/views/shared/js/errors.js','/views/shared/js/dialog.js','/views/shared/js/error_dialog.js','/views/conversation/js/link_helper.js','/views/conversation/js/link_action_handler.js','/views/shared/js/contact_renderer.js','/views/shared/js/activity_picker.js','/views/conversation/js/information.js','/views/shared/js/shared_components.js','/views/shared/js/task_runner.js','/views/shared/js/silent_sms.js','/views/conversation/js/recipients.js','/views/conversation/js/attachment.js','/views/conversation/js/attachment_renderer.js','/views/conversation/js/conversation.js','/views/conversation/js/subject_composer.js','/views/conversation/js/compose.js','/views/shared/js/wbmp.js','/views/shared/js/smil.js','/views/shared/js/notify.js','/views/shared/js/activity_handler.js','/views/shared/js/system_message_handler.js','/views/shared/js/localization_helper.js','/lib/bridge/client.js','/services/js/activity/activity_client.js','/services/js/messaging/messaging_client.js','/services/js/moz_mobile_connections/moz_mobile_connections_client.js'],_lazyLoadStyles:['/shared/style/confirm.css','/shared/style/status.css','/shared/style/input_areas.css','/shared/style/progress_activity.css','/views/shared/style/composer.css','/views/conversation/style/message.css','/views/conversation/style/report_view.css','/views/conversation/style/attachment.css','/views/conversation/style/conversation.css','/views/new_message/style/new_message.css','/views/new_message/style/recipients.css'],_lazyLoadInit:function(){var lazyLoadPromise=LazyLoader.load([...this._lazyLoadScripts,...this._lazyLoadStyles]).then(()=>{LocalizationHelper.init();InterInstanceEventDispatcher.connect();SilentSms.init();if(navigator.mozHasPendingMessage('activity')){ActivityHandler.init();}else{SystemMessageHandler.init();}
TimeHeaders.init();ConversationView.init();MessagingClient.init(App.instanceId);MozMobileConnectionsClient.init(App.instanceId);Information.initDefaultViews();Navigation.setReady();window.performance.mark('contentInteractive');Settings.init();window.performance.mark('objectsInitEnd');});return lazyLoadPromise;},init:function(){var loaded=function(){window.removeEventListener('DOMContentLoaded',loaded);window.performance.mark('navigationLoaded');Utils.initializeShimHost(App.instanceId);MessageManager.init();Drafts.init();InboxView.init();Navigation.init();InboxView.once('fully-loaded',()=>{window.performance.mark('fullyLoaded');App.setReady();});InboxView.once('visually-loaded',()=>{window.performance.mark('visuallyLoaded');});if(Navigation.isDefaultPanel()&&!navigator.mozHasPendingMessage('notification')&&!navigator.mozHasPendingMessage('activity')){debug('Rendering threads now.');InboxView.once('visually-loaded',()=>{this._lazyLoadInit();});InboxView.renderThreads();}else{debug('Not using default panel, waiting for navigated event');Navigation.once('navigated',()=>InboxView.renderThreads());this._lazyLoadInit();}
window.performance.mark('navigationInteractive');}.bind(this);window.addEventListener('DOMContentLoaded',loaded);}};Startup.init();})(window);

View file

@ -0,0 +1,348 @@
/*global Contacts,
MessageManager,
Navigation,
NotificationHelper,
Notify,
Settings,
SilentSms,
SMIL,
Utils
*/
/*exported SystemMessageHandler */
(function(exports) {
'use strict';
/**
* Number of milliseconds after which currently acquired wake lock will be
* automatically released.
* @type {Number}
* @const
*/
const AUTO_WAKE_LOCK_UNLOCK_TIMEOUT = 30 * 1000;
const priv = {
wakeLock: Symbol('wakeLock'),
autoWakeLockUnlockTimeout: Symbol('autoWakeLockUnlockTimeout')
};
/**
* Object that is responsible for handling "sms-received" and "notification"
* system messages.
*/
var SystemMessageHandler = {
/**
* Wake lock instance.
* @type {MozWakeLock}
* @private
*/
[priv.wakeLock]: null,
/**
* Timeout that will trigger to release of currently active wake lock.
* @type {Number}
* @private
*/
[priv.autoWakeLockUnlockTimeout]: null,
/**
* Initializes system message handlers, if app is run as activity, no
* system message handlers will be registered.
*/
init() {
if (!navigator.mozSetMessageHandler) {
return;
}
navigator.mozSetMessageHandler(
'sms-received', this.onSmsReceivedSystemMessage.bind(this)
);
navigator.mozSetMessageHandler(
'notification', this.onNotificationSystemMessage.bind(this)
);
},
/**
* Requests application wake lock.
* @private
*/
requestWakeLock() {
this[priv.wakeLock] = navigator.requestWakeLock('cpu');
this[priv.autoWakeLockUnlockTimeout] = setTimeout(
() => this.releaseWakeLock(), AUTO_WAKE_LOCK_UNLOCK_TIMEOUT
);
},
/**
* Releases currently active application wake lock.
* @private
*/
releaseWakeLock() {
var autoWakeLockUnlockTimeout = this[priv.autoWakeLockUnlockTimeout];
if (autoWakeLockUnlockTimeout) {
clearTimeout(autoWakeLockUnlockTimeout);
this[priv.autoWakeLockUnlockTimeout] = null;
}
var wakeLock = this[priv.wakeLock];
if (wakeLock) {
wakeLock.unlock();
this[priv.wakeLock] = null;
}
},
/**
* Prepares notification body for the message
* @param {MozMmsMessage|MozSmsMessage} message Message to prepare
* notification body for.
* @returns {Promise.<{raw: string}|string>} L10n object that will serve as
* notification body.
* @private
*/
getNotificationBody(message) {
// In case of sms, we want to display text content in notification.
if (message.type === 'sms') {
return Promise.resolve({ raw: message.body || '' });
}
var mmsNotificationBodyL10n = null;
// Here we can only have one sender, so deliveryInfo[0].deliveryStatus
// => message status from sender.
if (message.deliveryInfo[0].deliveryStatus !== 'success') {
// If the delivery status is manual/rejected/error, we need to apply
// specific text to notify user that message is not downloaded.
mmsNotificationBodyL10n = 'notDownloaded-title';
} else if (message.subject) {
// If subject exist, we display subject in notification.
mmsNotificationBodyL10n = { raw: message.subject };
}
return mmsNotificationBodyL10n ?
Promise.resolve(mmsNotificationBodyL10n) :
SMIL.parse(message).then((slideArray) => {
// If MMS has at least one non-empty text slide, let's display that
// text in notification otherwise use predefined 'mms message'.
var textSlide = slideArray.find((slide) => !!slide.text);
return textSlide ? { raw: textSlide.text } : 'mms-message';
});
},
/**
* Prepares notification title for the message
* @param {MozMmsMessage|MozSmsMessage} message Message to prepare
* notification title for.
* @returns {Promise.<{raw: string}|{id: string, args: Object}>} L10n object
* that will serve as notification title.
* @private
*/
getNotificationTitle(message) {
return Contacts.findByAddress(message.sender).then((contacts) => {
var sender = contacts.length && contacts[0].name &&
contacts[0].name.length && contacts[0].name[0] ||
message.sender;
if (Settings.hasSeveralSim() && message.iccId) {
return Utils.getSimNameByIccId(message.iccId).then(
(simName) => ({
id: 'dsds-notification-title-with-sim',
args: { sim: simName, sender: sender }
})
);
}
return { raw: sender };
});
},
/**
* Retrieves correct application icon to use within notifications.
* @returns {string} Application icon URI.
* @private
*/
getNotificationIcon() {
return navigator.mozApps.getSelf().then(
(app) => NotificationHelper.getIconURI(app)
);
},
/**
* Processes received class-0 message (flash message).
* @param {MozMmsMessage|MozSmsMessage} message Received class-0 message.
* @returns {Promise.<void>} Promise that is resolved once message is fully
* handled.
* @private
*/
onFlashMessageReceived(message) {
// We have to remove message from DB as it should not be kept in Inbox.
return MessageManager.deleteMessages(message.id).then(
() => navigator.mozApps.getSelf()
).then((app) =>{
app.launch();
Notify.ringtone();
Notify.vibrate();
Utils.alert({ raw: message.body || '' }, { raw: message.sender });
});
},
/**
* Handles received class-1, class-2 or class-3 message.
* @param {MozMmsMessage|MozSmsMessage} message Received message.
* @returns {Promise.<void>} Promise that is resolved once message is fully
* handled.
* @private
*/
onMessageReceived(message) {
// If message type is mms and pending on server, ignore the notification
// because it will be retrieved from server automatically. Handle other
// manual/error status as manual download and dispatch notification.
// Please ref mxr for all the possible delivery status:
// http://mxr.mozilla.org/mozilla-central/source/dom/mms/src/ril/
// MmsService.js#62
// Here we can only have one sender, so deliveryInfo[0].deliveryStatus
// => message status from sender.
if (message.type === 'mms' &&
message.deliveryInfo[0].deliveryStatus === 'pending') {
return;
}
var isInConversation = Navigation.isCurrentPanel(
'thread', { id: message.threadId }
);
// If app is already opened and we're in the right conversation, let's
// just notify user about new message.
if (!document.hidden && isInConversation) {
Notify.ringtone();
Notify.vibrate();
return;
}
return Promise.all([
this.getNotificationTitle(message),
this.getNotificationBody(message),
this.getNotificationIcon()
]).then(([titleL10n, bodyL10n, icon]) => {
// Force the helper to leave close behavior as it is because it should
// be handled in Utils.closeNotificationsForThread later.
return NotificationHelper.send(titleL10n, {
icon: icon,
bodyL10n: bodyL10n,
tag: 'threadId:' + message.threadId,
data: { id: message.id, threadId: message.threadId },
closeOnClick: false
});
}).then((notification) => {
notification.addEventListener(
'click', () => this.onNotificationClicked(message)
);
// Close notification if we are already in conversation view and app
// becomes visible.
if (document.hidden && isInConversation) {
Utils.onceDocumentIsVisible().then(() => notification.close());
}
});
},
/**
* Fires once notification is clicked.
* @param {{ id: string, threadId: string }} notification Notification
* arbitrary data.
* @returns {Promise.<void>} Promise that is resolved once notification
* click is handled.
* @private
*/
onNotificationClicked({ id, threadId }) {
return navigator.mozApps.getSelf().then((app) => {
app.launch();
// If we're currently in the target conversation, just do nothing.
if (Navigation.isCurrentPanel('thread', { id: threadId })) {
return;
}
//Validate if message still exists before opening message thread
//See issue https://bugzilla.mozilla.org/show_bug.cgi?id=837029
return MessageManager.getMessage(id).then(
(message) => Navigation.toPanel('thread', { id: message.threadId }),
() => {
if (Navigation.hasPendingInit()) {
Navigation.init();
}
Utils.alert('deleted-sms');
}
);
});
},
/**
* Handles "sms-received" system message.
* @param {MozMmsMessage|MozSmsMessage} message Received message.
* @returns {Promise.<void>} Promise that is resolved once message is fully
* handled.
* @private
*/
onSmsReceivedSystemMessage(message) {
// Acquire the cpu wake lock when we receive an SMS. This raises the
// priority of this process above vanilla background apps, making it less
// likely to be killed on OOM. It also prevents the device from going to
// sleep before the user is notified of the new message.
//
// We'll release it once we display a notification to the user. We also
// release the lock after 30s, in case we never run the notification code
// for some reason.
this.requestWakeLock();
var handleReceivedMessagePromise;
if (message.messageClass === 'class-0') {
handleReceivedMessagePromise = this.onFlashMessageReceived(message);
} else {
handleReceivedMessagePromise = SilentSms.checkSilentModeFor(
message.sender
).then((isSilent) => {
if (!isSilent) {
return this.onMessageReceived(message);
}
});
}
return handleReceivedMessagePromise.then(() => this.releaseWakeLock());
},
/**
* Handles "notification" system message.
* @param {Notification} notification Notification object.
* @returns {Promise.<void>} Promise that is resolved once notification
* system message is handled.
* @private
*/
onNotificationSystemMessage(notification) {
// When notification is removed from notification tray, notification
// system message will still be fired, but "clicked" property will be
// equal to false. This should change once bug 1139363 is landed. When
// user clicks on notification we'll get two system messages, first to
// notify app that notification is clicked and then, once we show
// Conversation view to the user, we remove that notification from the
// tray that causes the second system message with "clicked" set to false.
if (!notification.clicked || !notification.data) {
// Force closing the window for notification removal case
// because navigation didn't init correctly if pending notification
// message existed.
if (Navigation.hasPendingInit()) {
window.close();
return Promise.reject(new Error('Notification has been dismissed.'));
}
return Promise.resolve();
}
// At the moment notification.data is { id, threadId }.
return this.onNotificationClicked(notification.data);
}
};
exports.SystemMessageHandler = SystemMessageHandler;
})(self);

View file

@ -0,0 +1,2 @@
;'use strict';function TaskRunner(){this._currentTask=Promise.resolve();}
TaskRunner.prototype.push=function(task){return(this._currentTask=this._currentTask.then(task,task));};

View file

@ -0,0 +1,6 @@
;;;(function(exports){'use strict';var updateTimer=null;var TimeHeaders={init:function th_init(){onvisibilityChange();document.addEventListener('visibilitychange',onvisibilityChange);window.addEventListener('timeformatchange',ontimeFormatChange);window.addEventListener('languagechange',onlanguageChange);},startScheduler:function th_startScheduler(){var now=Date.now(),nextTimeout=new Date(now+60000);nextTimeout.setSeconds(0);nextTimeout.setMilliseconds(0);this.stopScheduler();updateTimer=setTimeout(function(){this.updateAll('header[data-time-update=repeat]');this.startScheduler();}.bind(this),nextTimeout.getTime()-now);},stopScheduler:function th_stopScheduler(){clearTimeout(updateTimer);},updateAll:function th_updateAll(selector){selector=selector||'[data-time-update]';var elements=document.querySelectorAll(selector),length=elements.length,i;for(i=0;i<length;i++){this.update(elements[i]);}},update:function th_update(element){var ts=element.dataset.time;if(!ts){return;}
if(element.dataset.dateOnly==='true'){Utils.setHeaderDate({time:ts,element:element,withTime:false});}else if(element.dataset.timeOnly==='true'){element.removeAttribute('data-l10n-id');element.textContent=Utils.getFormattedHour(ts);}else{Utils.setHeaderDate({time:ts,element:element,withTime:true});}}};function onvisibilityChange(){if(document.hidden){TimeHeaders.stopScheduler();}
else{TimeHeaders.updateAll();TimeHeaders.startScheduler();}}
function ontimeFormatChange(){Utils.resetDateFormatters();TimeHeaders.updateAll();}
function onlanguageChange(){Utils.resetDateFormatters();TimeHeaders.updateAll();}
exports.TimeHeaders=TimeHeaders;}(this));

View file

@ -0,0 +1,55 @@
;;;(function(exports){'use strict';var rdashes=/-(.)/g;var rescape=/[.?*+^$[\]\\(){}|-]/g;var rparams=/([^?=&]+)(?:=([^&]*))?/g;var rnondialablechars=/[^,#+\*\d]/g;var rmail=/[\w-]+@[\w\-]/;var Utils={date:{shared:new Date(),formatters:{'time':{options:{hour:'numeric',minute:'numeric',},formatter:null},'full':{options:{month:'2-digit',day:'2-digit',year:'numeric',},formatter:null},'full-wt':{options:{month:'2-digit',day:'2-digit',year:'numeric',hour:'numeric',minute:'numeric',},formatter:null},'day':{options:{weekday:'long',},formatter:null},'day-wt':{options:{weekday:'long',hour:'numeric',minute:'numeric',},formatter:null},},},escapeRegex:function ut_escapeRegex(str){if(typeof str!=='string'){return'';}
return str.replace(rescape,'\\$&');},getFormattedHour:function ut_getFormattedHour(time){this.date.shared.setTime(+time);return this._getFormatter('time',false).format(this.date.shared);},getDayDate:function re_getDayDate(time){this.date.shared.setTime(+time);this.date.shared.setHours(0,0,0,0);return this.date.shared.getTime();},resetDateFormatters:function ut_resetDateFormatters(){for(var type in this.date.formatters){this.date.formatters[type].formatter=null;}},_getFormatter:function ut_getFormatter(type,withTime){if(withTime){type+='-wt';}
if(!this.date.formatters[type].formatter){var options=Object.assign({},this.date.formatters[type].options);if(options.hour){options.hour12=navigator.mozHour12;}
this.date.formatters[type].formatter=Intl.DateTimeFormat(navigator.languages,options);}
return this.date.formatters[type].formatter;},setHeaderDate:function ut_setHeaderDate({time,element,withTime}){var formatter;var today=Utils.getDayDate(Date.now());var otherDay=Utils.getDayDate(time);var dayDiff=(today-otherDay)/86400000;this.date.shared.setTime(+time);if(isNaN(dayDiff)){element.setAttribute('data-l10n-id','incorrectDate');return;}
if(dayDiff<0){formatter=this._getFormatter('full',withTime);element.removeAttribute('data-l10n-id');element.textContent=formatter.format(this.date.shared);return;}
if(dayDiff===0){if(withTime){document.l10n.setAttributes(element,'todayWithTime',{time:this.getFormattedHour(this.date.shared)});}else{element.setAttribute('data-l10n-id','today');}}else if(dayDiff===1){if(withTime){document.l10n.setAttributes(element,'yesterdayWithTime',{time:this.getFormattedHour(this.date.shared)});}else{element.setAttribute('data-l10n-id','yesterday');}}else if(dayDiff<6){formatter=this._getFormatter('day',withTime);element.removeAttribute('data-l10n-id');element.textContent=formatter.format(this.date.shared);}else{formatter=this._getFormatter('full',withTime);element.removeAttribute('data-l10n-id');element.textContent=formatter.format(this.date.shared);}},getContactDetails:function ut_getContactDetails(number,contacts,include){var details={};include=include||{};function updateDetails(contact){var name,phone,i,length,subscriber,org;name=contact.name[0];org=contact.org&&contact.org[0];length=contact.tel?contact.tel.length:0;subscriber=number.length>7?number.substr(-8):number;for(i=0;i<length;i++){if(contact.tel[i].value.indexOf(subscriber)!==-1){phone=contact.tel[i];break;}}
details.isContact=true;if(include.photoURL){var photo=ContactPhotoHelper.getThumbnail(contact);if(photo){details.photoURL=window.URL.createObjectURL(photo);}}
details.name=name;details.org=details.org||org;}
if(!contacts||(Array.isArray(contacts)&&contacts.length===0)){details.title='';}else if(!Array.isArray(contacts)){updateDetails(contacts);details.title=details.name||details.org;}else{for(var i=0,l=contacts.length;i<l;i++){updateDetails(contacts[i]);if(details.name){break;}}
details.title=details.name||details.org;}
return details;},getPhoneDetails:function ut_getPhoneDetails(input,tels){var length=tels.length;var hasUniqueCarriers=true;var hasUniqueTypes=true;var found,tel,type,carrier;for(var i=0;i<length;i++){tel=tels[i];if(tel.value&&Utils.probablyMatches(tel.value,input)){found=tel;}
if(carrier&&carrier===tel.carrier){hasUniqueCarriers=false;}
if(type&&type===tel.type[0]){hasUniqueTypes=false;}
carrier=tel.carrier;type=(tel.type&&tel.type[0])||'';}
if(!found){return null;}
return{type:(found.type&&found.type[0])||'',carrier:hasUniqueCarriers||hasUniqueTypes?found.carrier:'',number:found.value};},removeNonDialables:function ut_removeNonDialables(input){return input.replace(rnondialablechars,'');},probablyMatches:function ut_probablyMatches(a,b){var service=navigator.mozPhoneNumberService;if(typeof a!=='string'||typeof b!=='string'){return false;}
if(Settings.supportEmailRecipient&&Utils.isEmailAddress(a)&&Utils.isEmailAddress(b)){return a===b;}
if(service&&service.normalize){a=service.normalize(a);b=service.normalize(b);}else{a=Utils.removeNonDialables(a);b=Utils.removeNonDialables(b);}
return a===b||a.slice(-7)===b.slice(-7);},multiRecipientMatch:function ut_multiRecipientMatch(a,b){a=[].concat(a);b=[].concat(b);var blen=b.length;if(a.length!==blen){return false;}
return a.every(function(number){for(var i=0;i<blen;i++){if(Utils.probablyMatches(number,b[i])){return true;}}});},getResizedImgBlob:function ut_getResizedImgBlob(blob,limit,callback){var defaultLimit=295*1024;if(typeof limit==='function'){callback=limit;limit=defaultLimit;}
limit=limit===0?defaultLimit:Math.min(limit,defaultLimit);if(blob.size<limit){setTimeout(function blobCb(){callback(blob);});return;}
var ratio=Math.sqrt(blob.size/limit);Utils._resizeImageBlobWithRatio({blob:blob,limit:limit,ratio:ratio,callback:callback});},getSizeForL10n:function ut_getSizeForL10n(size){var sizeKB=size/1024;var sizeMB=sizeKB/1024;if(size<1000){return{l10nId:'attachmentSizeB',l10nArgs:{n:size}};}else if(sizeKB<1000){return{l10nId:'attachmentSizeKB',l10nArgs:{n:sizeKB.toFixed(1)}};}
return{l10nId:'attachmentSizeMB',l10nArgs:{n:sizeMB.toFixed(1)}};},_resizeImageBlobWithRatio:function ut_resizeImageBlobWithRatio(obj){var blob=obj.blob;var callback=obj.callback;var limit=obj.limit;var ratio=Math.ceil(obj.ratio);var qualities=[0.65,0.5,0.25];var sampleSize=1;var sampleSizeHash='';if(blob.size<limit||ratio<=1){setTimeout(function blobCb(){callback(blob);});return;}
if(blob.type==='image/jpeg'){if(ratio>=8){sampleSize=8;}else{sampleSize=ratio=Utils.getClosestSampleSize(ratio);}
sampleSizeHash='#-moz-samplesize='+sampleSize;}
var img=document.createElement('img');var url=window.URL.createObjectURL(blob);img.src=url+sampleSizeHash;img.onload=function onBlobLoaded(){window.URL.revokeObjectURL(url);var targetWidth=img.width*sampleSize/ratio;var targetHeight=img.height*sampleSize/ratio;var canvas=document.createElement('canvas');canvas.width=targetWidth;canvas.height=targetHeight;var context=canvas.getContext('2d',{willReadFrequently:true});context.drawImage(img,0,0,targetWidth,targetHeight);img.src='';var level=0;function cleanup(){canvas.width=canvas.height=0;canvas=null;}
function ensureSizeLimit(resizedBlob){if(resizedBlob.size<limit){cleanup();setTimeout(callback.bind(null,resizedBlob));}else{resizedBlob=null;if(level<qualities.length){canvas.toBlob(ensureSizeLimit,'image/jpeg',qualities[level++]);}else{cleanup();setTimeout(Utils._resizeImageBlobWithRatio.bind(Utils,{blob:blob,limit:limit,ratio:ratio*2,callback:callback}));}}}
canvas.toBlob(ensureSizeLimit,blob.type);};},getClosestSampleSize:function ut_getClosestSampleSize(ratio){if(ratio>=8){return 8;}
if(ratio>=4){return 4;}
if(ratio>=2){return 2;}
return 1;},camelCase:function ut_camelCase(str){return str.replace(rdashes,function replacer(str,p1){return p1.toUpperCase();});},typeFromMimeType:function ut_typeFromMimeType(mime){var MAX_MIME_TYPE_LENGTH=256;if(typeof mime!=='string'||mime.length>MAX_MIME_TYPE_LENGTH){return null;}
var index=mime.indexOf('/');if(index===-1){return null;}
var mainPart=mime.slice(0,index);var secondPart=mime.substr(index+1).toLowerCase();switch(mainPart){case'image':return'img';case'text':if(secondPart.indexOf('vcard')!==-1){return'vcard';}
if(secondPart!=='plain'){return'ref';}
return mainPart;case'video':case'audio':case'application':return mainPart;default:return null;}},params:function(input){var parsed={};input.replace(rparams,function($0,$1,$2){if($2==='true'){$2=true;}else if($2==='false'){$2=false;}
parsed[$1]=$2;});return parsed;},url(base,params){if(base.indexOf('?')===-1){base+='?';}else{base+='&';}
for(var key in params){if(params[key]==null){continue;}
base+=encodeURIComponent(key)+'='+
encodeURIComponent(params[key])+'&';}
return base.slice(0,-1);},basicContact:function(number,records){var record;if(Array.isArray(records)){if(records.length>0){record=records[0];}}else if(records!==null){record=records;}
if(!record&&!number){return;}
var telLength=(record&&record.tel)?record.tel.length:0;var tel;for(var i=0;i<telLength;i++){if(record.tel[i].value===number){tel=record.tel[i];break;}}
if(!tel){tel={type:[''],value:number,carrier:''};}
var details=Utils.getContactDetails(tel,record);var info=Utils.getDisplayObject(details.title||null,tel);return info;},getDisplayObject:function(theTitle,tel){var number=tel.value;var type=tel.type&&tel.type.length?tel.type[0]:'';var data={name:theTitle||number,number:number,type:type,carrier:tel.carrier||''};if(Settings.supportEmailRecipient){data.email=number;data.emailHTML='';}
return data;},asyncLoadRevokeURL:function(url){setTimeout(function(){var image=new Image();image.src=url;image.onload=image.onerror=function revokePhotoURL(){window.URL.revokeObjectURL(this.src);};});},isEmailAddress:function(email){return rmail.test(email);},closeNotificationsForThread:function(threadId){var targetTag='threadId:'+threadId;return Notification.get({tag:targetTag}).then(function onSuccess(notifications){for(var i=0;i<notifications.length;i++){notifications[i].close();}}).catch(function onError(reason){console.error('Notification.get(tag: '+targetTag+'): ',reason);});},debounce:function(func,waitTime){var timeout,args,context;var executeLater=function(){func.apply(context,args);timeout=context=args=null;};return function(){context=this;args=arguments;if(timeout){clearTimeout(timeout);}
timeout=setTimeout(executeLater,waitTime);};},throttle:function(func,delay=300,options={}){var timeout=null;var previous=0;var preventFirstCall=!!options.preventFirstCall;var preventLastCall=!!options.preventLastCall;if(typeof func!=='function'){throw new Error('func must be a Function');}
if(typeof delay!=='number'||delay<0){throw new Error('delay must be a positive number');}
return function(...args){var now=Date.now();if(!previous&&preventFirstCall){previous=now;}
var remaining=delay-(now-previous);if(remaining<=0){if(timeout){clearTimeout(timeout);timeout=null;}
previous=now;func.apply(this,args);}else if(!timeout&&!preventLastCall){timeout=setTimeout(()=>{previous=preventFirstCall?0:Date.now();timeout=null;func.apply(this,args);},remaining);}};},alert:function(message,title){var deferred=this.Promise.defer();var dialog=new Dialog({title:title||'modal-dialog-default-title',body:message,options:{cancel:{text:'modal-dialog-ok-button',method:deferred.resolve}}});dialog.show();return deferred.promise;},confirm:function(message,title,confirmOptions){var confirmButtonText=confirmOptions&&confirmOptions.text||'modal-dialog-ok-button';var confirmButtonClassName=confirmOptions&&confirmOptions.className||'recommend';var deferred=this.Promise.defer();var dialog=new Dialog({title:title||'modal-dialog-default-title',body:message,options:{cancel:{text:'modal-dialog-cancel-button',method:deferred.reject},confirm:{text:confirmButtonText,className:confirmButtonClassName,method:deferred.resolve}}});dialog.show();return deferred.promise;},Promise:{defer:function(){var deferred={};deferred.promise=new Promise(function(resolve,reject){deferred.resolve=resolve;deferred.reject=reject;});return deferred;},async:function(generatorFunction){return function asyncGenerator(){var generator=generatorFunction.apply(this,arguments);function handle(result){if(result.done){return Promise.resolve(result.value);}
return Promise.resolve(result.value).then((result)=>handle(generator.next(result)),(error)=>handle(generator.throw(error)));}
try{return handle(generator.next());}catch(error){return Promise.reject(error);}};}},getSimNameByIccId(iccId){var index=Settings.getServiceIdByIccId(iccId);if(index===null){return Promise.resolve('');}
return document.l10n.formatValue('sim-id-label',{id:index+1});},initializeShimHost(appInstanceId){var shimHostIframe=document.querySelector('.shim-host');var promise=typeof shimHostIframe.contentWindow.bootstrap==='function'?Promise.resolve():new Promise((resolve)=>{shimHostIframe.addEventListener('load',function onLoad(){shimHostIframe.removeEventListener('load',onLoad);resolve();});});return promise.then(()=>{window.mozSetMessageHandler=function(type,handler){navigator.mozSetMessageHandler(type,(request)=>handler(request));};shimHostIframe.contentWindow.bootstrap(appInstanceId);});},onceDocumentIsVisible(){if(!document.hidden){return Promise.resolve();}
var defer=this.Promise.defer();document.addEventListener('visibilitychange',function waitVisibility(){document.removeEventListener('visibilitychange',waitVisibility);defer.resolve();});return defer.promise;}};exports.Utils=Utils;}(this));

View file

@ -0,0 +1,23 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*exported WaitingScreen */
'use strict';
var WaitingScreen = {
get loading() {
delete this.loading;
return (this.loading = document.getElementById('loading'));
},
get loadingHeader() {
delete this.loadingHeader;
return (this.loadingHeader = document.getElementById('loading-header'));
},
show: function ws_show() {
this.loading.classList.add('show-loading');
},
hide: function ws_hide() {
this.loading.classList.remove('show-loading');
}
};

View file

@ -0,0 +1,11 @@
;'use strict';var WBMP=(function(document){function decode(arrayBuffer,callback){var bytes=new Uint8Array(arrayBuffer);var ptr=0;function readOctet(){return bytes[ptr++]&0xff;}
function readMultiByteInteger(){var result=0;while(true){if(result&0xfe000000){throw'error parsing integer';}
var b=bytes[ptr++];result=(result<<7)|(b&0x7f);if(!(b&0x80)){return result;}}}
function write(data,w,bit){var color=bit?255:0;data[w]=color;data[w+1]=color;data[w+2]=color;data[w+3]=255;}
try{if(readMultiByteInteger()!==0){return false;}
if(readOctet()!==0){return false;}
var width=readMultiByteInteger();var height=readMultiByteInteger();if(width===0||width>65535||height===0||height>65535){return false;}
var canvas=document.createElement('canvas');canvas.setAttribute('width',width);canvas.setAttribute('height',height);var ctx=canvas.getContext('2d',{willReadFrequently:true});var imageData=ctx.createImageData(width,height);var data=imageData.data;for(var y=0;y<height;++y){for(var x=0;x<width;x+=8){var bits=bytes[ptr++];var w=(y*width+x)*4;write(data,w,bits&0x80);write(data,w+4,bits&0x40);write(data,w+8,bits&0x20);write(data,w+12,bits&0x10);write(data,w+16,bits&0x08);write(data,w+20,bits&0x04);write(data,w+24,bits&0x02);write(data,w+28,bits&0x01);}}
if(ptr>bytes.length){return null;}
ctx.putImageData(imageData,0,0);canvas.toBlob(callback);}catch(e){return null;}}
return{decode:decode};})(document);

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Messages Shim Host</title>
<!-- Firefox Desktop shims -->
<!-- Shim scripts -->
<script src="/lib/bridge/service.js"></script>
<script src="/lib/bridge/plugins/stream/service.js"></script>
<script src="/services/js/bridge_service_mixin.js"></script>
<script src="/services/js/activity/activity_shim.js"></script>
<script src="/services/js/moz_settings/moz_settings_shim.js"></script>
<script src="/services/js/moz_mobile_message/moz_mobile_message_shim.js"></script>
<script src="/services/js/moz_mobile_connections/moz_mobile_connections_shim.js"></script>
<script src="/views/shared/js/shim_host_bootstrap.js"></script>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,25 @@
/*
Structure of app
*/
.view-body {
overflow-y: auto;
overflow-x: hidden;
height: -moz-calc(100% - 5rem);
width: 100%;
/* This box-sizing is necessary to prevent vertical scrolling in views that
* specify vertical paddings and/or borders. */
-moz-box-sizing: border-box;
will-change: scroll-position;
}
/* Style of highlighted text. */
.highlight {
background-color: #b2f2ff;
}
/* Hidden elements */
.hide, [data-template] {
display: none !important;
}

View file

@ -0,0 +1,278 @@
#messages-compose-form {
display: flex;
flex-direction: column;
height: auto; /* overrides building blocks */
min-height: 4rem;
background-color: #fff;
}
/* Overwrite. No more fixed height */
form[role="search"] p input,
form[role="search"] p textarea,
form[role="search"] button[type="submit"] + p > textarea {
height: auto;
}
/*
* Input now are contentEditable containers. We define
* placeholder management here as well.
*/
#messages-input {
position: relative; /* to position its generated content */
overflow: auto;
box-sizing: border-box;
width: 100%;
height: 100%;
padding-inline-end: 0.5rem;
color: #474747;
border-inline-end: 0.1rem solid #ccc;
font-size: 1.7rem;
line-height: 2.1rem;
unicode-bidi: -moz-plaintext;
}
#messages-input.placeholder {
unicode-bidi: unset;
}
#messages-input:after {
content: attr(title);
position: absolute;
top: 0;
left: 0;
right: 0;
color: #9b9b9b;
font-style: italic;
white-space: nowrap;
pointer-events: none;
visibility: hidden;
}
#messages-input.placeholder:after {
visibility: visible;
}
/* Overwrite BB because of the previous rule */
form[role="search"] button[type="submit"]:after {
content: "";
background: none;
}
.mms-label-line {
display: flex;
justify-content: flex-end;
align-items: flex-end;
background-color: #e1f0ec;
}
.subject-composer {
position: relative;
flex-grow: 1;
min-width: 0;
padding: 1rem 0 0.3rem;
}
.mms-label {
padding: 1rem 0 0.3rem;
}
.mms-label,
.message-counter {
display: none;
flex: none;
text-align: center;
box-sizing: border-box;
width: 6rem;
color: #575757;
font-size: 1.4rem;
}
.message-counter {
padding-top: .7rem;
}
/**
* 1. "MMS" label is displayed near the subject field when subject input is
* visible and message type is MMS;
* 2. Remaining characters counter is displayed in bottom section if character
* count exceeds predefined threshold and message type is SMS.
*/
[data-message-type='mms'] .mms-label,
[data-message-type='sms'] .message-counter:not(:empty) {
display: block;
}
.content-composer {
display: flex;
flex-direction: row;
min-height: 0;
}
.subject-composer-input {
position: relative;
box-sizing: border-box;
min-height: 2.2rem;
padding: 0;
padding-bottom: 0.1rem;
padding-inline-start: 1.5rem;
color: #008787;
font-size: 1.7rem;
}
.subject-composer-placeholder {
position: absolute;
top: 1rem;
/* adding both left/right so that it behaves correctly in RTL */
left: 1.5rem;
right: 1.5rem;
color: #00adad;
font-size: 1.7rem;
font-weight: normal;
font-style: italic;
pointer-events: none;
}
.composer-message-input-container {
position: relative;
flex: auto;
box-sizing: border-box;
min-width: 0;
height: 100%;
min-height: 4rem;
padding: 1rem 0;
}
.composer-button-container {
position: relative;
display: flex;
flex: none;
flex-direction: row;
justify-content: center;
align-content: space-between;
flex-wrap: wrap;
width: 6rem;
-moz-user-select: none; /* without this, tapping on the element selects its content */
}
.messages-attach-container {
display: flex;
}
#messages-send-button {
height: 4rem;
overflow: visible;
background: #fff no-repeat center center / 3rem;
align-self: flex-end;
}
html[dir="ltr"] #messages-send-button {
background-image: url(images/icons/actionicon_sms_send_30x30.png);
/* When we put the icon at the center, an optical illusion makes it look to be
* slightly to the left, so we offset the button by 2px to the right to
* counteract this illusion */
transform: translate(.2rem);
}
html[dir="rtl"] #messages-send-button {
background-image: url(images/icons/actionicon_sms_send_30x30_rtl.png);
transform: translate(-.2rem);
}
#messages-send-button:after {
display: none
}
#messages-send-button[disabled] {
background-blend-mode: luminosity;
opacity: 0.625;
}
#messages-attach-button {
align-self: flex-end;
flex: none;
box-sizing: content-box;
width: 3rem;
margin: 0;
padding: 0;
padding-inline-start: 0.5rem;
padding-inline-end: 1rem;
background: transparent
url('images/icons/actionicon_sms_add_attachment_30x30.png')
no-repeat center / 3rem;
}
#messages-attach-button[disabled] {
background-blend-mode: luminosity;
opacity: 0.3;
}
/*
DSDS
*/
.sim-indication {
position: absolute;
offset-inline-end: 1.8rem;
bottom: 0.4rem;
display: none;
line-height: 1;
color: #5f5f5f;
font-size: 1.4rem;
font-style: normal;
}
.sim-indication:before {
position: absolute;
/* 2.6rem from the end; means .8rem from .sim-indication */
offset-inline-end: .8rem;
bottom: -0.1rem;
content: '...';
color: #b2b2b2;
font-size: 1.6rem;
}
button[disabled] .sim-indication {
color: inherit;
}
.has-preferred-sim .sim-indication {
display: block;
}
/**
* Override Building Block [Input areas] styles
* We should get rid of all [role="search"] Messages rules in bug 1055357.
*/
form.bottom[role="search"] {
z-index: 3;
}

View file

@ -0,0 +1,108 @@
/*
* This file contains styles shared between views when they're in edit mode.
* View edit mode is activated via Options -> Select threads/messages.
*/
/* Override Building Block [Edit mode] styles */
form[role="dialog"][data-type="edit"] gaia-header {
transform: translateY(-110%);
}
form[role="dialog"][data-type="edit"] > menu {
transform: translateY(110%);
}
/* Add a margin at the end of the last thread or message node to avoid overlay
* with the edit mode menu */
.edit .edit-container {
padding-bottom: 7rem;
}
#main-wrapper.edit [data-type="list"] li a {
pointer-events: none;
}
.edit .bottom-tab[role="region"] {
visibility: visible;
}
/* Override Building Block Tabs styles */
form[role="dialog"][data-type="edit"] .bb-tablist:before {
bottom: 5.5rem ;
}
form[role="dialog"][data-type="edit"] .bb-tablist > li {
height: 5.5rem ;
}
.bb-tablist > li button {
background-position: 50% 50% ;
}
.bb-tablist > li button[data-action="delete-button"] {
background-image: url("images/icons/delete.png");
}
/* Lists: edit mode */
html[dir="ltr"] .edit [data-type="list"] li > a {
transform: translateX(-1.5rem);
}
html[dir="rtl"] .edit [data-type="list"] li > a {
transform: translateX(1.5rem);
}
.edit [data-type="list"] li > label {
opacity: 1;
pointer-events: auto;
}
html[dir="ltr"] .edit [data-type="list"] li > label {
transform: translateX(2rem);
}
html[dir="rtl"] .edit [data-type="list"] li > label {
transform: translateX(-2rem);
}
.edit [data-type="list"] li > .pack-checkbox input ~ span:after {
opacity: 1;
}
html[dir="ltr"] .edit [data-type="list"] li > .pack-checkbox input ~ span:after {
transform: translateX(2.5rem);
}
html[dir="rtl"] .edit [data-type="list"] li > .pack-checkbox input ~ span:after {
transform: translateX(-2.5rem);
}
.edit [data-type="list"] li > a > p {
margin-inline-end: 3.4rem;
}
html[dir="ltr"] .edit [data-type="list"] li > a > p {
transform: translateX(5rem);
}
html[dir="rtl"] .edit [data-type="list"] li > a > p {
transform: translateX(-5rem);
}
/* Currently is used in ConversationView only */
html[dir="ltr"] .edit [data-type="list"] .danger {
transform: translateX(2.5rem);
}
html[dir="rtl"] .edit [data-type="list"] .danger {
transform: translateX(-2.5rem);
}
form[role="dialog"][data-type="edit"] {
visibility: hidden;
}
.edit form[role="dialog"][data-type="edit"] {
visibility: visible;
}
.edit form[role="dialog"][data-type="edit"] > menu {
transform: translateY(0);
}
.edit form[role="dialog"][data-type="edit"] gaia-header {
transform: translateY(0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

View file

@ -0,0 +1,34 @@
/* Most of this should probably move to the Building Blocks: Bug 1011418 */
section[role="notification"] {
box-sizing: border-box;
z-index: 1;
position: relative; /* so that we can compete
with absolutely positioned elements */
overflow: hidden;
min-height: 3.5rem;
background-color: rgba(77,77,77,0.95);
font-size: 1.5rem;
font-weight: 300;
font-style: italic;
color: #fff;
}
section[role="notification"] a {
color: #9ef2e7;
text-decoration: underline;
}
section[role="notification"] p {
margin: .9rem 1.5rem;
line-height: 1.7rem;
text-align: center;
}
section[role="notification"] a {
color: #9ef2e7;
text-decoration: underline;
}

View file

@ -0,0 +1,25 @@
.panel {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow: auto;
background: white;
animation: var(--transition-duration) both ease;
}
.panel {
transform: translate(100%); /* BiDi proof */
}
.panel-active {
transform: translate(0);
}
.panel-hidden {
display: none;
}

View file

@ -0,0 +1,172 @@
/* Global */
html, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
overflow: hidden;
color: #000;
font-size: 1.6rem;
line-height: 2.2rem;
vertical-align: middle;
background: var(--header-background);
}
/* Text conventions */
h1, h2, h3, h4, h5, h6 {
margin: 0;
padding: 0;
font-weight: normal;
}
ul, ol, dl, dt, dd {
padding: 0;
margin: 0;
list-style-type: none;
}
p, li {
word-wrap: break-word;
margin: 0;
padding: 0;
}
q {
quotes: none;
display: block;
}
pre, blockquote {
padding: 0;
margin: 0;
}
em {
font-style: italic;
}
address {
font-style: normal;
display: inline;
}
abbr, acronym {
cursor: default;
border: none;
}
strong {
font-weight: 700;
}
b {
font-weight: 600;
}
small {
font-size: 1.1rem;
line-height: 1rem;
color: #999;
}
/* Links */
a {
text-decoration: none;
outline: none;
color: #ff4e00;
}
a:hover {
text-decoration: none;
}
a img {
border: none;
}
a abbr {
cursor: pointer;
}
/* Forms */
form {
margin: 0;
}
fieldset {
margin: 2rem 0 0;
padding: 0;
border: none;
}
fieldset.first {
margin: 0;
}
legend {
padding: 0;
display: block;
width: 0;
height: 0;
overflow: hidden;
font: 0/0 a;
}
button::-moz-focus-inner {
padding: 0;
border: none;
}
input, textarea, select, button {
margin: 0;
padding: 0;
vertical-align: middle;
}
input:focus, textarea:focus {
outline: none;
}
textarea {
resize: none;
overflow: auto;
height: 4.6rem;
}
input[type="text"], textarea {
font-size: 1.3rem;
line-height: 4rem;
}
input[type="text"]:focus, textarea:focus {
border-color: #7a7a7a;
}
[contenteditable] {
outline: none;
word-wrap: break-word;
}
/* Media */
img {
display: block;
padding: 0;
margin: 0;
width: 100%;
}
img:-moz-broken {
line-height: 1.1rem;
overflow: hidden;
}
/* HTML 5 */
article, aside, details, figcaption, figure, footer, header, hgroup,
menu, nav, section, video, audio, canvas, progress, meter, time {
display: block;
padding: 0;
margin: 0;
}

View file

@ -0,0 +1,261 @@
html, body {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
font-size: 10px;
overflow-x: hidden !important;
overflow-y: hidden !important;
}
.shim-host {
width: 0;
height: 0;
}
/* Default alignment in case a dir attribute is specified. */
html[dir="ltr"] [dir] { text-align: left; }
html[dir="rtl"] [dir] { text-align: right; }
/*
Override BB. Some CSS tweaks for having a better confirm readability
*/
form[role="dialog"][data-type="confirm"] p {
white-space: pre-line;
}
form[role="dialog"].error-dialog-show-recipient ul {
border: none;
}
form[role="dialog"].error-dialog-show-recipient ul li {
padding: 0;
}
/* This does not override the building block, that's why we use a shorter
* selector */
.error-dialog-show-recipient li {
/* future-proof in case BB moves to "text-align: start" */
width: -moz-fit-content;
/* List items are phone numbers */
direction: ltr;
unicode-bidi: -moz-isolate;
}
gaia-header {
position: relative;
z-index: 10;
}
.subheader {
position: absolute;
z-index: 6; /* should be between 4 and 9 (inclusive). 3 is the z-index for
the input form, 10 is the z-index for the header */
left: 0;
right: 0;
top: 5rem;
}
/* Container for conversation/new message view */
#composer-container {
position: relative;
background: #e1f0ec;
}
/*
* Flex layout is wrapped by 'flex-composer'. Our flex layout has
* 2 well differentiated:
* - 'messages-container'
* - 'messages-compose-form'
*/
.flex-composer {
display: flex;
flex-direction: column;
height: 100%;
top: 0;
overflow: hidden;
}
/* Override Building Block [Lists] styles */
[data-type="list"] {
padding: 0;
}
.bottom-tab[role="region"] {
visibility: hidden;
}
[data-type="list"] header {
margin: 0 1.5rem;
}
[data-type="list"] li > label {
display: block;
overflow: hidden;
opacity: 0;
}
[data-type="list"] aside.pack-end {
margin: 0 .5rem;
}
/* Panel handling */
#main-wrapper {
position: absolute;
overflow: hidden;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
.disabled {
opacity: 0.3;
pointer-events: none;
}
/*
Loading screen while deleting
*/
#loading.show-loading {
display: block;
}
#loading {
display: none;
}
.loading-header {
text-align: start;
}
#loading [role="status"] {
text-align: center;
height: 4rem;
}
#loading p {
border-bottom: none;
}
.article-list[data-type="list"] ul.contact-list {
overflow-x: hidden;
overflow-y: auto;
width: calc(100% - 3.1rem);
padding: 0 1.5rem;
background: #fff;
}
/**
* TODO: It's an adjustment for line-height/padding to prevent bottom of chars
* been cut. This fix will be moved to shared list.css once Bug 1144588 landed
*/
.contact-list .suggestion > .name {
line-height: 2.4rem;
padding-top: 0.8rem;
}
form[role="dialog"][data-type="action"].contact-prompt > header {
height: auto;
min-height: 5rem;
font-weight: 300;
}
.unknown-contact-header {
line-height: 2.5rem;
padding: 1.3rem 0;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
font-size: 2.3rem;
}
.contact-header {
/* bottom padding is .9rem because we need .1rem more for the
* nice shadow line at the header bottom, defined as a :after element */
padding: 0.8rem 0 0.9rem;
}
.contact-header .name {
line-height: 3.3rem;
padding: 0.1rem 0 0.4rem;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
font-size: 2.3rem;
}
.contact-header .details {
line-height: 1.5rem;
padding: 0 0 0.7rem;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
font-size: 1.5rem;
}
.contact-header span[data-type=img] {
display: block;
width: 6rem;
height: 6rem;
background-size: cover;
background-position: center;
}
.contact-header aside.pack-end {
margin-inline-end: -1.5rem; /* compensate the header's 3rem padding */
}
html[dir="ltr"] .contact-header aside.pack-end { float: right; }
html[dir="rtl"] .contact-header aside.pack-end { float: left; }
.suggestion * {
pointer-events: none;
}
form[role="dialog"][data-type="action"] > header {
text-align: start;
}
/* Phone details label styles */
.phone-type,
.phone-type-separator,
.phone-carrier,
.phone-carrier-separator {
display: none;
}
.has-phone-type > .phone-type,
.has-phone-type > .phone-type-separator,
.has-phone-carrier > .phone-carrier,
.has-phone-carrier > .phone-carrier-separator {
display: unset;
}
/* Text selection control */
html {
-moz-user-select: none;
}
.editable-select-mode [contenteditable="true"] {
-moz-user-select: text;
}
/* ellipsis direction hack before bug 883884 fixed */
.ellipsis-dir-fix {
display: block;
width: -moz-fit-content;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}