Publish Vulpes Preview 15 sources for desktop, Android and Tundra

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

View file

@ -0,0 +1,90 @@
define([
'require',
'l10n!',
'cards'
], function (require) {
var mozL10n = require('l10n!'), cards = require('cards');
return {
_bindPrefs: function (checkIntervalClassName, notifyEmailClassName, soundOnSendClassName, signatureEnabledClassName, signatureButtonClassName) {
if (checkIntervalClassName) {
var checkIntervalNode = this.nodeFromClass(checkIntervalClassName), currentInterval = this.account.syncInterval, syncIntervalString = String(currentInterval), extraOptions = [];
if (typeof _secretDebug !== 'undefined' && _secretDebug.fastSync) {
extraOptions = extraOptions.concat(_secretDebug.fastSync);
}
var hasOption = Array.slice(checkIntervalNode.options, 0).some(function (option) {
return syncIntervalString === option.value;
});
if (!hasOption && extraOptions.indexOf(currentInterval) === -1) {
extraOptions.push(currentInterval);
}
extraOptions.forEach(function (interval) {
var node = document.createElement('option'), seconds = interval / 1000;
node.value = String(interval);
mozL10n.setAttributes(node, 'settings-check-dynamic', { n: seconds });
checkIntervalNode.appendChild(node);
});
checkIntervalNode.value = syncIntervalString;
checkIntervalNode.addEventListener('change', this.onChangeSyncInterval.bind(this), false);
}
if (notifyEmailClassName) {
var notifyMailNode = this.nodeFromClass(notifyEmailClassName);
notifyMailNode.addEventListener('click', this.onNotifyEmailClick.bind(this), false);
notifyMailNode.checked = this.account.notifyOnNew;
}
if (soundOnSendClassName) {
var soundOnSendNode = this.nodeFromClass(soundOnSendClassName);
soundOnSendNode.addEventListener('click', this.onSoundOnSendClick.bind(this), false);
soundOnSendNode.checked = this.account.playSoundOnSend;
}
if (signatureEnabledClassName) {
var signatureEnabledNode = this.nodeFromClass(signatureEnabledClassName);
signatureEnabledNode.addEventListener('click', this.onSignatureEnabledClick.bind(this), false);
signatureEnabledNode.checked = !!this.identity.signatureEnabled;
}
if (signatureButtonClassName) {
this.signatureButton = this.nodeFromClass(signatureButtonClassName);
this.updateSignatureButton();
this.signatureButton.addEventListener('click', this.onClickSignature.bind(this), false);
}
},
nodeFromClass: function (className) {
return this.getElementsByClassName(className)[0];
},
onChangeSyncInterval: function (event) {
var value = parseInt(event.target.value, 10);
console.log('sync interval changed to', value);
this.account.modifyAccount({ syncInterval: value });
},
onNotifyEmailClick: function (event) {
var checked = event.target.checked;
console.log('notifyOnNew changed to: ' + checked);
this.account.modifyAccount({ notifyOnNew: checked });
},
onSoundOnSendClick: function (event) {
var checked = event.target.checked;
console.log('playSoundOnSend changed to: ' + checked);
this.account.modifyAccount({ playSoundOnSend: checked });
},
onSignatureEnabledClick: function (event) {
var checked = event.target.checked;
console.log('signatureEnabled changed to: ' + checked);
this.identity.modifyIdentity({ signatureEnabled: checked });
},
updateSignatureButton: function () {
var text = this.identity.signature || '', isEmpty = text.trim().length === 0, node = this.signatureButton.firstElementChild;
node.textContent = text;
node.classList.toggle('empty-placeholder', isEmpty);
if (isEmpty) {
mozL10n.setAttributes(node, 'settings-empty-signature-label');
} else {
node.removeAttribute('data-l10n-id');
}
},
onClickSignature: function (index) {
cards.pushCard('settings_signature', 'animate', {
account: this.account,
index: index
}, 'right');
}
};
});

View file

@ -0,0 +1,9 @@
<li class="cmp-attachment-item">
<span class="cmp-attachment-icon" role="presentation"></span>
<span class="cmp-attachment-fileinfo">
<span dir="auto" class="cmp-attachment-filename"></span>
<span class="cmp-attachment-filesize"></span>
</span>
<span class="cmp-attachment-remove" role="button"
data-l10n-id="compose-attachment-remove"></span>
</li>

View file

@ -0,0 +1,11 @@
<li class="cmp-autocomplete-item">
<!-- Using an a tag so that screen reader properly activates a selection
when tapped. Using a div meant the ul element got the click. To get proper full bleed on the active/selected color, need to use another div inside
the a tag to get that while keeping the margins on text and border. -->
<a class="cmp-autocomplete-item-link" href="#">
<div class="cmp-autocomplete-item-details">
<span class="cmp-autocomplete-name" dir="auto" aria-hidden="true"></span>
<span class="cmp-autocomplete-email" dir="auto" aria-hidden="true"></span>
</div>
</a>
</li>

View file

@ -0,0 +1,34 @@
define(['require'], function (require) {
var contacts = navigator.mozContacts;
function makeResult(query, contacts) {
return {
query: query,
contacts: contacts
};
}
return function match(query) {
return new Promise(function (resolve, reject) {
if (!contacts || !query) {
return resolve(makeResult(query, []));
}
var request = contacts.find({
filterBy: [
'email',
'name',
'givenName',
'familyName'
],
filterOp: 'startsWith',
filterLimit: 30,
filterValue: query
});
request.onsuccess = function () {
resolve(makeResult(query, this.result));
};
request.onerror = function (err) {
console.error('autocomplete_source contacts.find error: ' + err);
resolve(makeResult(query, []));
};
});
};
});

View file

@ -0,0 +1,13 @@
<form class="cmp-contact-menu" role="dialog" data-type="action">
  <header></header>
<menu>
<button class="cmp-contact-menu-edit" data-l10n-id="message-edit-menu-edit">
</button>
  <button class="cmp-contact-menu-delete" data-l10n-id="message-edit-menu-delete">
</button>
<span class="last-button-container">
<button class="cmp-contact-menu-cancel" data-l10n-id="message-multiedit-cancel">
</button>
</span>
</menu>
</form>

View file

@ -0,0 +1,9 @@
<form role="dialog" class="cmp-draft-menu" data-type="action">
<menu>
<button id="cmp-draft-save" data-l10n-id="compose-draft-save"></button>
<button id="cmp-draft-discard" class="danger" data-l10n-id="compose-delete-confirm"></button>
<span class="last-button-container">
<button id="cmp-draft-cancel" data-l10n-id="message-multiedit-cancel"></button>
</span>
</menu>
</form>

View file

@ -0,0 +1,11 @@
<form role="dialog" data-type="confirm">
<section>
<h1 data-l10n-id="compose-invalid-addresses-title"></h1>
<p data-l10n-id="compose-invalid-addresses-description"></p>
</section>
<menu>
<button id="cmp-confirm-invalid-addresses"
class="confirm-dialog-ok full recommend"
data-l10n-id="dialog-button-ok"></button>
</menu>
</form>

View file

@ -0,0 +1,4 @@
<div class="cmp-peep-bubble peep-bubble" dir="auto" role="button">
<span class="cmp-peep-name"></span>
<span class="cmp-peep-address collapsed"></span>
</div>

View file

@ -0,0 +1,119 @@
<section class="cmp-compose-header" role="region" data-statuscolor="default">
<header data-prop="headerNode">
<a role="button" data-event="click:onBack" href="#" class="cmp-back-btn"
data-l10n-id="back-button">
<span class="icon icon-back"></span>
</a>
<menu type="toolbar">
<a role="button" href="#" data-event="click:onAttachmentAdd"
class="cmp-attachment-btn" data-l10n-id="compose-attachment-button">
<span class="icon icon-attachment"></span>
</a>
<a role="button" data-prop="sendButton" data-event="click:onSend"
href="#" class="cmp-send-btn" data-l10n-id="compose-send-button">
<span class="icon icon-send"></span>
</a>
</menu>
<h1 class="cmp-compose-header-label"
data-l10n-id="compose-new-header"></h1>
</header>
</section>
<div data-prop="scrollContainer"
class="scrollregion-below-header cmp-autocomplete-origin">
<div data-prop="addrBar">
<div class="cmp-autocomplete-input-container">
<div class="cmp-autocomplete-input-list">
<div data-prop="firstEnvelopeLine"
data-event="click:onContainerClick"
class="cmp-envelope-line cmp-combo cmp-autocomplete-input-label"
role="group" data-l10n-id="to-group">
<span class="cmp-to-label cmp-addr-label"
data-l10n-id="compose-to" aria-hidden="true"></span>
<div class="cmp-to-container cmp-addr-container">
<div class="cmp-bubble-container">
<input data-prop="toNode"
data-event="keydown:onAddressKeydown,input:onAddressInput"
dir="auto"
class="cmp-to-text cmp-addr-text" type="email" />
</div>
</div>
<div data-event="click:onContactAdd"
class="cmp-to-add cmp-contact-add"
role="button"
data-l10n-id="compose-contact-add-recipient"></div>
</div>
<!-- XXX: spec calls for showing cc/bcc merged until selected,
but there is also the case where replying itself might need
to expand, so we are deferring that feature -->
<div data-event="click:onContainerClick"
class="cmp-envelope-line cmp-combo cmp-autocomplete-input-label"
role="group" data-l10n-id="cc-group">
<span class="cmp-cc-label cmp-addr-label"
data-l10n-id="compose-cc" aria-hidden="true"></span>
<div class="cmp-cc-container cmp-addr-container">
<div class="cmp-bubble-container">
<input data-prop="ccNode"
data-event="keydown:onAddressKeydown,input:onAddressInput"
dir="auto"
class="cmp-cc-text cmp-addr-text" type="email" />
</div>
</div>
<div data-event="click:onContactAdd"
class="cmp-cc-add cmp-contact-add"
role="button"
data-l10n-id="compose-contact-add-recipient"></div>
</div>
<div data-event="click:onContainerClick"
class="cmp-envelope-line cmp-combo cmp-autocomplete-input-label"
role="group" data-l10n-id="bcc-group">
<span class="cmp-bcc-label cmp-addr-label"
data-l10n-id="compose-bcc" aria-hidden="true"></span>
<div class="cmp-bcc-container cmp-addr-container">
<div class="cmp-bubble-container">
<input data-prop="bccNode"
data-event="keydown:onAddressKeydown,input:onAddressInput"
dir="auto"
class="cmp-bcc-text cmp-addr-text" type="email" />
</div>
</div>
<div data-event="click:onContactAdd"
class="cmp-bcc-add cmp-contact-add"
role="button"
data-l10n-id="compose-contact-add-recipient"></div>
</div>
</div>
</div>
<div class="cmp-envelope-line cmp-subject">
<span class="cmp-subject-label"
data-l10n-id="compose-subject" aria-hidden="true"></span>
<input data-prop="subjectNode"
dir="auto"
class="cmp-subject-text" type="text" data-l10n-id="subject" />
</div>
<div data-prop="errorMessage" class="cmp-error-message collapsed"></div>
<div data-prop="attachmentTotal"
class="cmp-envelope-line cmp-attachment-total collapsed"
aria-hidden="true">
<span data-prop="attachmentLabel"
class="cmp-attachment-label cmp-addr-label"
data-l10n-id="compose-attachments[zero]"></span>
<span data-prop="attachmentsSize" class="cmp-attachment-size"></span>
</div>
<ul data-prop="attachmentsContainer" class="cmp-attachment-container">
</ul>
</div>
<div data-prop="textBodyNode" class="cmp-body-text" contenteditable="true" role="textbox" aria-multiline="true"
data-l10n-id="compose-text"></div>
<div data-prop="htmlBodyContainer"
data-event="click:_focusEditorWithCursorAtEnd"
dir="auto"
class="cmp-body-html"
data-l10n-id="message-body-container">
</div>
<!-- Autocomplete at the bottom since it should have the highest z order
placement, so that the autocomplete matches are visible. -->
<cmp-autocomplete data-prop="autocomplete"
data-event="autocompleteSelected"
data-source="cards/cmp/autocomplete_source">
</cmp-autocomplete>
</div>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,13 @@
<div class="card-confirm-dialog card">
<form data-statuscolor="background"
role="dialog" data-type="confirm" class="collapsed confirm-dialog-form">
<section>
<h1 data-l10n-id="confirm-dialog-title"></h1>
<p class="confirm-dialog-message"></p>
</section>
<menu>
<button class="confirm-dialog-cancel" data-l10n-id="message-multiedit-cancel"></button>
<button class="confirm-dialog-ok recommend" data-l10n-id="dialog-button-ok"></button>
</menu>
</form>
</div>

View file

@ -0,0 +1,4 @@
<a class="fld-account-item">
<span class="selected-indicator"></span>
<span class="fld-account-name"></span>
</a>

View file

@ -0,0 +1,5 @@
<a class="fld-folder-item">
<span class="selected-indicator"></span>
<span dir="auto" class="fld-folder-name"></span>
<span class="fld-folder-unread"></span>
</a>

View file

@ -0,0 +1,71 @@
<!-- Our card does not have a header of its own; it reuses the message_list's
header. We want to steal clicks on its menu button too, hence this node
and fld-header-back which is a transparent button that overlays the menu
button. -->
<div class="fld-header-placeholder" data-statuscolor="default">
<!-- Unlike a generic back button that navigates to a different screen, folder
picker header button triggers the folders and settings overlay closure.
Thus the screen reader user requires more context as to what activating
the button would do. -->
<button aria-expanded="true" aria-controls="cards-folder-picker"
data-l10n-id="message-list-menu"
data-event="click:_closeCard"
class="fld-header-back"></button>
</div>
<!-- Backing semi-opaque layer for everything below the header so that
anything that the folder drawer does not cover looks inactive-ish.
Clicking on this makes the drawer go away. -->
<div data-event="click:_closeCard" class="fld-shield"></div>
<!-- This exists to clip fld-content so that when it animates in/out using
translateY it doesn't paint over-top of the header. -->
<div class="fld-content-container">
<!-- This is the actual drawer thing that slides up/down using translateY.
-->
<div class="fld-content">
<!-- Scrollable container holds everything but the non-scrolling settings
button. -->
<div class="fld-acct-scrollouter">
<!-- Combo-box style widget that shows the current account name when
collapsed and the fld-acct-header-account-header label when
expanded. The actual items live in fld-accountlist-container
for animation reasons; see below. -->
<a data-prop="accountHeader"
data-event="click:toggleAccounts"
class="fld-acct-header closed" role="region">
<!--
The &nbsp; is on purpose, so that initial height measurement of the
element be correct even before this element has real content.
-->
<span class="fld-acct-header-account-label">&nbsp;</span>
<span class="fld-acct-header-account-header"
data-l10n-id="drawer-accounts-header"></span>
<span class="fld-account-switch-arrow"></span>
</a>
<!-- Exists to clip fld-acct-container which uses translateY to hide the
list of accounts in negative-space until the account list is
opened. This has an explicit height style set based on whether the
list of accounts is displayed or not because fld-acct-scrollinner
will always have the same height for layout purposes because the
translateY hiding does not affect layout. -->
<div data-prop="fldAcctScrollInner" class="fld-acct-scrollinner">
<!-- The list of all accounts and the folders for the current account.
As noted above, translateY is used to hide the list of accounts
when we don't want to be displaying it. -->
<div data-prop="fldAcctContainer" class="fld-acct-container">
<!-- The list of accounts -->
<div data-prop="accountListContainer"
class="fld-accountlist-container">
</div>
<!-- The list of folders for the current account. -->
<div data-prop="foldersContainer"
class="fld-folders-container">
</div>
</div>
</div>
</div>
<!-- settings button; always present; does not scroll -->
<a data-event="click:onShowSettings" class="fld-nav-toolbar bottom-toolbar">
<span class="fld-settings-link" data-l10n-id="drawer-settings-link"></span>
</a>
</div>
</div>

View file

@ -0,0 +1,213 @@
define([
'require',
'container_listen',
'tmpl!./fld/folder_item.html',
'tmpl!./fld/account_item.html',
'folder_depth_classes',
'cards',
'evt',
'transition_end',
'css!style/folder_cards',
'./base_card',
'template!./folder_picker.html'
], function (require) {
var containerListen = require('container_listen'), fldFolderItemNode = require('tmpl!./fld/folder_item.html'), fldAccountItemNode = require('tmpl!./fld/account_item.html'), FOLDER_DEPTH_CLASSES = require('folder_depth_classes'), cards = require('cards'), evt = require('evt'), transitionEnd = require('transition_end');
require('css!style/folder_cards');
return [
require('./base_card')(require('template!./folder_picker.html')),
{
createdCallback: function () {
containerListen(this.foldersContainer, 'click', this.onClickFolder.bind(this));
containerListen(this.accountListContainer, 'click', this.onClickAccount.bind(this));
transitionEnd(this, this.onTransitionEnd.bind(this));
this.updateAccount = this.updateAccount.bind(this);
},
onArgs: function (args) {
var model = this.model = args.model;
model.latest('account', this.updateAccount);
var accountCount = model.getAccountCount();
if (accountCount > 1) {
this.classList.remove('one-account');
this.currentAccountContainerHeight = this.accountHeader.getBoundingClientRect().height * accountCount;
this.hideAccounts();
}
this.acctsSlice = model.api.viewAccounts(false);
this.acctsSlice.onsplice = this.onAccountsSplice.bind(this);
this.acctsSlice.onchange = this.onAccountsChange.bind(this);
},
extraClasses: [
'anim-vertical',
'anim-overlay',
'one-account'
],
onShowSettings: function (event) {
cards.pushCard('settings_main', 'animate');
},
updateAccount: function (account) {
var oldAccount = this.curAccount;
this.mostRecentSyncTimestamp = 0;
if (oldAccount !== account) {
this.foldersContainer.innerHTML = '';
this.model.latestOnce('folder', function (folder) {
this.curAccount = account;
this.querySelector('.fld-acct-header-account-label').textContent = account.name;
if (!this.curFolder) {
this.curFolder = folder;
}
if (this.foldersSlice) {
this.foldersSlice.onsplice = null;
this.foldersSlice.onchange = null;
}
this.foldersSlice = this.model.foldersSlice;
this.onFoldersSplice(0, 0, this.foldersSlice.items, true, false);
this.foldersSlice.onsplice = this.onFoldersSplice.bind(this);
}.bind(this));
}
},
onClickAccount: function (accountNode, event) {
var oldAccountId = this.curAccount.id, accountId = accountNode.account.id;
this.curAccount = accountNode.account;
if (oldAccountId !== accountId) {
this._waitingAccountId = accountId;
this._closeCard();
}
},
toggleAccounts: function () {
var hadAnimated = this.fldAcctContainer.classList.contains('animated');
if (!hadAnimated) {
this.fldAcctContainer.classList.add('animated');
this.fldAcctContainer.clientWidth;
}
if (this.accountHeader.classList.contains('closed')) {
this.showAccounts();
} else {
this.hideAccounts();
}
},
showAccounts: function () {
var height = this.currentAccountContainerHeight;
this.fldAcctScrollInner.style.height = height + this.foldersContainer.getBoundingClientRect().height + 'px';
this.fldAcctContainer.style.transform = 'translateY(0)';
this.accountHeader.classList.remove('closed');
},
hideAccounts: function () {
var foldersHeight = this.foldersContainer.getBoundingClientRect().height;
if (foldersHeight) {
this.fldAcctScrollInner.style.height = foldersHeight + 'px';
}
this.fldAcctContainer.style.transform = 'translateY(-' + this.currentAccountContainerHeight + 'px)';
this.accountHeader.classList.add('closed');
},
onAccountsSplice: function (index, howMany, addedItems, requested, moreExpected) {
var accountListContainer = this.accountListContainer;
var postSliceCount = this.acctsSlice.items.length + addedItems.length - howMany;
this.classList.toggle('one-account', postSliceCount <= 1);
var account;
if (howMany) {
for (var i = index + howMany - 1; i >= index; i--) {
account = this.acctsSlice.items[i];
if (account.element) {
accountListContainer.removeChild(account.element);
}
}
}
var insertBuddy = index >= accountListContainer.childElementCount ? null : accountListContainer.children[index];
addedItems.forEach(function (account) {
var accountNode = account.element = fldAccountItemNode.cloneNode(true);
accountNode.account = account;
this.updateAccountDom(account, true);
accountListContainer.insertBefore(accountNode, insertBuddy);
}.bind(this));
this.currentAccountContainerHeight = this.accountHeader.getBoundingClientRect().height * accountListContainer.children.length;
this.hideAccounts();
},
onAccountsChange: function (account) {
this.updateAccountDom(account, false);
},
updateAccountDom: function (account, firstTime) {
var accountNode = account.element;
if (firstTime) {
accountNode.querySelector('.fld-account-name').textContent = account.name;
if (this.curAccount && this.curAccount.id === account.id) {
accountNode.classList.add('fld-account-selected');
}
}
},
onFoldersSplice: function (index, howMany, addedItems, requested, moreExpected) {
var foldersContainer = this.foldersContainer;
var folder;
if (howMany) {
for (var i = index + howMany - 1; i >= index; i--) {
folder = this.foldersSlice.items[i];
foldersContainer.removeChild(folder.element);
}
}
var insertBuddy = index >= foldersContainer.childElementCount ? null : foldersContainer.children[index], self = this;
addedItems.forEach(function (folder) {
var folderNode = folder.element = fldFolderItemNode.cloneNode(true);
folderNode.folder = folder;
self.updateFolderDom(folder, true);
foldersContainer.insertBefore(folderNode, insertBuddy);
});
},
updateFolderDom: function (folder, firstTime) {
var folderNode = folder.element;
if (firstTime) {
if (!folder.selectable) {
folderNode.classList.add('fld-folder-unselectable');
}
var depthIdx = Math.min(FOLDER_DEPTH_CLASSES.length - 1, folder.depth);
folderNode.classList.add(FOLDER_DEPTH_CLASSES[depthIdx]);
if (depthIdx > 0) {
folderNode.classList.add('fld-folder-depthnonzero');
}
folderNode.querySelector('.fld-folder-name').textContent = folder.name;
folderNode.dataset.type = folder.type;
}
if (folder === this.curFolder) {
folderNode.classList.add('fld-folder-selected');
} else {
folderNode.classList.remove('fld-folder-selected');
}
},
onClickFolder: function (folderNode, event) {
var folder = folderNode.folder;
if (!folder.selectable) {
return;
}
var oldFolder = this.curFolder;
this.curFolder = folder;
this.updateFolderDom(oldFolder);
this.updateFolderDom(folder);
this._showFolder(folder);
this._closeCard();
},
onTransitionEnd: function (event) {
if (!this.classList.contains('opened') && event.target.classList.contains('fld-content')) {
cards.removeCardAndSuccessors(this, 'none');
if (this._waitingAccountId) {
var model = this.model;
model.changeAccountFromId(this._waitingAccountId, function () {
model.selectInbox();
});
this._waitingAccountId = null;
}
}
},
_closeCard: function () {
evt.emit('folderPickerClosing');
this.classList.remove('opened');
},
_showFolder: function (folder) {
this.model.changeFolder(folder);
},
onCardVisible: function () {
this.classList.add('opened');
},
die: function () {
this.acctsSlice.die();
this.model.removeListener('account', this.updateAccount);
}
}
];
});

View file

@ -0,0 +1,12 @@
<section role="region">
<header>
<a href="#"
data-event="click:domEvt"
data-domevt-name="editHeaderClose"
role="button"
data-l10n-id="close-button">
<span class="icon icon-close"></span>
</a>
<h1 data-prop="headerNode"></h1>
</header>
</section>

View file

@ -0,0 +1,31 @@
<!-- Toolbar for multi-edit state -->
<ul class="bb-tablist" role="toolbar">
<li role="presentation">
<button data-prop="deleteBtn"
data-event="click:domEvt"
data-domevt-name="onDeleteMessages"
class="icon msg-delete-btn"
data-l10n-id="message-delete-button"></button>
</li>
<li role="presentation">
<button data-prop="starBtn"
data-event="click:domEvt"
data-domevt-name="onStarMessages"
class="icon msg-star-btn"
data-l10n-id="message-star-button"></button>
</li>
<li role="presentation">
<button data-prop="readBtn"
data-event="click:domEvt"
data-domevt-name="onMarkMessagesRead"
class="icon msg-mark-read-btn"
data-l10n-id="message-mark-read-button"></button>
</li>
<li role="presentation">
<button data-prop="moveBtn"
data-event="click:domEvt"
data-domevt-name="onMoveMessages"
class="icon msg-move-btn"
data-l10n-id="message-move-button"></button>
</li>
</ul>

View file

@ -0,0 +1,22 @@
<div data-prop="vScrollContainer" class="msg-vscroll-container"></div>
<!-- maintain vertical space for the syncing/sync more div's
regardless of their displayed status so we don't scroll them
out of the way -->
<div class="msg-messages-sync-container">
<p data-prop="syncingNode" class="msg-messages-syncing collapsed"
role="progressbar" data-l10n-id="messages-syncing-progressbar">
<span data-l10n-id="messages-syncing"></span>
</p>
<p data-prop="syncMoreNode"
data-event="click:onGetMoreMessages"
class="msg-messages-sync-more collapsed"
role="button">
<span data-l10n-id="messages-load-more"></span>
</p>
</div>
<div data-prop="messageEmptyContainer"
class="msg-list-empty-container collapsed">
<p data-prop="messageEmptyText"
class="msg-list-empty-message-text"
data-l10n-id="messages-folder-empty"></p>
</div>

View file

@ -0,0 +1,89 @@
<!-- Non-search header -->
<section data-prop="normalHeader"
class="msg-list-header msg-nonsearch-only"
data-statuscolor="default"
role="region">
<header>
<!-- Unlike a generic back button that navigates to a different screen,
folder list header button triggers the folders and settings overlay. Thus
the screen reader user requires more context as to what activating the
button would do. -->
<a href="#" class="msg-folder-list-btn" data-event="click:onShowFolders"
aria-expanded="false" aria-controls="cards-folder-picker"
role="button" data-l10n-id="message-list-menu">
<span class="icon icon-menu"></span>
</a>
<menu data-prop="headerMenuNode" type="toolbar" class="anim-opacity">
<a href="#" class="msg-compose-btn" data-event="click:onCompose"
data-l10n-id="message-list-compose">
<span class="icon icon-compose"></span>
</a>
</menu>
<h1 data-prop="folderLabel"
class="msg-list-header-folder-label header-label">
<span data-prop="folderNameNode"
dir="auto"
class="msg-list-header-folder-name"></span>
<span data-prop="folderUnread"
class="msg-list-header-folder-unread collapsed"></span>
</h1>
</header>
</section>
<!-- Multi-edit state header -->
<lst-edit-header data-prop="editHeader"
data-event="editHeaderClose"></lst-edit-header>
<!-- Scroll region -->
<div data-prop="scrollContainer" class="msg-list-scrollouter">
<!-- exists so we can force a minimum height -->
<div class="msg-list-scrollinner">
<!-- The search textbox hides under the lip of the messages.
As soon as any typing happens in it, we push the search
controls card. -->
<form role="search" data-prop="searchBar"
class="msg-search-tease-bar msg-nonsearch-only">
<p>
<input data-event="focus:onSearchButton"
data-prop="searchTextTease"
class="msg-search-text-tease" type="text"
dir="auto"
data-l10n-id="message-search-input" />
</p>
</form>
<lst-msg-vscroll data-prop="msgVScroll"
data-event="messageClick:onClickMessage"
data-empty-l10n-id="messages-folder-empty">
</lst-msg-vscroll>
</div>
</div>
<!-- New email notification bar -->
<div class="message-list-topbar"></div>
<!-- Toolbar for non-multi-edit state -->
<ul data-prop="normalToolbar" class="bb-tablist msg-list-action-toolbar"
role="toolbar">
<li role="presentation" class="msg-nonsearch-only">
<button data-prop="refreshBtn" data-event="click:onRefresh"
class="icon msg-refresh-btn" data-state="synchronized"
data-l10n-id="messages-refresh-button">
</button>
</li>
<li role="status" class="msg-nonsearch-only msg-last-sync">
<span data-prop="lastSyncedLabel"
class="msg-last-synced-label"
data-l10n-id="folder-last-synced-label"></span>
<span data-prop="lastSyncedAtNode"
class="msg-last-synced-value"></span>
</li>
<li role="presentation">
<button data-prop="editBtn" data-event="click:setEditModeStart"
class="icon msg-edit-btn" data-l10n-id="edit-button"></button>
</li>
</ul>
<lst-edit-toolbar data-prop="editToolbar"
data-event="onDeleteMessages,onStarMessages,
onMarkMessagesRead,onMoveMessages">
</lst-edit-toolbar>

View file

@ -0,0 +1,81 @@
<!-- Search header -->
<section data-prop="searchHeader"
class="msg-search-header"
role="region">
<form role="search" data-event="submit:onSearchSubmit">
<button data-event="click:onCancelSearch"
class="msg-search-cancel" type="submit"
data-l10n-id="message-search-cancel"></button>
<p>
<input data-prop="searchInput" data-event="input:onSearchTextChange"
type="text" required="required" class="msg-search-text"
autocorrect="off"
inputmode="verbatim"
x-inputmode="verbatim"
dir="auto"
data-l10n-id="message-search-input" />
<button type="reset"
data-l10n-id="form-clear-input"
data-event="click:onClearSearch"></button>
</p>
</form>
<!-- Search filter switcher -->
<header class="msg-search-controls-bar">
<ul role="tablist" class="bb-tablist filter" data-type="filter">
<li role="presentation" class="msg-search-from msg-search-filter"
data-filter="author">
<a data-l10n-id="message-search-from" role="tab"
aria-selected="false"></a></li>
<li role="presentation" class="msg-search-to msg-search-filter"
data-filter="recipients">
<a data-l10n-id="message-search-to" role="tab"
aria-selected="false"></a></li>
<li role="presentation" class="msg-search-subject msg-search-filter"
data-filter="subject">
<a data-l10n-id="message-search-subject" role="tab"
aria-selected="false"></a>
</li>
<li role="presentation" class="msg-search-body msg-search-filter"
data-filter="body">
<a data-l10n-id="message-search-body" role="tab"
aria-selected="false"></a></li>
<li role="presentation" class="msg-search-body msg-search-filter"
data-filter="all">
<a data-l10n-id="message-search-all" role="tab"
aria-selected="true"></a></li>
</ul>
</header>
</section>
<!-- Multi-edit state header -->
<lst-edit-header data-prop="editHeader"
data-event="editHeaderClose"></lst-edit-header>
<!-- Scroll region -->
<div data-prop="scrollContainer" class="msg-list-scrollouter">
<!-- exists so we can force a minimum height -->
<div class="msg-list-scrollinner">
<lst-msg-vscroll data-prop="msgVScroll"
data-event="messageClick:onClickMessage"
data-empty-l10n-id="messages-search-empty">
</lst-msg-vscroll>
</div>
</div>
<!-- New email notification bar -->
<div class="message-list-topbar"></div>
<!-- Toolbar for non-multi-edit state -->
<ul data-prop="normalToolbar" class="bb-tablist msg-list-action-toolbar"
role="toolbar">
<li role="presentation">
<button data-prop="editBtn" data-event="click:setEditModeStart"
class="icon msg-edit-btn" data-l10n-id="edit-button"></button>
</li>
</ul>
<lst-edit-toolbar data-prop="editToolbar"
data-event="onDeleteMessages,onStarMessages,
onMarkMessagesRead,onMoveMessages">
</lst-edit-toolbar>

View file

@ -0,0 +1,205 @@
define([
'require',
'exports',
'module',
'shared/js/accessibility_helper',
'cards',
'container_listen',
'date',
'header_cursor',
'message_display',
'message_list_topbar',
'./lst/peep_dom',
'./lst/default_vscroll_data',
'./base_card',
'template!./message_list_search.html',
'./lst/edit_controller',
'./lst/msg_click'
], function (require, exports) {
var accessibilityHelper = require('shared/js/accessibility_helper'), cards = require('cards'), containerListen = require('container_listen'), date = require('date'), HeaderCursor = require('header_cursor'), messageDisplay = require('message_display'), MessageListTopBar = require('message_list_topbar'), updatePeepDom = require('./lst/peep_dom').update;
var defaultSearchVScrollData = {
header: require('./lst/default_vscroll_data'),
matches: []
};
var MATCHED_TEXT_CLASS = 'highlight';
function appendMatchItemTo(matchItem, node) {
var text = matchItem.text;
var idx = 0;
for (var iRun = 0; iRun <= matchItem.matchRuns.length; iRun++) {
var run;
if (iRun === matchItem.matchRuns.length) {
run = {
start: text.length,
length: 0
};
} else {
run = matchItem.matchRuns[iRun];
}
if (run.start > idx) {
var tnode = document.createTextNode(text.substring(idx, run.start));
node.appendChild(tnode);
}
if (!run.length) {
continue;
}
var hspan = document.createElement('span');
hspan.classList.add(MATCHED_TEXT_CLASS);
hspan.textContent = text.substr(run.start, run.length);
node.appendChild(hspan);
idx = run.start + run.length;
}
}
return [
require('./base_card')(require('template!./message_list_search.html')),
require('./lst/edit_controller'),
require('./lst/msg_click'),
{
createdCallback: function () {
containerListen(this.querySelector('.filter'), 'click', this.onSearchFilterClick.bind(this));
this.searchFilterTabs = this.querySelectorAll('.filter [role="tab"]');
this.isFirstTimeVisible = true;
this._folderChanged = this._folderChanged.bind(this);
this.msgVScroll.on('emptyLayoutShown', function () {
this.editBtn.disabled = true;
}.bind(this));
this.msgVScroll.on('emptyLayoutHidden', function () {
this.editBtn.disabled = false;
}.bind(this));
this.msgVScroll.on('messagesChange', function (message, index) {
this.updateMatchedMessageDom(message);
}.bind(this));
this.msgVScroll.on('messagesComplete', function (newEmailCount) {
if (!this.msgVScroll.vScroll.list) {
this.msgVScroll.vScroll.setData(this.msgVScroll.listFunc);
}
}.bind(this));
var vScrollBindData = function bindSearch(model, node) {
model.element = node;
node.message = model.header;
this.updateMatchedMessageDom(model);
}.bind(this);
this.msgVScroll.init(this.scrollContainer, vScrollBindData, defaultSearchVScrollData);
this._topBar = new MessageListTopBar(this.querySelector('.message-list-topbar'));
this._topBar.bindToElements(this.scrollContainer, this.msgVScroll.vScroll);
},
onArgs: function (args) {
var model = this.model = args.model;
var headerCursor = this.headerCursor = args.headerCursor || new HeaderCursor(model);
this.msgVScroll.setHeaderCursor(headerCursor);
model.latest('folder', this._folderChanged);
},
onCardVisible: function () {
if (this.isFirstTimeVisible) {
this.searchInput.focus();
}
this.msgVScroll.vScroll.nowVisible();
this.isFirstTimeVisible = false;
},
showSearch: function (phrase, filter) {
console.log('sf: showSearch. phrase:', phrase, phrase.length);
this.curFolder = this.model.folder;
this.editModeEnabled = true;
this.msgVScroll.vScroll.clearDisplay();
this.curPhrase = phrase;
this.curFilter = filter;
this.msgVScroll._snippetRequestPending = false;
this.msgVScroll.waitingOnChunk = true;
this.headerCursor.startSearch(phrase, {
author: filter === 'all' || filter === 'author',
recipients: filter === 'all' || filter === 'recipients',
subject: filter === 'all' || filter === 'subject',
body: filter === 'all' || filter === 'body'
});
return true;
},
onSearchFilterClick: function (filterNode, event) {
accessibilityHelper.setAriaSelected(filterNode.firstElementChild, this.searchFilterTabs);
this.showSearch(this.searchInput.value, filterNode.dataset.filter);
},
onSearchTextChange: function (event) {
console.log('sf: typed, now:', this.searchInput.value);
this.showSearch(this.searchInput.value, this.curFilter);
},
onSearchSubmit: function (event) {
event.preventDefault();
this.searchInput.blur();
},
onCancelSearch: function (event) {
if (event.explicitOriginalTarget !== event.target) {
return;
}
try {
this.headerCursor.endSearch();
} catch (ex) {
console.error('problem killing slice:', ex, '\n', ex.stack);
}
cards.removeCardAndSuccessors(this, 'animate');
},
onClearSearch: function () {
this.showSearch('', this.curFilter);
},
editModeChanged: function (enabled) {
if (enabled) {
cards.setStatusColor(this.editHeader);
} else {
cards.setStatusColor(this);
}
},
updateMatchedMessageDom: function (matchedHeader) {
var msgNode = matchedHeader.element, matches = matchedHeader.matches, message = matchedHeader.header;
if (!msgNode) {
return;
}
var classAction = message.isPlaceholderData ? 'add' : 'remove';
var defaultDataClass = this.msgVScroll.vScroll.itemDefaultDataClass;
msgNode.classList[classAction](defaultDataClass);
msgNode.dataset.id = matchedHeader.id;
var dateNode = msgNode.querySelector('.msg-header-date');
var subjectNode = msgNode.querySelector('.msg-header-subject');
var authorNode = msgNode.querySelector('.msg-header-author');
if (matches.author) {
authorNode.textContent = '';
appendMatchItemTo(matches.author, authorNode);
} else {
message.author.element = authorNode;
message.author.onchange = updatePeepDom;
message.author.onchange(message.author);
}
var dateTime = dateNode.dataset.time = message.date.valueOf();
date.relativeDateElement(dateNode, dateTime);
if (matches.subject) {
subjectNode.textContent = '';
appendMatchItemTo(matches.subject[0], subjectNode);
} else {
messageDisplay.subject(subjectNode, message);
}
var snippetNode = msgNode.querySelector('.msg-header-snippet');
if (matches.body) {
snippetNode.textContent = '';
appendMatchItemTo(matches.body[0], snippetNode);
} else {
snippetNode.textContent = message.snippet;
}
var attachmentsNode = msgNode.querySelector('.msg-header-attachments');
attachmentsNode.classList.toggle('msg-header-attachments-yes', message.hasAttachments);
snippetNode.classList.toggle('icon-short', message.hasAttachments);
msgNode.classList.toggle('unread', !message.isRead);
var starNode = msgNode.querySelector('.msg-header-star');
starNode.classList.toggle('msg-header-star-starred', message.isStarred);
subjectNode.classList.toggle('icon-short', message.isStarred);
this.updateDomSelectState(msgNode, message);
},
_folderChanged: function (folder) {
if (!this.model.foldersSlice) {
return;
}
this.curFolder = folder;
this.showSearch('', 'all');
},
die: function () {
this.msgVScroll.die();
this.model.removeListener('folder', this._folderChanged);
}
}
];
});

View file

@ -0,0 +1,92 @@
<section class="msg-reader-header" role="region" data-statuscolor="default">
<header>
<a data-event="click:onBack" href="#" class="msg-back-btn"
role="button" data-l10n-id="back-button">
<span class="icon icon-back"></span>
</a>
<menu type="toolbar">
<button data-prop="previousBtn" class="msg-up-btn"
data-l10n-id="message-reader-next-button">
<span data-prop="previousIcon" class="icon icon-up"></span>
</button>
<button data-prop="nextBtn" class="msg-down-btn"
data-l10n-id="message-reader-previous-button">
<span data-prop="nextIcon" class="icon icon-down"></span>
</button>
</menu>
<h1 dir="auto" class="msg-reader-header-label"></h1>
</header>
</section>
<div data-prop="scrollContainer"
class="scrollregion-below-header scrollregion-horizontal-too">
<div data-prop="envelopeBar" class="msg-envelope-bar">
<div class="msg-envelope-line msg-envelope-from-line"
role="listbox" data-l10n-id="from-group">
<span class="msg-envelope-key"
data-l10n-id="envelope-from" aria-hidden="true"></span>
</div>
<!-- the details starts out collapsed, but can be toggled -->
<div class="msg-envelope-details">
<div class="msg-envelope-line msg-envelope-to-line"
role="listbox" data-l10n-id="to-group">
<span class="msg-envelope-key"
data-l10n-id="envelope-to" aria-hidden="true"></span>
</div>
<div class="msg-envelope-line msg-envelope-cc-line"
role="listbox" data-l10n-id="cc-group">
<span class="msg-envelope-key"
data-l10n-id="envelope-cc" aria-hidden="true"></span>
</div>
<div class="msg-envelope-line msg-envelope-bcc-line"
role="listbox" data-l10n-id="bcc-group">
<span class="msg-envelope-key"
data-l10n-id="envelope-bcc" aria-hidden="true"></span>
</div>
</div>
<div class="msg-envelope-subject-container">
<h3 aria-level="2" class="msg-envelope-subject"
data-l10n-id="subject" dir="auto"></h3>
<span role="heading" aria-level="3" class="msg-envelope-date"
data-l10n-id="message-reader-date-received"></span>
</div>
</div>
<ul data-prop="attachmentsContainer"
class="msg-attachments-container collapsed"
data-l10n-id="attachments-container">
</ul>
<!-- Tells us about remote/not downloaded images, asks to show -->
<div data-prop="loadBar" class="msg-reader-load-infobar collapsed"
role="button">
<p data-prop="loadBarText" class="msg-reader-load-infobar-text"></p>
<button class="msg-reader-load-infobar-button"
aria-hidden="true"></button>
</div>
<div data-prop="rootBodyNode"
class="msg-body-container"
data-l10n-id="message-body-container">
<progress data-l10n-id="message-body-container-progress"></progress>
</div>
</div>
<ul class="bb-tablist msg-reader-action-toolbar" role="toolbar">
<li role="presentation">
<button data-prop="deleteBtn" class="icon msg-delete-btn"
data-l10n-id="message-delete-button"></button>
</li>
<li role="presentation">
<button data-prop="starBtn" class="icon msg-star-btn"
data-l10n-id="message-star-button"></button>
</li>
<li role="presentation">
<button data-prop="readBtn" class="icon msg-mark-read-btn"
data-l10n-id="message-mark-read-button"></button>
</li>
<li role="presentation">
<button data-prop="moveBtn" class="icon msg-move-btn"
data-l10n-id="message-move-button"></button>
</li>
<li role="presentation">
<button data-prop="replyBtn" class="icon msg-reply-btn"
data-l10n-id="message-reply-forward-button"></button>
</li>
</ul>

View file

@ -0,0 +1,876 @@
define('tmpl!cards/msg/contact_menu.html', ['tmpl'], function (tmpl) {
return tmpl.toDom('<form class="msg-contact-menu" role="dialog" data-type="action">\n\xA0 \xA0<header></header>\n\xA0 \xA0<menu>\n <button class="msg-contact-menu-new collapsed" data-l10n-id="message-contact-menu-new">\n </button>\n <button class="msg-contact-menu-reply collapsed" data-l10n-id="message-contact-menu-reply">\n </button>\n <button class="msg-contact-menu-view collapsed" data-l10n-id="message-contact-menu-view">\n </button>\n <button class="msg-contact-menu-create-contact collapsed" data-l10n-id="message-contact-menu-create">\n </button>\n <button class="msg-contact-menu-add-to-existing-contact collapsed" data-l10n-id="message-contact-menu-add-existing">\n </button>\n <span class="last-button-container">\n <button class="msg-contact-menu-cancel" data-l10n-id="message-multiedit-cancel">\n </button>\n </span>\n\xA0 \xA0</menu>\n\xA0</form>\n');
});
define('tmpl!cards/msg/reply_menu.html', ['tmpl'], function (tmpl) {
return tmpl.toDom('<form class="msg-reply-menu" role="dialog" data-type="action">\n <header></header>\n <menu>\n <button class="msg-reply-menu-reply" data-l10n-id="message-reply-menu-reply">\n </button>\n <button class="msg-reply-menu-reply-all" data-l10n-id="message-reply-menu-reply-all">\n </button>\n <button class="msg-reply-menu-forward" data-l10n-id="message-reply-menu-forward">\n </button>\n <span class="last-button-container">\n <button class="msg-reply-menu-cancel" data-l10n-id="message-reply-menu-cancel">\n </button>\n </span>\n </menu>\n</form>\n');
});
define('tmpl!cards/msg/browse_confirm.html', ['tmpl'], function (tmpl) {
return tmpl.toDom('<form role="dialog" class="msg-browse-confirm" data-type="confirm">\n <section>\n <h1 data-l10n-id="confirm-dialog-title"></h1>\n <p></p>\n </section>\n <menu>\n <button id="msg-browse-cancel" data-l10n-id="message-multiedit-cancel"></button>\n <button id="msg-browse-ok" class="recommend" data-l10n-id="dialog-button-ok"></button>\n </menu>\n</form>');
});
define('tmpl!cards/msg/peep_bubble.html', ['tmpl'], function (tmpl) {
return tmpl.toDom('<div class="msg-peep-bubble peep-bubble" dir="auto" role="option">\n <span class="msg-peep-content"></span>\n</div>\n');
});
define('tmpl!cards/msg/attachment_item.html', ['tmpl'], function (tmpl) {
return tmpl.toDom('<li class="msg-attachment-item">\n <span class="msg-attachment-icon" role="presentation"></span>\n <span class="msg-attachment-fileinfo">\n <span dir="auto" class="msg-attachment-filename"></span>\n <span class="msg-attachment-filesize"></span>\n <span data-l10n-id="message-attachment-too-large"\n class="msg-attachment-too-large"></span>\n </span>\n <button class="msg-attachment-download" data-l10n-id="download-button">\n <span class="icon icon-download"></span></button>\n <span class="msg-attachment-downloading">\n <progress class="small" data-l10n-id="downloading-progress"></progress>\n </span>\n <button class="msg-attachment-view" data-l10n-id="view-button">\n <span data-l10n-id="message-attachment-view" class="icon icon-view"></span>\n </button>\n</li>\n');
});
define('tmpl!cards/msg/attachment_disabled_confirm.html', ['tmpl'], function (tmpl) {
return tmpl.toDom('<form role="dialog" class="msg-attachment-disabled-confirm" data-type="confirm">\n <section>\n <p data-l10n-id="message-send-attachment-disabled-confirm"></p>\n </section>\n <menu>\n <button id="msg-attachment-disabled-cancel" data-l10n-id="message-multiedit-cancel"></button>\n <button id="msg-attachment-disabled-ok" data-l10n-id="dialog-button-ok"></button>\n </menu>\n</form>\n');
});
define('tmpl!cards/msg/attachment_did_not_open_alert.html', ['tmpl'], function (tmpl) {
return tmpl.toDom('<form role="dialog" class="msg-attachment-did-not-open" data-type="confirm">\n <section>\n <h1 data-l10n-id="message-attachment-did-not-open-label"></h1>\n <p><span data-l10n-id="message-attachment-did-not-open-body"></span></p>\n </section>\n <menu>\n <button id="msg-attachment-did-not-open-ok" data-l10n-id="dialog-button-ok"></button>\n </menu>\n</form>\n');
});
define('marquee', [], function () {
var Marquee = {
timingFunction: [
'linear',
'ease'
],
setup: function marquee_setup(text, headerNode) {
this._headerNode = headerNode;
this._headerWrapper = document.getElementById('marquee-h-wrapper');
if (!this._headerWrapper) {
this._headerWrapper = document.createElement('div');
this._headerWrapper.id = 'marquee-h-wrapper';
this._headerNode.appendChild(this._headerWrapper);
}
var headerText = document.getElementById('marquee-h-text');
if (!headerText) {
headerText = document.createElement('div');
headerText.id = 'marquee-h-text';
this._headerWrapper.dir = 'auto';
this._headerWrapper.appendChild(headerText);
}
headerText.textContent = text;
},
activate: function marquee_activate(behavior, timingFun) {
if (!this._headerNode || !this._headerWrapper) {
return;
}
var mode = behavior || 'scroll';
var tf = timingFun || null;
var timing = Marquee.timingFunction.indexOf(tf) >= 0 ? tf : 'linear';
var marqueeCssClass = 'marquee';
var titleText = document.getElementById('marquee-h-text');
var cssClass, width;
if (this._headerWrapper.clientWidth < this._headerWrapper.scrollWidth) {
this._marqueeCssClassList = [];
switch (mode) {
case 'scroll':
cssClass = marqueeCssClass + '-rtl';
width = this._headerWrapper.scrollWidth;
titleText.style.width = width + 'px';
titleText.classList.add(cssClass + '-start-' + timing);
this._marqueeCssClassList.push(cssClass + '-start-' + timing);
var self = this;
titleText.addEventListener('animationend', function () {
titleText.classList.remove(cssClass + '-start-' + timing);
this._marqueeCssClassList.pop();
var visibleWidth = self._headerWrapper.clientWidth + 'px';
titleText.style.transform = 'translateX(' + visibleWidth + ')';
titleText.classList.add(cssClass);
this._marqueeCssClassList.push(cssClass);
});
break;
case 'alternate':
var dirSuffix = '';
if (window.getComputedStyle(titleText).direction === 'rtl') {
dirSuffix = '-rtl';
}
timing += dirSuffix;
cssClass = marqueeCssClass + '-alt-';
width = this._headerWrapper.scrollWidth - this._headerWrapper.clientWidth;
titleText.style.width = width + 'px';
titleText.classList.add(cssClass + timing);
break;
}
} else {
if (!this._marqueeCssClassList) {
return;
}
for (var titleCssClass in this._marqueeCssClassList) {
titleText.classList.remove(titleCssClass);
}
titleText.style.transform = '';
}
}
};
return Marquee;
});
define('mime_to_class', [], function () {
return function mimeToClass(mimeType) {
mimeType = mimeType || '';
return 'mime-' + (mimeType.split('/')[0] || '');
};
});
define('file_display', [
'require',
'l10n!'
], function (require) {
var mozL10n = require('l10n!');
return {
fileSize: function (node, sizeInBytes) {
var kilos = Math.ceil(sizeInBytes / 1024);
mozL10n.setAttributes(node, 'attachment-size-kib', { kilobytes: kilos });
}
};
});
define('template!cards/message_reader.html', ['template'], function (template) {
return {
createdCallback: template.templateCreatedCallback,
template: template.objToFn({
'id': 'cards/message_reader.html',
'deps': [],
'text': '<section class="msg-reader-header" role="region" data-statuscolor="default">\n <header>\n <a data-event="click:onBack" href="#" class="msg-back-btn"\n role="button" data-l10n-id="back-button">\n <span class="icon icon-back"></span>\n </a>\n <menu type="toolbar">\n <button data-prop="previousBtn" class="msg-up-btn"\n data-l10n-id="message-reader-next-button">\n <span data-prop="previousIcon" class="icon icon-up"></span>\n </button>\n <button data-prop="nextBtn" class="msg-down-btn"\n data-l10n-id="message-reader-previous-button">\n <span data-prop="nextIcon" class="icon icon-down"></span>\n </button>\n </menu>\n <h1 dir="auto" class="msg-reader-header-label"></h1>\n </header>\n</section>\n<div data-prop="scrollContainer"\n class="scrollregion-below-header scrollregion-horizontal-too">\n <div data-prop="envelopeBar" class="msg-envelope-bar">\n <div class="msg-envelope-line msg-envelope-from-line"\n role="listbox" data-l10n-id="from-group">\n <span class="msg-envelope-key"\n data-l10n-id="envelope-from" aria-hidden="true"></span>\n </div>\n <!-- the details starts out collapsed, but can be toggled -->\n <div class="msg-envelope-details">\n <div class="msg-envelope-line msg-envelope-to-line"\n role="listbox" data-l10n-id="to-group">\n <span class="msg-envelope-key"\n data-l10n-id="envelope-to" aria-hidden="true"></span>\n </div>\n <div class="msg-envelope-line msg-envelope-cc-line"\n role="listbox" data-l10n-id="cc-group">\n <span class="msg-envelope-key"\n data-l10n-id="envelope-cc" aria-hidden="true"></span>\n </div>\n <div class="msg-envelope-line msg-envelope-bcc-line"\n role="listbox" data-l10n-id="bcc-group">\n <span class="msg-envelope-key"\n data-l10n-id="envelope-bcc" aria-hidden="true"></span>\n </div>\n </div>\n <div class="msg-envelope-subject-container">\n <h3 aria-level="2" class="msg-envelope-subject"\n data-l10n-id="subject" dir="auto"></h3>\n <span role="heading" aria-level="3" class="msg-envelope-date"\n data-l10n-id="message-reader-date-received"></span>\n </div>\n </div>\n <ul data-prop="attachmentsContainer"\n class="msg-attachments-container collapsed"\n data-l10n-id="attachments-container">\n </ul>\n <!-- Tells us about remote/not downloaded images, asks to show -->\n <div data-prop="loadBar" class="msg-reader-load-infobar collapsed"\n role="button">\n <p data-prop="loadBarText" class="msg-reader-load-infobar-text"></p>\n <button class="msg-reader-load-infobar-button"\n aria-hidden="true"></button>\n </div>\n <div data-prop="rootBodyNode"\n class="msg-body-container"\n data-l10n-id="message-body-container">\n <progress data-l10n-id="message-body-container-progress"></progress>\n </div>\n</div>\n\n<ul class="bb-tablist msg-reader-action-toolbar" role="toolbar">\n <li role="presentation">\n <button data-prop="deleteBtn" class="icon msg-delete-btn"\n data-l10n-id="message-delete-button"></button>\n </li>\n <li role="presentation">\n <button data-prop="starBtn" class="icon msg-star-btn"\n data-l10n-id="message-star-button"></button>\n </li>\n <li role="presentation">\n <button data-prop="readBtn" class="icon msg-mark-read-btn"\n data-l10n-id="message-mark-read-button"></button>\n </li>\n <li role="presentation">\n <button data-prop="moveBtn" class="icon msg-move-btn"\n data-l10n-id="message-move-button"></button>\n </li>\n <li role="presentation">\n <button data-prop="replyBtn" class="icon msg-reply-btn"\n data-l10n-id="message-reply-forward-button"></button>\n </li>\n</ul>\n'
})
};
});
define('cards/message_reader', [
'require',
'tmpl!./msg/delete_confirm.html',
'tmpl!./msg/contact_menu.html',
'tmpl!./msg/reply_menu.html',
'tmpl!./msg/browse_confirm.html',
'tmpl!./msg/peep_bubble.html',
'tmpl!./msg/attachment_item.html',
'tmpl!./msg/attachment_disabled_confirm.html',
'tmpl!./msg/attachment_did_not_open_alert.html',
'cards',
'confirm_dialog',
'date',
'toaster',
'header_cursor',
'evt',
'iframe_shims',
'marquee',
'l10n!',
'query_uri',
'mime_to_class',
'file_display',
'message_display',
'./base_card',
'template!./message_reader.html'
], function (require) {
var MimeMapper, msgDeleteConfirmNode = require('tmpl!./msg/delete_confirm.html'), msgContactMenuNode = require('tmpl!./msg/contact_menu.html'), msgReplyMenuNode = require('tmpl!./msg/reply_menu.html'), msgBrowseConfirmNode = require('tmpl!./msg/browse_confirm.html'), msgPeepBubbleNode = require('tmpl!./msg/peep_bubble.html'), msgAttachmentItemNode = require('tmpl!./msg/attachment_item.html'), msgAttachmentDisabledConfirmNode = require('tmpl!./msg/attachment_disabled_confirm.html'), msgAttachmentDidNotOpenAlertNode = require('tmpl!./msg/attachment_did_not_open_alert.html'), cards = require('cards'), ConfirmDialog = require('confirm_dialog'), date = require('date'), toaster = require('toaster'), HeaderCursor = require('header_cursor'), evt = require('evt'), iframeShims = require('iframe_shims'), Marquee = require('marquee'), mozL10n = require('l10n!'), queryURI = require('query_uri'), mimeToClass = require('mime_to_class'), fileDisplay = require('file_display'), messageDisplay = require('message_display');
var CONTENT_TYPES_TO_CLASS_NAMES = [
null,
'msg-body-content',
'msg-body-signature',
'msg-body-leadin',
null,
'msg-body-disclaimer',
'msg-body-list',
'msg-body-product',
'msg-body-ads'
];
var CONTENT_QUOTE_CLASS_NAMES = [
'msg-body-q1',
'msg-body-q2',
'msg-body-q3',
'msg-body-q4',
'msg-body-q5',
'msg-body-q6',
'msg-body-q7',
'msg-body-q8',
'msg-body-q9'
];
var MAX_QUOTE_CLASS_NAME = 'msg-body-qmax';
var OCTET_STREAM_TYPE = 'application/octet-stream';
function sendActivity(obj) {
return new MozActivity(obj);
}
return [
require('./base_card')(require('template!./message_reader.html')),
{
createdCallback: function () {
this.htmlBodyNodes = [];
this._on('msg-up-btn', 'click', 'onPrevious');
this._on('msg-down-btn', 'click', 'onNext');
this._on('msg-reply-btn', 'click', 'onReplyMenu');
this._on('msg-delete-btn', 'click', 'onDelete');
this._on('msg-star-btn', 'click', 'onToggleStar');
this._on('msg-move-btn', 'click', 'onMove');
this._on('msg-mark-read-btn', 'click', 'onMarkRead');
this._on('msg-envelope-bar', 'click', 'onEnvelopeClick');
this._on('msg-reader-load-infobar', 'click', 'onLoadBarClick');
this._emittedContentEvents = false;
this.disableReply();
this._builtBodyDom = false;
this.handleBodyChange = this.handleBodyChange.bind(this);
this.onMessageSuidNotFound = this.onMessageSuidNotFound.bind(this);
this.onCurrentMessage = this.onCurrentMessage.bind(this);
},
onArgs: function (args) {
this.model = args.model;
this.messageSuid = args.messageSuid;
var headerCursor = this.headerCursor = args.headerCursor || new HeaderCursor(this.model);
headerCursor.on('messageSuidNotFound', this.onMessageSuidNotFound);
headerCursor.latest('currentMessage', this.onCurrentMessage);
headerCursor.setCurrentMessage(this.header);
},
_contextMenuType: {
VIEW_CONTACT: 1,
CREATE_CONTACT: 2,
ADD_TO_CONTACT: 4,
REPLY: 8,
NEW_MESSAGE: 16
},
skipEmitContentEvents: true,
_on: function (className, eventName, method, skipProtection) {
this.getElementsByClassName(className)[0].addEventListener(eventName, function (evt) {
if (this.header || skipProtection) {
return this[method](evt);
}
}.bind(this), false);
},
_setHeader: function (header) {
this.header = header.makeCopy();
this.hackMutationHeader = header;
if (!this.header.isRead) {
this.header.setRead(true);
} else {
this.readBtn.classList.remove('unread');
mozL10n.setAttributes(this.readBtn, 'message-mark-read-button');
}
this.starBtn.classList.toggle('msg-star-btn-on', this.hackMutationHeader.isStarred);
this.starBtn.setAttribute('aria-pressed', this.hackMutationHeader.isStarred);
this.emit('header');
},
postInsert: function () {
this._inDom = true;
if (this._afterInDomMessage) {
this.onCurrentMessage(this._afterInDomMessage);
this._afterInDomMessage = null;
}
},
told: function (args) {
if (args.messageSuid) {
this.messageSuid = args.messageSuid;
}
},
handleBodyChange: function (evt) {
this.buildBodyDom(evt.changeDetails);
},
onBack: function (event) {
cards.removeCardAndSuccessors(this, 'animate');
},
onPrevious: function (event) {
this.headerCursor.advance('previous');
},
onNext: function (event) {
this.headerCursor.advance('next');
},
onMessageSuidNotFound: function (messageSuid) {
if (this.messageSuid === messageSuid) {
this.onBack();
}
},
onCurrentMessage: function (currentMessage) {
if (!this._inDom) {
this._afterInDomMessage = currentMessage;
return;
}
if (this.header && this.header.id === currentMessage.header.id) {
return;
}
this.messageSuid = null;
this._setHeader(currentMessage.header);
this.clearDom();
this.latestOnce('header', function () {
this.buildHeaderDom(this);
this.header.getBody({ downloadBodyReps: true }, function (body) {
if (this.header.id !== body.id) {
return;
}
this.body = body;
body.onchange = this.handleBodyChange;
if (body.bodyRepsDownloaded) {
this.buildBodyDom();
}
}.bind(this));
}.bind(this));
var hasPrevious = currentMessage.siblings.hasPrevious;
this.previousBtn.disabled = !hasPrevious;
this.previousIcon.classList[hasPrevious ? 'remove' : 'add']('icon-disabled');
var hasNext = currentMessage.siblings.hasNext;
this.nextBtn.disabled = !hasNext;
this.nextIcon.classList[hasNext ? 'remove' : 'add']('icon-disabled');
},
reply: function () {
cards.eatEventsUntilNextCard();
var composer = this.header.replyToMessage(null, () => {
cards.pushCard('compose', 'animate', {
model: this.model,
composer: composer
});
});
},
replyAll: function () {
cards.eatEventsUntilNextCard();
var composer = this.header.replyToMessage('all', () => {
cards.pushCard('compose', 'animate', {
model: this.model,
composer: composer
});
});
},
forward: function () {
var needToPrompt = this.header.hasAttachments || this.body.embeddedImageCount > 0;
var forwardMessage = function () {
cards.eatEventsUntilNextCard();
var composer = this.header.forwardMessage('inline', () => {
cards.pushCard('compose', 'animate', {
model: this.model,
composer: composer
});
});
}.bind(this);
if (needToPrompt) {
var dialog = msgAttachmentDisabledConfirmNode.cloneNode(true);
ConfirmDialog.show(dialog, {
id: 'msg-attachment-disabled-ok',
handler: function () {
forwardMessage();
}
}, {
id: 'msg-attachment-disabled-cancel',
handler: null
});
} else {
forwardMessage();
}
},
canReplyAll: function () {
var myAddresses = this.model.account.identities.map(function (ident) {
return ident.address;
});
var otherAddresses = (this.header.to || []).concat(this.header.cc || []);
if (this.header.replyTo && this.header.replyTo.author) {
otherAddresses.push(this.header.replyTo.author);
}
for (var i = 0; i < otherAddresses.length; i++) {
var otherAddress = otherAddresses[i];
if (otherAddress.address && myAddresses.indexOf(otherAddress.address) == -1) {
return true;
}
}
return false;
},
onReplyMenu: function (event) {
var contents = msgReplyMenuNode.cloneNode(true);
cards.setStatusColor(contents);
document.body.appendChild(contents);
var formSubmit = function (evt) {
cards.setStatusColor();
document.body.removeChild(contents);
switch (evt.explicitOriginalTarget.className) {
case 'msg-reply-menu-reply':
this.reply();
break;
case 'msg-reply-menu-reply-all':
this.replyAll();
break;
case 'msg-reply-menu-forward':
this.forward();
break;
case 'msg-reply-menu-cancel':
break;
}
return false;
}.bind(this);
contents.addEventListener('submit', formSubmit);
if (!this.canReplyAll()) {
contents.querySelector('.msg-reply-menu-reply-all').classList.add('collapsed');
}
},
onDelete: function () {
var dialog = msgDeleteConfirmNode.cloneNode(true);
var content = dialog.getElementsByTagName('p')[0];
mozL10n.setAttributes(content, 'message-edit-delete-confirm');
ConfirmDialog.show(dialog, {
id: 'msg-delete-ok',
handler: function () {
var op = this.header.deleteMessage();
cards.removeCardAndSuccessors(this, 'animate');
toaster.toastOperation(op);
}.bind(this)
}, {
id: 'msg-delete-cancel',
handler: null
});
},
onToggleStar: function () {
this.starBtn.classList.toggle('msg-star-btn-on', !this.hackMutationHeader.isStarred);
this.hackMutationHeader.isStarred = !this.hackMutationHeader.isStarred;
this.starBtn.setAttribute('aria-pressed', this.hackMutationHeader.isStarred);
this.header.setStarred(this.hackMutationHeader.isStarred);
},
onMove: function () {
cards.folderSelector(this.model, function (folder) {
var op = this.header.moveMessage(folder);
cards.removeCardAndSuccessors(this, 'animate');
toaster.toastOperation(op);
}.bind(this), function (folder) {
return folder.isValidMoveTarget;
});
},
setRead: function (isRead) {
this.hackMutationHeader.isRead = isRead;
this.header.setRead(isRead);
this.readBtn.classList.toggle('unread', !isRead);
mozL10n.setAttributes(this.readBtn, isRead ? 'message-mark-read-button' : 'message-mark-unread-button');
},
onMarkRead: function () {
this.setRead(!this.hackMutationHeader.isRead);
},
onEnvelopeClick: function (event) {
var target = event.target;
if (!target.classList.contains('msg-peep-bubble')) {
return;
}
this.onPeepClick(target);
},
onPeepClick: function (target) {
var contents = msgContactMenuNode.cloneNode(true);
var peep = target.peep;
var headerNode = contents.getElementsByTagName('header')[0];
Marquee.setup(peep.address, headerNode);
document.body.appendChild(contents);
Marquee.activate('alternate', 'ease');
var formSubmit = function (evt) {
document.body.removeChild(contents);
switch (evt.explicitOriginalTarget.className) {
case 'msg-contact-menu-new':
cards.pushCard('compose', 'animate', {
model: this.model,
composerData: {
message: this.header,
onComposer: function (composer) {
composer.to = [{
address: peep.address,
name: peep.name
}];
}
}
});
break;
case 'msg-contact-menu-view':
sendActivity({
name: 'open',
data: {
type: 'webcontacts/contact',
params: { 'id': peep.contactId }
}
});
break;
case 'msg-contact-menu-create-contact':
var params = { 'email': peep.address };
if (peep.name) {
params.givenName = peep.name;
}
sendActivity({
name: 'new',
data: {
type: 'webcontacts/contact',
params: params
}
});
break;
case 'msg-contact-menu-add-to-existing-contact':
sendActivity({
name: 'update',
data: {
type: 'webcontacts/contact',
params: { 'email': peep.address }
}
});
break;
case 'msg-contact-menu-reply':
var composer = this.header.replyToMessage(null, () => {
cards.pushCard('compose', 'animate', {
model: this.model,
composer: composer
});
});
break;
}
return false;
}.bind(this);
contents.addEventListener('submit', formSubmit);
var contextMenuOptions = this._contextMenuType.NEW_MESSAGE;
var messageType = peep.type;
if (messageType === 'from') {
contextMenuOptions |= this._contextMenuType.REPLY;
}
if (peep.isContact) {
contextMenuOptions |= this._contextMenuType.VIEW_CONTACT;
} else {
contextMenuOptions |= this._contextMenuType.CREATE_CONTACT;
contextMenuOptions |= this._contextMenuType.ADD_TO_CONTACT;
}
if (contextMenuOptions & this._contextMenuType.VIEW_CONTACT) {
contents.querySelector('.msg-contact-menu-view').classList.remove('collapsed');
}
if (contextMenuOptions & this._contextMenuType.CREATE_CONTACT) {
contents.querySelector('.msg-contact-menu-create-contact').classList.remove('collapsed');
}
if (contextMenuOptions & this._contextMenuType.ADD_TO_CONTACT) {
contents.querySelector('.msg-contact-menu-add-to-existing-contact').classList.remove('collapsed');
}
if (contextMenuOptions & this._contextMenuType.REPLY) {
contents.querySelector('.msg-contact-menu-reply').classList.remove('collapsed');
}
if (contextMenuOptions & this._contextMenuType.NEW_MESSAGE) {
contents.querySelector('.msg-contact-menu-new').classList.remove('collapsed');
}
},
onLoadBarClick: function (event) {
var self = this;
var loadBar = this.loadBar;
if (!this.body.embeddedImagesDownloaded) {
this.body.downloadEmbeddedImages(function () {
if (!self.body) {
return;
}
for (var i = 0; i < self.htmlBodyNodes.length; i++) {
self.body.showEmbeddedImages(self.htmlBodyNodes[i], self.iframeResizeHandler);
}
});
loadBar.classList.add('collapsed');
} else {
for (var i = 0; i < this.htmlBodyNodes.length; i++) {
this.body.showExternalImages(this.htmlBodyNodes[i], this.iframeResizeHandler);
}
loadBar.classList.add('collapsed');
}
},
getAttachmentBlob: function (attachment, callback) {
try {
var storageType = attachment._file[0];
var filename = attachment._file[1];
var storage = navigator.getDeviceStorage(storageType);
var getreq = storage.get(filename);
getreq.onerror = function () {
console.warn('Could not open attachment file: ', filename, getreq.error.name);
};
getreq.onsuccess = function () {
var blob = getreq.result;
callback(blob);
};
} catch (ex) {
console.warn('Exception getting attachment from device storage:', attachment._file, '\n', ex, '\n', ex.stack);
}
},
onDownloadAttachmentClick: function (node, attachment) {
node.setAttribute('state', 'downloading');
var registerWithDownloadManager = true;
attachment.download(function downloaded() {
if (!attachment._file) {
return;
}
node.setAttribute('state', 'downloaded');
}, null, registerWithDownloadManager);
},
onViewAttachmentClick: function (node, attachment) {
console.log('trying to open', attachment._file, 'known type:', attachment.mimetype);
if (!attachment._file) {
return;
}
if (attachment.isDownloaded) {
this.getAttachmentBlob(attachment, function (blob) {
try {
if (!blob) {
throw new Error('Blob does not exist');
}
var useType = attachment.mimetype;
if (!useType || useType === OCTET_STREAM_TYPE) {
useType = blob.type;
}
if (!useType || useType === OCTET_STREAM_TYPE) {
useType = MimeMapper.guessTypeFromFileProperties(attachment.filename, OCTET_STREAM_TYPE);
}
if (!useType) {
useType = OCTET_STREAM_TYPE;
}
console.log('triggering open activity with MIME type:', useType);
var activity = new MozActivity({
name: 'open',
data: {
type: useType,
filename: attachment.filename,
blob: blob,
url: attachment.filename
}
});
activity.onerror = function () {
console.warn('Problem with "open" activity', activity.error.name);
if (activity.error.name === 'NO_PROVIDER') {
var dialog = msgAttachmentDidNotOpenAlertNode.cloneNode(true);
ConfirmDialog.show(dialog, {
id: 'msg-attachment-did-not-open-ok',
handler: null
}, { handler: null });
}
};
activity.onsuccess = function () {
console.log('"open" activity allegedly succeeded');
};
} catch (ex) {
console.warn('Problem creating "open" activity:', ex, '\n', ex.stack);
}
});
}
},
onHyperlinkClick: function (event, linkNode, linkUrl, linkText) {
if (/^mailto:/i.test(linkUrl)) {
var data = queryURI(linkUrl);
cards.pushCard('compose', 'animate', {
model: this.model,
composerData: {
onComposer: (composer, composeCard) => {
Object.keys(data).forEach(key => {
if (key === 'to' || key === 'cc' || key === 'bcc') {
composer[key] = data[key].map(addr => {
return this.model.api.parseMailbox(addr).address;
}).filter(value => {
return value;
});
} else {
composer[key] = data[key];
}
});
}
}
});
} else {
var dialog = msgBrowseConfirmNode.cloneNode(true);
var content = dialog.getElementsByTagName('p')[0];
mozL10n.setAttributes(content, 'browse-to-url-prompt', { url: linkUrl });
ConfirmDialog.show(dialog, {
id: 'msg-browse-ok',
handler: function () {
sendActivity({
name: 'view',
data: {
type: 'url',
url: linkUrl
}
});
}.bind(this)
}, {
id: 'msg-browse-cancel',
handler: null
});
}
},
_populatePlaintextBodyNode: function (bodyNode, rep) {
for (var i = 0; i < rep.length; i += 2) {
var node = document.createElement('div'), cname;
var etype = rep[i] & 15;
if (etype === 4) {
var qdepth = (rep[i] >> 8 & 255) + 1;
if (qdepth > 8) {
cname = MAX_QUOTE_CLASS_NAME;
} else {
cname = CONTENT_QUOTE_CLASS_NAMES[qdepth];
}
} else {
cname = CONTENT_TYPES_TO_CLASS_NAMES[etype];
}
if (cname) {
node.setAttribute('class', cname);
}
var subnodes = this.model.api.utils.linkifyPlain(rep[i + 1], document);
for (var iNode = 0; iNode < subnodes.length; iNode++) {
node.appendChild(subnodes[iNode]);
}
bodyNode.appendChild(node);
}
},
buildHeaderDom: function (domNode) {
var header = this.header;
function updatePeep(peep) {
var nameNode = peep.element.querySelector('.msg-peep-content');
if (peep.type === 'from') {
domNode.querySelector('.msg-reader-header-label').textContent = peep.name || peep.address;
nameNode.textContent = peep.address;
nameNode.classList.add('msg-peep-address');
} else {
nameNode.textContent = peep.name || peep.address;
if (!peep.name && peep.address) {
nameNode.classList.add('msg-peep-address');
} else {
nameNode.classList.remove('msg-peep-address');
}
}
}
function addHeaderEmails(type, peeps) {
var lineClass = 'msg-envelope-' + type + '-line';
var lineNode = domNode.getElementsByClassName(lineClass)[0];
if (!peeps || !peeps.length) {
lineNode.classList.add('collapsed');
return;
}
lineNode.classList.remove('collapsed');
var peepTemplate = msgPeepBubbleNode;
for (var i = 0; i < peeps.length; i++) {
var peep = peeps[i];
peep.type = type;
peep.element = peepTemplate.cloneNode(true);
peep.element.peep = peep;
peep.onchange = updatePeep;
updatePeep(peep);
lineNode.appendChild(peep.element);
}
}
addHeaderEmails('from', [header.author]);
addHeaderEmails('to', header.to);
addHeaderEmails('cc', header.cc);
addHeaderEmails('bcc', header.bcc);
var dateNode = domNode.querySelector('.msg-envelope-date');
var dateTime = dateNode.dataset.time = header.date.valueOf();
date.relativeDateElement(dateNode, dateTime);
messageDisplay.subject(domNode.querySelector('.msg-envelope-subject'), header);
},
clearDom: function () {
Array.slice(this.querySelectorAll('.msg-peep-bubble')).forEach(function (node) {
node.parentNode.removeChild(node);
});
var attachmentsContainer = this.querySelector('.msg-attachments-container');
attachmentsContainer.innerHTML = '';
this.rootBodyNode.innerHTML = '<progress data-l10n-id="message-body-container-progress"></progress>';
this.loadBar.classList.add('collapsed');
},
buildBodyDom: function (changeDetails) {
var body = this.body;
if (!body || this.header.id !== body.id) {
return;
}
var domNode = this, rootBodyNode = this.rootBodyNode, reps = body.bodyReps, hasExternalImages = false, showEmbeddedImages = body.embeddedImageCount && body.embeddedImagesDownloaded;
if (!this._builtBodyDom) {
iframeShims.bindSanitizedClickHandler(rootBodyNode, this.onHyperlinkClick.bind(this), rootBodyNode, null);
this._builtBodyDom = true;
}
if (reps.length && reps[0].isDownloaded) {
var progressNode = rootBodyNode.querySelector('progress');
if (progressNode) {
progressNode.parentNode.removeChild(progressNode);
}
}
for (var iRep = 0; iRep < reps.length; iRep++) {
var rep = reps[iRep];
var repNode = rootBodyNode.childNodes[iRep];
if (!repNode) {
repNode = rootBodyNode.appendChild(document.createElement('div'));
}
if (changeDetails && changeDetails.bodyReps && changeDetails.bodyReps.indexOf(iRep) === -1) {
continue;
}
repNode.innerHTML = '';
if (rep.type === 'plain') {
this._populatePlaintextBodyNode(repNode, rep.content);
} else if (rep.type === 'html') {
var iframeShim = iframeShims.createAndInsertIframeForContent(rep.content, this.scrollContainer, repNode, null, 'interactive', this.onHyperlinkClick.bind(this));
var iframe = iframeShim.iframe;
var bodyNode = iframe.contentDocument.body;
this.iframeResizeHandler = iframeShim.resizeHandler;
this.model.api.utils.linkifyHTML(iframe.contentDocument);
this.htmlBodyNodes.push(bodyNode);
if (body.checkForExternalImages(bodyNode)) {
hasExternalImages = true;
}
if (showEmbeddedImages) {
body.showEmbeddedImages(bodyNode, this.iframeResizeHandler);
}
}
}
var loadBar = this.loadBar;
if (body.embeddedImageCount && !body.embeddedImagesDownloaded) {
loadBar.classList.remove('collapsed');
mozL10n.setAttributes(this.loadBarText, 'message-download-images-tap', { n: body.embeddedImageCount });
} else if (hasExternalImages) {
loadBar.classList.remove('collapsed');
mozL10n.setAttributes(this.loadBarText, 'message-show-external-images');
} else {
loadBar.classList.add('collapsed');
}
var attachmentsContainer = domNode.querySelector('.msg-attachments-container');
if (body.attachments && body.attachments.length) {
attachmentsContainer.classList.remove('collapsed');
require(['shared/js/mime_mapper'], function (mapper) {
if (!MimeMapper) {
MimeMapper = mapper;
}
var attTemplate = msgAttachmentItemNode, filenameTemplate = attTemplate.querySelector('.msg-attachment-filename'), filesizeTemplate = attTemplate.querySelector('.msg-attachment-filesize');
for (var iAttach = 0; iAttach < body.attachments.length; iAttach++) {
var attNode = attachmentsContainer.childNodes[iAttach];
if (!attNode) {
attNode = attachmentsContainer.appendChild(document.createElement('li'));
}
if (changeDetails && changeDetails.attachments && changeDetails.attachments.indexOf(iAttach) === -1) {
continue;
}
var attachment = body.attachments[iAttach], state;
var extension = attachment.filename.split('.').pop();
var MAX_ATTACHMENT_SIZE = 25 * 1024 * 1024;
var attachmentDownloadable = true;
var mimeClass = mimeToClass(attachment.mimetype || MimeMapper.guessTypeFromExtension(extension));
if (attachment.isDownloaded) {
state = 'downloaded';
} else if (!attachment.isDownloadable) {
state = 'nodownload';
attachmentDownloadable = false;
} else if (attachment.sizeEstimateInBytes > MAX_ATTACHMENT_SIZE) {
state = 'toolarge';
attachmentDownloadable = false;
} else {
state = 'downloadable';
}
attTemplate.setAttribute('state', state);
filenameTemplate.textContent = attachment.filename;
fileDisplay.fileSize(filesizeTemplate, attachment.sizeEstimateInBytes);
var attachmentNode = attTemplate.cloneNode(true);
attachmentNode.classList.add(mimeClass);
attachmentsContainer.replaceChild(attachmentNode, attNode);
var downloadButton = attachmentNode.querySelector('.msg-attachment-download');
downloadButton.disabled = !attachmentDownloadable;
if (attachmentDownloadable) {
downloadButton.addEventListener('click', this.onDownloadAttachmentClick.bind(this, attachmentNode, attachment));
}
attachmentNode.setAttribute('aria-disabled', !attachmentDownloadable);
attachmentNode.querySelector('.msg-attachment-view').addEventListener('click', this.onViewAttachmentClick.bind(this, attachmentNode, attachment));
this.enableReply();
}
}.bind(this));
} else {
attachmentsContainer.classList.add('collapsed');
this.enableReply();
}
},
disableReply: function () {
var btn = this.querySelector('.msg-reply-btn');
btn.setAttribute('aria-disabled', true);
},
enableReply: function () {
var btn = this.querySelector('.msg-reply-btn');
btn.removeAttribute('aria-disabled');
if (!this._emittedContentEvents) {
evt.emit('metrics:contentDone');
this._emittedContentEvents = true;
}
},
die: function () {
var headerCursor = this.headerCursor;
headerCursor.removeListener('messageSuidNotFound', this.onMessageSuidNotFound);
headerCursor.removeListener('currentMessage', this.onCurrentMessage);
if (this.header) {
this.header.__die();
this.header = null;
}
if (this.body) {
this.body.die();
this.body = null;
}
}
}
];
});

View file

@ -0,0 +1,9 @@
<form role="dialog" class="msg-attach-confirm" data-type="confirm">
<section>
<h1></h1>
<p></p>
</section>
<menu>
<button id="msg-attach-ok" class="full" data-l10n-id="dialog-button-ok"></button>
</menu>
</form>

View file

@ -0,0 +1,9 @@
<form role="dialog" class="msg-attachment-did-not-open" data-type="confirm">
<section>
<h1 data-l10n-id="message-attachment-did-not-open-label"></h1>
<p><span data-l10n-id="message-attachment-did-not-open-body"></span></p>
</section>
<menu>
<button id="msg-attachment-did-not-open-ok" data-l10n-id="dialog-button-ok"></button>
</menu>
</form>

View file

@ -0,0 +1,9 @@
<form role="dialog" class="msg-attachment-disabled-confirm" data-type="confirm">
<section>
<p data-l10n-id="message-send-attachment-disabled-confirm"></p>
</section>
<menu>
<button id="msg-attachment-disabled-cancel" data-l10n-id="message-multiedit-cancel"></button>
<button id="msg-attachment-disabled-ok" data-l10n-id="dialog-button-ok"></button>
</menu>
</form>

View file

@ -0,0 +1,17 @@
<li class="msg-attachment-item">
<span class="msg-attachment-icon" role="presentation"></span>
<span class="msg-attachment-fileinfo">
<span dir="auto" class="msg-attachment-filename"></span>
<span class="msg-attachment-filesize"></span>
<span data-l10n-id="message-attachment-too-large"
class="msg-attachment-too-large"></span>
</span>
<button class="msg-attachment-download" data-l10n-id="download-button">
<span class="icon icon-download"></span></button>
<span class="msg-attachment-downloading">
<progress class="small" data-l10n-id="downloading-progress"></progress>
</span>
<button class="msg-attachment-view" data-l10n-id="view-button">
<span data-l10n-id="message-attachment-view" class="icon icon-view"></span>
</button>
</li>

View file

@ -0,0 +1,10 @@
<form role="dialog" class="msg-browse-confirm" data-type="confirm">
<section>
<h1 data-l10n-id="confirm-dialog-title"></h1>
<p></p>
</section>
<menu>
<button id="msg-browse-cancel" data-l10n-id="message-multiedit-cancel"></button>
<button id="msg-browse-ok" class="recommend" data-l10n-id="dialog-button-ok"></button>
</menu>
</form>

View file

@ -0,0 +1,19 @@
<form class="msg-contact-menu" role="dialog" data-type="action">
   <header></header>
   <menu>
<button class="msg-contact-menu-new collapsed" data-l10n-id="message-contact-menu-new">
</button>
<button class="msg-contact-menu-reply collapsed" data-l10n-id="message-contact-menu-reply">
</button>
<button class="msg-contact-menu-view collapsed" data-l10n-id="message-contact-menu-view">
</button>
<button class="msg-contact-menu-create-contact collapsed" data-l10n-id="message-contact-menu-create">
</button>
<button class="msg-contact-menu-add-to-existing-contact collapsed" data-l10n-id="message-contact-menu-add-existing">
</button>
<span class="last-button-container">
<button class="msg-contact-menu-cancel" data-l10n-id="message-multiedit-cancel">
</button>
</span>
   </menu>
 </form>

View file

@ -0,0 +1,10 @@
<form role="dialog" class="msg-delete-confirm" data-type="confirm">
<section>
<h1 data-l10n-id="confirm-dialog-title"></h1>
<p></p>
</section>
<menu>
<button id="msg-delete-cancel" data-l10n-id="message-multiedit-cancel"></button>
<button id="msg-delete-ok" class="danger" data-l10n-id="message-edit-menu-delete"></button>
</menu>
</form>

View file

@ -0,0 +1,19 @@
<a class="msg-header-item" role="option" tabindex="0">
<label class="pack-checkbox negative" aria-hidden="true">
<input type="checkbox"><span></span>
</label>
<div class="msg-header-details-section">
<span dir="auto" class="msg-header-author"></span>
<span dir="auto" class="msg-header-subject"></span>
<span dir="auto" class="msg-header-date"></span>
<span dir="auto" class="msg-header-snippet"></span>
</div>
<div class="msg-header-syncing-section"></div>
<div class="msg-header-unread-section"
data-l10n-id="message-header-unread"></div>
<div class="msg-header-icons-section">
<span class="msg-header-star" data-l10n-id="message-header-starred"></span>
<span class="msg-header-attachments"
data-l10n-id="message-header-attachments"></span>
</div><div class="msg-header-avatar-section" aria-hidden="true">
</div></a>

View file

@ -0,0 +1,10 @@
<form role="dialog" class="msg-large-message-confirm" data-type="confirm">
<section>
<h1 data-l10n-id="confirm-dialog-title"></h1>
<p><span data-l10n-id="message-large-message-confirm"></span></p>
</section>
<menu>
<button id="msg-large-message-cancel" data-l10n-id="message-large-message-cancel"></button>
<button id="msg-large-message-ok" data-l10n-id="message-large-message-ok"></button>
</menu>
</form>

View file

@ -0,0 +1,3 @@
<div class="msg-peep-bubble peep-bubble" dir="auto" role="option">
<span class="msg-peep-content"></span>
</div>

View file

@ -0,0 +1,15 @@
<form class="msg-reply-menu" role="dialog" data-type="action">
<header></header>
<menu>
<button class="msg-reply-menu-reply" data-l10n-id="message-reply-menu-reply">
</button>
<button class="msg-reply-menu-reply-all" data-l10n-id="message-reply-menu-reply-all">
</button>
<button class="msg-reply-menu-forward" data-l10n-id="message-reply-menu-forward">
</button>
<span class="last-button-container">
<button class="msg-reply-menu-cancel" data-l10n-id="message-reply-menu-cancel">
</button>
</span>
</menu>
</form>

View file

@ -0,0 +1,136 @@
define([
'require',
'query_string',
'services',
'cards'
], function (require) {
var queryString = require('query_string');
var services = require('services');
var cards = require('cards');
var oauthSecrets = services.oauth2;
var curOauthSecrets;
var redeemingCode = false;
var TIMEOUT_MS = 30 * 1000;
var redirectUri = 'http://localhost';
var deferred, p, oauthSettings;
function onBrowserComplete(message) {
var data = message.data;
if (message.type === 'oauth2Complete') {
if (!data.code) {
reset('reject', new Error('no code returned'));
return;
}
console.log('oauth redirect returned with code');
redeemingCode = true;
redeemCode(data.code).then(function (redeemed) {
var expiresInMS = parseInt(redeemed.expires_in, 10) * 1000;
var expireTimeMS = Date.now() + Math.max(0, expiresInMS - TIMEOUT_MS);
var result = {
status: 'success',
tokens: {
accessToken: redeemed.access_token,
refreshToken: redeemed.refresh_token,
expireTimeMS: expireTimeMS
},
secrets: curOauthSecrets
};
reset('resolve', result);
}, function (err) {
reset('reject', err);
});
} else {
reset('resolve', { status: 'cancel' });
}
}
function redeemCode(code) {
console.log('redeeming oauth code');
return new Promise(function (xhrResolve, xhrReject) {
var xhr = new XMLHttpRequest({ mozSystem: true });
xhr.open('POST', oauthSettings.tokenEndpoint, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.timeout = TIMEOUT_MS;
xhr.onload = function () {
if (xhr.status < 200 || xhr.status >= 300) {
console.error('token redemption failed', xhr.status, xhr.responseText);
xhrReject('status' + xhr.status);
} else {
try {
var data = JSON.parse(xhr.responseText);
console.log('oauth code redeemed. access_token? ' + !!data.access_token + ', refresh_token? ' + !!data.refresh_token);
xhrResolve(data);
} catch (ex) {
console.error('badly formed JSON response for token redemption:', xhr.responseText);
xhrReject(ex);
}
}
};
xhr.onerror = function (err) {
console.error('token redemption weird error:', err);
xhrReject(err);
};
xhr.ontimeout = function () {
console.error('token redemption timeout');
xhrReject('timeout');
};
var redemptionArgs = {
code: code,
client_id: curOauthSecrets.clientId,
client_secret: curOauthSecrets.clientSecret,
redirect_uri: redirectUri,
grant_type: 'authorization_code'
};
xhr.send(queryString.fromObject(redemptionArgs));
});
}
function reset(actionName, value) {
console.log('oauth2 fetch reset with action: ' + actionName);
var action = deferred[actionName];
deferred = p = null;
redeemingCode = false;
action(value);
}
return function oauth2Fetch(o2Settings, extraQueryObject) {
if (deferred) {
reset('reject', new Error('Multiple oauth calls, starting new one'));
}
p = new Promise(function (res, rej) {
deferred = {
resolve: res,
reject: rej
};
});
oauthSettings = o2Settings;
curOauthSecrets = undefined;
if (o2Settings.clientId && o2Settings.clientSecret) {
curOauthSecrets = {
clientId: o2Settings.clientId,
clientSecret: o2Settings.clientSecret
};
} else if (o2Settings.secretGroup) {
curOauthSecrets = oauthSecrets[o2Settings.secretGroup];
}
if (!curOauthSecrets) {
reset('reject', new Error('no secrets for group: ' + o2Settings.secretGroup));
return p;
}
var authEndpointQuery = {
client_id: curOauthSecrets.clientId,
redirect_uri: redirectUri,
response_type: 'code',
scope: o2Settings.scope,
max_auth_age: 0
};
var url = o2Settings.authEndpoint + '?' + queryString.fromObject(authEndpointQuery);
if (extraQueryObject) {
var extraArgs = queryString.fromObject(extraQueryObject);
if (extraArgs) {
url += '&' + extraArgs;
}
}
cards.pushCard('setup_oauth2', 'animate', {
url: url,
onBrowserComplete: onBrowserComplete
});
return p;
};
});

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,145 @@
<!-- Per-account settings menu; largely read-only for now -->
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic bbshim settings-account-common" role="region">
<header class="tng-account-header">
<a data-event="click:onBack"
href="#" class="tng-back-btn back-btn header-start-btn"
role="button" data-l10n-id="back-button">
<span class="icon icon-back"></span>
</a>
<h1 data-prop="headerLabel"
class="tng-account-header-label header-label"></h1>
</header>
<section class="scrollregion-below-header skin-organic" role="region">
<header class="collapsed"></header>
<header class="tng-main-accounts-label">
<h2 data-l10n-id="settings-sub-header-account">
AccounT
</h2>
</header>
<ul class="tng-list" data-l10n-id="settings-account-group">
<li class="list-text-with-sub">
<span data-l10n-id="settings-account-name" class="list-text">
YouR NamE</span>
<span data-prop="accountNameNode" dir="auto"
class="tng-account-name list-sub-text"></span>
</li>
<li>
<label class="tng-notify-mail-label pack-switch list-pack-switch">
<input class="tng-notify-mail" type="checkbox" />
<span data-l10n-id="settings-account-display-notifications"
class="list-text list-pack-switch-text">
DisplaY NotificationS FoR NeW MessageS</span>
</label>
</li>
<li>
<label class="tng-sound-onsend-label pack-switch list-pack-switch">
<input class="tng-sound-onsend" type="checkbox" />
<span data-l10n-id="settings-account-play-sound-onsend"
class="list-text list-pack-switch-text">
PlaY SounD AfteR MessagE SenT</span>
</label>
</li>
<li>
<label data-prop="defaultLabelNode"
class="pack-checkbox tng-default-label list-pack-checkbox">
<input data-prop="defaultInputNode"
class="tng-default-input" type="checkbox">
<span data-l10n-id="settings-default-account"
class="list-text list-pack-checkbox-text">
SeT AS DefaulT EmaiL</span>
</label>
</li>
<li class="settings-signature settings-input-list">
<label class="pack-switch tng-default-label list-pack-switch">
<input class="tng-signature-input" type="checkbox">
<span data-l10n-id="settings-account-signature-label"
class="list-text list-pack-switch-text">
SignaturE</span>
</label>
<button class="signature-button button icon">
<div dir="auto" class="signature-button-content"></div>
</button>
</li>
</ul>
<header class="tng-main-accounts-label">
<h2 data-l10n-id="settings-sub-header-data">
DatA
</h2>
</header>
<ul class="tng-list" data-l10n-id="settings-data-group">
<li class="syncinterval-setting select-item">
<label data-l10n-id="settings-account-check-mail" class="list-text">
ChecK FoR NeW MessageS</label>
<span class="button icon icon-dialog">
<select class="tng-account-check-interval mail-select"
data-l10n-id="settings-account-check-mail-select">
<option value="0" data-l10n-id="settings-check-every-manual">
ManuallY</option>
<option value="300000" data-l10n-id="settings-check-every-5min">
5 MinuteS</option>
<option value="600000" data-l10n-id="settings-check-every-10min">
10 MinuteS</option>
<option value="900000" data-l10n-id="settings-check-every-15min">
15 MinuteS</option>
<option value="1800000" data-l10n-id="settings-check-every-30min">
30 MinuteS</option>
<option value="3600000" data-l10n-id="settings-check-every-60min">
60 MinuteS</option>
</select>
</span>
</li>
<li data-prop="syncSettingNode" class="synchronize-setting select-item">
<label data-l10n-id="settings-account-synchronize-window"
class="list-text"></label>
<span class="button icon icon-dialog">
<select data-prop="synchronizeNode"
data-event="change:onChangeSynchronize"
class="mail-select tng-account-synchronize"
data-l10n-id="settings-account-synchronize-window-select">
<option value="auto" data-l10n-id="settings-synchronize-auto">
AutomatiC</option>
<option value="1d" data-l10n-id="settings-synchronize-one-day">
1 DaY</option>
<option value="3d" data-l10n-id="settings-synchronize-three-days">
3 DayS</option>
<option value="1w" data-l10n-id="settings-synchronize-one-week">
1 WeeK</option>
<option value="2w" data-l10n-id="settings-synchronize-two-weeks">
2 WeekS</option>
<option value="1m" data-l10n-id="settings-synchronize-one-month">
1 MontH</option>
<option value="all" data-l10n-id="settings-synchronize-all">
AlL MessageS</option>
</select>
</span>
</li>
</ul>
<header class="tng-main-accounts-label">
<h2 data-l10n-id="settings-sub-header-server">
ServeR SettingS
</h2>
</header>
<ul data-prop="serversContainer" data-l10n-id="settings-server-group"
class="tng-list tng-account-server-container">
<li class="item-with-children">
<a href="#" data-prop="accountCredNode"
data-event="click:onClickCredentials"
class="tng-account-credentials list-text"></a>
</li>
<li class="list-text-pair">
<span data-l10n-id="settings-account-type" role="presentation"
class="list-text aside start">
AccounT TypE</span>
<div role="presentation">
<span data-prop="accountTypeNode" dir="auto" role="presentation"
class="tng-account-type"></span>
</div>
</li>
</ul>
<button href="#" data-l10n-id="settings-account-delete"
data-event="click:onDelete"
class="tng-account-delete danger"></button>
</section>
</section>

View file

@ -0,0 +1,98 @@
define([
'require',
'tmpl!./tng/account_settings_server.html',
'tmpl!./tng/account_delete_confirm.html',
'evt',
'l10n!',
'cards',
'confirm_dialog',
'./base_card',
'template!./settings_account.html',
'./account_prefs_mixins'
], function (require) {
var tngAccountSettingsServerNode = require('tmpl!./tng/account_settings_server.html'), tngAccountDeleteConfirmNode = require('tmpl!./tng/account_delete_confirm.html'), evt = require('evt'), mozL10n = require('l10n!'), cards = require('cards'), ConfirmDialog = require('confirm_dialog');
return [
require('./base_card')(require('template!./settings_account.html')),
require('./account_prefs_mixins'),
{
onArgs: function (args) {
this.account = args.account;
this.identity = this.account.identities[0];
this.headerLabel.textContent = args.account.name;
this._bindPrefs('tng-account-check-interval', 'tng-notify-mail', 'tng-sound-onsend', 'tng-signature-input', 'signature-button');
this.accountNameNode.textContent = this.identity && this.identity.name || this.account.name;
this.accountTypeNode.textContent = this.account.type === 'activesync' ? 'ActiveSync' : this.account.type === 'imap+smtp' ? 'IMAP+SMTP' : 'POP3+SMTP';
if (this.account.isDefault) {
this.defaultInputNode.disabled = true;
this.defaultInputNode.checked = true;
} else {
this.defaultLabelNode.addEventListener('click', this.onChangeDefaultAccount.bind(this), false);
}
if (this.account.type === 'activesync') {
this.synchronizeNode.value = this.account.syncRange;
} else {
this.syncSettingNode.parentNode.removeChild(this.syncSettingNode);
}
this.account.servers.forEach(function (server, index) {
var serverNode = tngAccountSettingsServerNode.cloneNode(true);
var serverLabel = serverNode.querySelector('.tng-account-server-label');
mozL10n.setAttributes(serverLabel, 'settings-' + server.type + '-label');
serverLabel.addEventListener('click', this.onClickServers.bind(this, index), false);
this.serversContainer.appendChild(serverNode);
}.bind(this));
var credL10nId = 'settings-account-userpass';
if (this.account.authMechanism === 'oauth2') {
credL10nId = 'settings-account-useroauth2';
}
mozL10n.setAttributes(this.accountCredNode, credL10nId);
},
onBack: function () {
cards.removeCardAndSuccessors(this, 'animate', 1);
},
onCardVisible: function () {
this.updateSignatureButton();
},
onClickCredentials: function () {
cards.pushCard('settings_account_credentials', 'animate', { account: this.account }, 'right');
},
onClickServers: function (index) {
cards.pushCard('settings_account_servers', 'animate', {
account: this.account,
index: index
}, 'right');
},
onChangeDefaultAccount: function (event) {
event.stopPropagation();
if (event.preventBubble) {
event.preventBubble();
}
if (!this.defaultInputNode.disabled) {
this.defaultInputNode.disabled = true;
this.defaultInputNode.checked = true;
this.account.modifyAccount({ setAsDefault: true });
}
},
onChangeSynchronize: function (event) {
this.account.modifyAccount({ syncRange: event.target.value });
},
onDelete: function () {
var account = this.account;
var dialog = tngAccountDeleteConfirmNode.cloneNode(true);
var content = dialog.getElementsByTagName('p')[0];
mozL10n.setAttributes(content, 'settings-account-delete-prompt', { account: account.name.replace(/@/, '\u200B@\u200B') });
ConfirmDialog.show(dialog, {
id: 'account-delete-ok',
handler: function () {
account.deleteAccount();
evt.emit('accountDeleted', account);
}
}, {
id: 'account-delete-cancel',
handler: null
});
},
die: function () {
}
}
];
});

View file

@ -0,0 +1,55 @@
<!-- Per-account credentials settings menu -->
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic bbshim" role="region">
<header class="tng-account-server-header">
<a data-event="click:onBack"
href="#" class="tng-back-btn back-btn header-start-btn"
role="button" data-l10n-id="back-button">
<span class="icon icon-back"></span>
</a>
<h1 data-prop="headerLabel"
class="tng-account-header-label header-label"></h1>
</header>
<section class="scrollregion-below-header skin-organic" role="region">
<header class="collapsed"></header>
<header>
<h2 data-l10n-id="settings-account-credentials"
class="tng-account-credentials-label"></h2>
</header>
<div class="tng-form">
<li class="tng-account-server-username settings-input-list">
<h2 data-l10n-id="settings-username"
class="tng-server-username-title" aria-level="3">
UsernamE</h2>
<input disabled data-prop="usernameNodeInput"
dir="auto"
class="tng-server-username-input" type="text" />
</li>
<li class="tng-account-server-password settings-input-list">
<h2 data-l10n-id="settings-password"
class="tng-server-password-title" aria-level="3">
PassworD</h2>
<!-- When the input is focused, a resize event will be fired
and will trigger _showCard() in mail-common.js
the UI state will become wrong after _showCard() is called
so disable the input first to prevent the wrong state
-->
<input data-prop="passwordNodeInput"
class="tng-server-password-input" type="password"
data-l10n-id="setup-new-password" />
</li>
</div>
<button data-l10n-id="settings-save"
data-prop="saveButton"
data-event="click:onClickSave"
class="tng-account-save settings-button recommend"></button>
<button data-l10n-id="settings-reauth"
data-prop="reauthButton"
data-event="click:onClickReauth"
class="tng-account-reauth settings-button recommend collapsed">
</button>
</section>
</section>

View file

@ -0,0 +1,52 @@
define([
'require',
'l10n!',
'cards',
'./oauth2/fetch',
'./base_card',
'template!./settings_account_credentials.html'
], function (require) {
var mozL10n = require('l10n!'), cards = require('cards'), oauthFetch = require('./oauth2/fetch');
return [
require('./base_card')(require('template!./settings_account_credentials.html')),
{
onArgs: function (args) {
this.account = args.account;
this.headerLabel.textContent = this.account.name;
if (this.account.authMechanism !== 'password') {
this.querySelector('.tng-account-server-password').classList.add('collapsed');
this.saveButton.classList.add('collapsed');
}
if (this.account.authMechanism === 'oauth2') {
this.reauthButton.classList.remove('collapsed');
}
this.usernameNodeInput.value = this.account.username;
},
onBack: function () {
cards.removeCardAndSuccessors(this, 'animate', 1);
},
onClickSave: function () {
var password = this.passwordNodeInput.value;
if (password) {
this.account.modifyAccount({ password: password });
this.account.clearProblems();
} else {
alert(mozL10n.get('settings-password-empty'));
}
this.onBack();
},
onClickReauth: function () {
var oauth2 = this.account._wireRep.credentials.oauth2;
oauthFetch(oauth2, { login_hint: this.account.username }).then(function (response) {
if (response.status === 'success') {
this.account.modifyAccount({ oauthTokens: response.tokens });
this.account.clearProblems();
this.onBack();
}
}.bind(this));
},
die: function () {
}
}
];
});

View file

@ -0,0 +1,43 @@
<!-- Per-account server settings menu -->
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic bbshim" role="region">
<header class="tng-account-server-header">
<a data-event="click:onBack"
href="#" class="tng-back-btn back-btn header-start-btn"
role="button" data-l10n-id="back-button">
<span class="icon icon-back"></span>
</a>
<h1 data-prop="headerLabel"
class="tng-account-header-label header-label">
AccounT NamE</h1>
</header>
<section class="scrollregion-below-header skin-organic" role="region">
<header class="collapsed"></header>
<header>
<h2 data-prop="serverLabel"
class="tng-account-server-label"></h2>
</header>
<div class="tng-form">
<li class="tng-account-server-hostname settings-input-list">
<h2 data-l10n-id="settings-hostname"
class="tng-server-hostname-title" aria-level="3">
HostnamE</h2>
<input data-prop="hostnameNodeInput"
disabled class="tng-server-hostname-input"
type="text" />
</li>
<li class="tng-account-server-port settings-input-list">
<h2 data-l10n-id="settings-port"
class="tng-server-port-title" aria-level="3"></h2>
<input data-prop="portNodeInput" disabled class="tng-server-port-input"
type="text" />
</li>
</div>
<!-- nothing is editable, nothing is saved; collapse. -->
<button data-l10n-id="settings-save"
data-event="click:onBack"
class="tng-account-save settings-button recommend collapsed"
></button>
</section>
</section>

View file

@ -0,0 +1,27 @@
define([
'require',
'l10n!',
'cards',
'./base_card',
'template!./settings_account_servers.html'
], function (require) {
var mozL10n = require('l10n!'), cards = require('cards');
return [
require('./base_card')(require('template!./settings_account_servers.html')),
{
onArgs: function (args) {
this.account = args.account;
this.server = args.account.servers[args.index];
this.headerLabel.textContent = this.account.name;
mozL10n.setAttributes(this.serverLabel, 'settings-' + this.server.type + '-label');
this.hostnameNodeInput.value = this.server.connInfo.hostname || this.server.connInfo.server;
this.portNodeInput.value = this.server.connInfo.port || '';
},
onBack: function () {
cards.removeCardAndSuccessors(this, 'animate', 1);
},
die: function () {
}
}
];
});

View file

@ -0,0 +1,70 @@
<!-- Secret developer debug page; for those in the know. This page can only be
reached by tapping 5 times on some whitespace on the main settings page.
NOTE NOTE NOTE NOTE NOTE
This page is intentionally unlocalized. There's a few reasons for this:
- The intended users are Firefox OS developers for which English is
currently a pre-requisite.
- It allows us to change these settings without being particularly
concerned about string freeze.
- We don't have to worry about the localized strings not capturing the
risk of enabling this logging.
This raises the question: Should this be shipped in non-developer builds if
we think it's not worth localizing? Our current rationale is that it is
useful. We (the developers) have had bug reporters enable the logging and
worked with us to fix bugs using the functionality. Especially given our
limited ability to provide updated versions of the app or have users
side-load versions of our certified app.
-->
<!-- This section element is needed by building blocks, would be nice if
bb used a class instead of a section element target -->
<section class="skin-organic settings-account-common bbshim" role="region">
<header class="tng-account-header">
<a data-event="click:onClose"
href="#" class="tng-back-btn back-btn header-start-btn">
<span class="icon icon-back"></span>
</a>
<h1 class="header-label">Debug Magic</h1>
</header>
<section class="scrollregion-below-header skin-organic" role="region">
<button data-event="click:resetApp"
href="#" class="tng-dbg-reset">Reset App</button>
<header><h2>Logging</h2></header>
<ul class="tng-list">
<li class="select-item">
<label class="list-text">Logging Mode</label>
<span class="button icon icon-dialog">
<select data-prop="loggingSelect"
data-event="change:onChangeLogging"
class="tng-dbg-logging">
<option value="">
No logging.
</option>
<option value="safe">
Circular: Safe data: no passwords or message contents logged.
</option>
<option value="dangerous">
Circular: Dangerous data: passwords and message contents logged.
</option>
<option value="realtime-dangerous">
Realtime + Circular: Dangerous data: passwords and message contents logged.
</option>
</select>
</span>
</li>
</ul>
<button data-event="click:dumpLog"
href="#"
class="tng-dbg-dump-storage"
>Dump circular log buffer to sdcard</button>
<header><h2>More Settings!</h2></header>
<button data-event="click:fastSync"
href="#"
class="tng-dbg-fastsync">Enable faster sync options</button>
<button data-event="click:resetStartupCache"
href="#">Reset startup cache</button>
</section>
</section>

View file

@ -0,0 +1,47 @@
var _secretDebug;
define([
'require',
'api',
'cards',
'html_cache',
'./base_card',
'template!./settings_debug.html'
], function (require) {
var MailAPI = require('api'), cards = require('cards'), htmlCache = require('html_cache');
if (!_secretDebug) {
_secretDebug = {};
}
return [
require('./base_card')(require('template!./settings_debug.html')),
{
createdCallback: function () {
this.loggingSelect.value = MailAPI.config.debugLogging || '';
},
onClose: function () {
cards.removeCardAndSuccessors(this, 'animate', 1);
},
resetApp: function () {
window.location.reload();
},
dumpLog: function (target) {
MailAPI.debugSupport('dumpLog', target);
},
onChangeLogging: function () {
var value = this.loggingSelect.value || false;
MailAPI.debugSupport('setLogging', value);
},
fastSync: function () {
_secretDebug.fastSync = [
20000,
60000
];
},
resetStartupCache: function () {
htmlCache.reset();
console.log('htmlCache.reset done');
},
die: function () {
}
}
];
});

View file

@ -0,0 +1,26 @@
<!-- Main settings menu, root of all (mail) settings -->
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic bbshim" role="region">
<header class="tng-main-header">
<menu data-event="click:onClose" type="toolbar" class="tng-close-btn">
<button data-l10n-id="settings-done"></button>
</menu>
<h1 class="tng-main-header-label"
data-l10n-id="settings-main-header"></h1>
</header>
<section class="scrollregion-below-header skin-organic" role="region">
<header class="collapsed"></header>
<header class="tng-main-accounts-label">
<h2 data-l10n-id="settings-account-section"></h2>
</header>
<ul data-prop="accountsContainer" class="tng-accounts-container"
data-l10n-id="settings-account-listbox" role="listbox">
</ul>
<button data-event="click:onClickAddAccount"
href="#" data-l10n-id="settings-account-add"
class="tng-account-add"></button>
<a data-event="click:onClickSecretButton"
class="tng-email-lib-version list-text">v1</a>
</section>
</section>

View file

@ -0,0 +1,77 @@
define([
'require',
'tmpl!./tng/account_item.html',
'api',
'cards',
'./base_card',
'template!./settings_main.html'
], function (require) {
var tngAccountItemNode = require('tmpl!./tng/account_item.html'), MailAPI = require('api'), cards = require('cards');
return [
require('./base_card')(require('template!./settings_main.html')),
{
createdCallback: function () {
this.acctsSlice = MailAPI.viewAccounts(false);
this.acctsSlice.onsplice = this.onAccountsSplice.bind(this);
this._secretButtonClickCount = 0;
this._secretButtonTimer = null;
},
extraClasses: [
'anim-fade',
'anim-overlay'
],
onClose: function () {
cards.removeCardAndSuccessors(this, 'animate', 1, 1);
},
onAccountsSplice: function (index, howMany, addedItems, requested, moreExpected) {
var accountsContainer = this.accountsContainer;
var account;
if (howMany) {
for (var i = index + howMany - 1; i >= index; i--) {
account = this.acctsSlice.items[i];
accountsContainer.removeChild(account.element);
}
}
var insertBuddy = index >= accountsContainer.childElementCount ? null : accountsContainer.children[index], self = this;
addedItems.forEach(function (account) {
var accountNode = account.element = tngAccountItemNode.cloneNode(true);
accountNode.account = account;
self.updateAccountDom(account, true);
accountsContainer.insertBefore(accountNode, insertBuddy);
});
},
updateAccountDom: function (account, firstTime) {
var accountNode = account.element;
if (firstTime) {
var accountLabel = accountNode.querySelector('.tng-account-item-label');
accountLabel.textContent = account.name;
accountNode.setAttribute('aria-label', account.name);
accountNode.addEventListener('click', this.onClickEnterAccount.bind(this, account), false);
}
},
onClickAddAccount: function () {
cards.pushCard('setup_account_info', 'animate', { allowBack: true }, 'right');
},
onClickEnterAccount: function (account) {
cards.pushCard('settings_account', 'animate', { account: account }, 'right');
},
onClickSecretButton: function () {
if (this._secretButtonTimer === null) {
this._secretButtonTimer = window.setTimeout(function () {
this._secretButtonTimer = null;
this._secretButtonClickCount = 0;
}.bind(this), 2000);
}
if (++this._secretButtonClickCount >= 5) {
window.clearTimeout(this._secretButtonTimer);
this._secretButtonTimer = null;
this._secretButtonClickCount = 0;
cards.pushCard('settings_debug', 'animate', {}, 'right');
}
},
die: function () {
this.acctsSlice.die();
}
}
];
});

View file

@ -0,0 +1,25 @@
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic bbshim" role="region">
<header class="tng-signature-header">
<a data-event="click:onBack"
href="#"
class="signature-back-btn tng-back-btn back-btn header-start-btn"
role="button" data-l10n-id="back-button">
<span class="icon icon-back"></span>
</a>
<menu data-event="click:onClickDone"
class="tng-close-btn tng-signature-done" type="toolbar">
<button data-l10n-id="settings-done"></button>
</menu>
<h1 class="tng-signature-header-label header-label" data-l10n-id="settings-account-signature-label"></h1>
</header>
<section class="scrollregion-below-header skin-organic" role="region">
<div data-prop="signatureNode"
dir="auto"
role="textbox"
aria-multiline="true"
data-l10n-id="settings-account-signature-textbox"
class="tng-signature-text" contenteditable="true"></div>
</section>
</section>

View file

@ -0,0 +1,81 @@
define([
'require',
'tmpl!./sig/save_signature.html',
'cards',
'./base_card',
'template!./settings_signature.html',
'./editor_mixins'
], function (require) {
var backFormNode = require('tmpl!./sig/save_signature.html'), cards = require('cards'), trailingRegExp = /\s+$/;
return [
require('./base_card')(require('template!./settings_signature.html')),
require('./editor_mixins'),
{
onArgs: function (args) {
this.account = args.account;
this.identity = this.account.identities[0];
this._bindEditor(this.signatureNode);
this.populateEditor(this.identity.signature || '');
},
getTextFromEditor: function () {
var text = this.fromEditor().replace(trailingRegExp, '');
return text;
},
goBack: function () {
cards.removeCardAndSuccessors(this, 'animate', 1);
},
onBack: function () {
var signature = this.getTextFromEditor();
if (signature === this.identity.signature) {
this.goBack();
return;
}
var menu = backFormNode.cloneNode(true);
this._savePromptMenu = menu;
cards.setStatusColor(menu);
document.body.appendChild(menu);
var formSubmit = function (evt) {
cards.setStatusColor();
document.body.removeChild(menu);
this._savePromptMenu = null;
switch (evt.explicitOriginalTarget.id) {
case 'sig-save':
this.identity.modifyIdentity({ signature: signature });
this.goBack();
break;
case 'sig-discard':
this.goBack();
break;
case 'sig-cancel':
break;
}
return false;
}.bind(this);
menu.addEventListener('submit', formSubmit);
},
onClickDone: function () {
var signature = this.getTextFromEditor();
if (signature !== this.identity.signature) {
this.identity.modifyIdentity({ signature: signature });
}
this.onBack();
},
onCardVisible: function () {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
selection.removeAllRanges();
}
var node = this.signatureNode.lastChild;
if (node) {
var range = document.createRange();
range.setStartAfter(node);
range.setEndAfter(node);
selection.addRange(range);
}
this.signatureNode.focus();
},
die: function () {
}
}
];
});

View file

@ -0,0 +1,15 @@
define([
'require',
'./setup_l10n_map',
'l10n!'
], function (require) {
var SETUP_ERROR_L10N_ID_MAP = require('./setup_l10n_map'), mozL10n = require('l10n!');
return {
showError: function (errName, errDetails) {
this.errorRegionNode.classList.remove('collapsed');
var errorStr = SETUP_ERROR_L10N_ID_MAP.hasOwnProperty(errName) ? SETUP_ERROR_L10N_ID_MAP[errName] : SETUP_ERROR_L10N_ID_MAP.unknown;
mozL10n.setAttributes(this.errorMessageNode, errorStr, errDetails);
this.scrollBelowNode.scrollTop = 0;
}
};
});

View file

@ -0,0 +1,55 @@
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic form-card bbshim" role="region">
<header class="sup-account-header">
<a data-prop="backButton"
data-event="click:onBack"
href="#" class="sup-back-btn collapsed"
role="button" data-l10n-id="back-button">
<span class="icon icon-back"></span>
</a>
<h1 class="sup-account-header-label"
data-l10n-id="setup-account-header3"></h1>
</header>
<div data-prop="scrollBelowNode" class="scrollregion-below-header">
<div data-prop="errorRegionNode" class="sup-error-region collapsed">
<div data-prop="errorMessageNode" class="sup-error-message"></div>
</div>
<form data-prop="formNode" class="sup-form sup-account-form">
<p>
<!-- x-inputmode is a temporary v1 B2G stop-gap -->
<input data-prop="nameNode"
data-event="input:onInfoInput"
class="sup-info-name" type="text"
data-l10n-id="setup-info-name"
x-inputmode="verbatim"
inputmode="verbatim"
dir="auto"
required />
<button type="reset"></button>
</p>
<p>
<input data-prop="emailNode"
data-event="input:onInfoInput"
class="sup-info-email" type="email"
data-l10n-id="setup-info-email"
dir="auto"
required />
<button type="reset"></button>
</p>
<p>
<button data-prop="nextButton"
data-event="click:onNext"
class="sup-info-next-btn recommend" disabled>
<span data-l10n-id="setup-info-next"></span>
</button>
<button data-prop="manualConfig"
data-event="click:onClickManualConfig"
class="sup-manual-config-btn" disabled
data-l10n-id="setup-manual-config2">
ManuaL SetuP
</button>
</p>
</form>
</div>
</section>

View file

@ -0,0 +1,69 @@
define([
'require',
'exports',
'module',
'evt',
'l10n!',
'cards',
'html_cache',
'form_navigation',
'./base_card',
'template!./setup_account_info.html',
'./setup_account_error_mixin'
], function (require, exports, module) {
var evt = require('evt'), mozL10n = require('l10n!'), cards = require('cards'), htmlCache = require('html_cache'), FormNavigation = require('form_navigation');
return [
require('./base_card')(require('template!./setup_account_info.html')),
require('./setup_account_error_mixin'),
{
createdCallback: function () {
htmlCache.cloneAndSave(module.id, this);
this.formNavigation = new FormNavigation({
formElem: this.formNode,
onLast: this.onNext.bind(this)
});
this.needsFocus = true;
},
onArgs: function (args) {
if (args.allowBack) {
this.backButton.classList.remove('collapsed');
}
if (args.launchedFromActivity) {
this.errorRegionNode.classList.remove('collapsed');
mozL10n.setAttributes(this.errorMessageNode, 'setup-empty-account-message');
}
},
onCardVisible: function () {
if (this.needsFocus) {
this.nameNode.focus();
this.needsFocus = false;
}
},
onBack: function (event) {
evt.emit('setupAccountCanceled', this);
},
onNext: function (event) {
event.preventDefault();
htmlCache.reset();
cards.pushCard('setup_progress', 'animate', {
displayName: this.nameNode.value,
emailAddress: this.emailNode.value,
callingCard: this
}, 'right');
},
onInfoInput: function (event) {
this.nextButton.disabled = this.manualConfig.disabled = !this.formNode.checkValidity();
},
onClickManualConfig: function (event) {
event.preventDefault();
cards.pushCard('setup_manual_config', 'animate', {
displayName: this.nameNode.value,
emailAddress: this.emailNode.value
}, 'right');
},
die: function () {
this.formNavigation = null;
}
}
];
});

View file

@ -0,0 +1,39 @@
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="form-card card skin-organic bbshim" role="region">
<header class="sup-account-header">
<a href="#"
data-event="click:onBack" class="sup-back-btn">
<span class="icon icon-back"></span>
</a>
<h1 class="sup-account-header-label"
data-l10n-id="setup-account-header3"></h1>
</header>
<div data-prop="scrollBelowNode" class="scrollregion-below-header">
<div data-prop="errorRegionNode" class="sup-error-region collapsed">
<div data-prop="errorMessageNode" class="sup-error-message"></div>
</div>
<form data-prop="formNode" class="sup-form sup-account-form">
<p class="list-text-with-sub" role="heading" aria-level="2">
<span data-l10n-id="setup-account-enter-password" class="list-text">
EnteR PassworD FoR:</span>
<span data-prop="emailNode"
class="sup-email-display list-sub-text"></span>
</p>
<p>
<input data-prop="passwordNode"
data-event="input:onInfoInput"
class="sup-info-password" type="password"
data-l10n-id="setup-info-password"
dir="auto"
required />
<button type="reset"></button>
</p>
<button data-prop="nextButton"
data-event="click:onNext"
class="sup-info-next-btn recommend" disabled>
<span data-l10n-id="setup-info-next"></span>
</button>
</form>
</div>
</section>

View file

@ -0,0 +1,49 @@
define([
'require',
'mix',
'cards',
'form_navigation',
'./base_card',
'template!./setup_account_password.html',
'./setup_account_error_mixin'
], function (require) {
var mix = require('mix'), cards = require('cards'), FormNavigation = require('form_navigation');
function bindFormNavigation(instance) {
return new FormNavigation({
formElem: instance.formNode,
onLast: instance.onNext.bind(instance)
});
}
return [
require('./base_card')(require('template!./setup_account_password.html')),
require('./setup_account_error_mixin'),
{
onArgs: function (args) {
this.args = args;
this.emailAddress = args.emailAddress;
this.emailNode.textContent = this.emailAddress;
this.needsFocus = true;
bindFormNavigation(this);
},
onCardVisible: function () {
if (this.needsFocus) {
this.passwordNode.focus();
this.needsFocus = false;
}
},
onBack: function (event) {
cards.removeCardAndSuccessors(this, 'animate', 1);
},
onNext: function (event) {
event.preventDefault();
this.args.password = this.passwordNode.value;
cards.pushCard('setup_progress', 'animate', mix({ callingCard: this }, this.args), 'right');
},
onInfoInput: function (event) {
this.nextButton.disabled = !this.formNode.checkValidity();
},
die: function () {
}
}
];
});

View file

@ -0,0 +1,65 @@
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic bbshim settings-account-common" role="region">
<header class="sup-account-header">
<menu type="toolbar">
<button data-event="click:onNext" class="sup-info-next-btn">
<span data-l10n-id="setup-info-next"></span>
</button>
</menu>
<h1 class="sup-account-header-label"
data-l10n-id="setup-account-header3"></h1>
</header>
<section class="scrollregion-below-header skin-organic" role="region">
<ul class="tng-list">
<li class="syncinterval-setting select-item">
<label data-l10n-id="settings-account-check-mail" class="list-text">
ChecK FoR NeW MessageS</label>
<span class="button icon icon-dialog">
<select class="tng-account-check-interval mail-select"
data-l10n-id="settings-account-check-mail-select">
<option value="0" data-l10n-id="settings-check-every-manual">
ManuallY</option>
<option value="300000" data-l10n-id="settings-check-every-5min">
5 MinuteS</option>
<option value="600000" data-l10n-id="settings-check-every-10min">
10 MinuteS</option>
<option value="900000" data-l10n-id="settings-check-every-15min">
15 MinuteS</option>
<option value="1800000" data-l10n-id="settings-check-every-30min">
30 MinuteS</option>
<option value="3600000" data-l10n-id="settings-check-every-60min">
60 MinuteS</option>
</select>
</span>
</li>
<li>
<label class="tng-notify-mail-label pack-switch list-pack-switch">
<input class="tng-notify-mail" type="checkbox" />
<span data-l10n-id="settings-account-display-notifications"
class="list-text list-pack-switch-text">
DisplaY NotificationS FoR NeW MessageS</span>
</label>
</li>
<li>
<label class="tng-sound-onsend-label pack-switch list-pack-switch">
<input class="tng-sound-onsend" type="checkbox" />
<span data-l10n-id="settings-account-play-sound-onsend"
class="list-text list-pack-switch-text">
PlaY SounD AfteR MessagE SenT</span>
</label>
</li>
<li class="settings-signature settings-input-list">
<label class="pack-switch tng-default-label list-pack-switch">
<input class="tng-signature-input" type="checkbox">
<span data-l10n-id="settings-account-signature-label"
class="list-text list-pack-switch-text">
SignaturE</span>
</label>
<button class="signature-button button icon">
<div dir="auto" class="signature-button-content"></div>
</button>
</li>
</ul>
</section>
</section>

View file

@ -0,0 +1,33 @@
define([
'require',
'cards',
'l10n!',
'./base_card',
'template!./setup_account_prefs.html',
'./account_prefs_mixins'
], function (require) {
var cards = require('cards'), mozL10n = require('l10n!');
return [
require('./base_card')(require('template!./setup_account_prefs.html')),
require('./account_prefs_mixins'),
{
onArgs: function (args) {
this.account = args.account;
this.identity = this.account.identities[0];
this.identity.modifyIdentity({
signatureEnabled: true,
signature: mozL10n.get('settings-default-signature-2')
});
this._bindPrefs('tng-account-check-interval', 'tng-notify-mail', 'tng-sound-onsend', 'tng-signature-input', 'signature-button');
},
onNext: function (event) {
cards.pushCard('setup_done', 'animate');
},
onCardVisible: function () {
this.updateSignatureButton();
},
die: function () {
}
}
];
});

View file

@ -0,0 +1,24 @@
<!-- Account all setup, add another or read your mail? -->
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic bbshim" role="region">
<header class="sup-account-header">
<h1 class="sup-account-header-label"
data-l10n-id="setup-completed-header"></h1>
</header>
<div class="sup-progress-region">
<span class="sup-progress-label" data-l10n-id="setup-completed-label">
AlL DonE!
</span>
</div>
<button data-event="click:onAddAnother"
class="sup-add-another-account-btn sup-form-btn"
data-l10n-id="setup-add-another-account">
AdD AnotheR AccounT
</button>
<button data-event="click:onShowMail"
class="sup-show-mail-btn sup-form-btn recommend"
data-l10n-id="setup-show-mail">
ContinuE tO MaiL
</button>
</section>

View file

@ -0,0 +1,21 @@
define([
'require',
'evt',
'./base_card',
'template!./setup_done.html'
], function (require) {
var evt = require('evt');
return [
require('./base_card')(require('template!./setup_done.html')),
{
onAddAnother: function () {
evt.emit('addAccount');
},
onShowMail: function () {
evt.emit('showLatestAccount');
},
die: function () {
}
}
];
});

View file

@ -0,0 +1,16 @@
<section class="error-card form-card card skin-organic bbshim" role="region">
<header>
<h1 class="sup-account-header-label header-label"></h1>
</header>
<div class="scrollregion-below-header">
<h2 class="sup-enable-label sup-form-label"></h2>
<p data-prop="accountNode" class="sup-gmail-account"></p>
<div class="actions">
<button data-event="click:onDismiss"
class="sup-dismiss-btn recommend"></button>
</div>
</div>
</section>

View file

@ -0,0 +1,38 @@
define([
'require',
'cards',
'./base_card',
'template!./setup_fix_gmail.html'
], function (require) {
var cards = require('cards');
return [
require('./base_card')(require('template!./setup_fix_gmail.html')),
{
extraClasses: [
'anim-fade',
'anim-overlay'
],
onArgs: function (args) {
this.account = args.account;
this.restoreCard = args.restoreCard;
this.accountNode.textContent = this.account.name;
var translations = {
'sup-account-header-label': 'setup-gmail-{ACCOUNT_TYPE}-header',
'sup-enable-label': 'setup-gmail-{ACCOUNT_TYPE}-message',
'sup-dismiss-btn': 'setup-gmail-{ACCOUNT_TYPE}-retry'
};
var accountType = this.account.type === 'imap+smtp' ? 'imap' : 'pop3';
for (var className in translations) {
var l10nId = translations[className].replace('{ACCOUNT_TYPE}', accountType);
this.getElementsByClassName(className)[0].setAttribute('data-l10n-id', l10nId);
}
},
die: function () {
},
onDismiss: function () {
this.account.clearProblems();
cards.removeCardAndSuccessors(this, 'animate', 1, this.restoreCard);
}
}
];
});

View file

@ -0,0 +1,21 @@
<section class="error-card form-card card skin-organic bbshim" role="region">
<header>
<h1 class="sup-account-header-label header-label"
data-l10n-id="setup-gmail-twofactor-header"></h1>
</header>
<div class="scrollregion-below-header">
<h2 class="sup-bad-password-label sup-form-label"
data-l10n-id="setup-gmail-twofactor-message"></h2>
<p data-prop="accountNode" class="sup-bad-password-account"></p>
<input data-prop="passwordNode" class="sup-info-password" type="password"
data-l10n-id="setup-info-password" />
<div class="actions">
<button data-event="click:onUsePassword"
class="sup-use-password-btn recommend"
data-l10n-id="setup-gmail-twofactor-fix"></button>
</div>
</div>
</section>

View file

@ -0,0 +1,11 @@
define([
'require',
'./base_card',
'template!./setup_fix_gmail_twofactor.html',
'./setup_fix_mixin'
], function (require) {
return [
require('./base_card')(require('template!./setup_fix_gmail_twofactor.html')),
require('./setup_fix_mixin')
];
});

View file

@ -0,0 +1,46 @@
define([
'require',
'l10n!',
'cards'
], function (require) {
var mozL10n = require('l10n!'), cards = require('cards');
return {
extraClasses: [
'anim-fade',
'anim-overlay'
],
onArgs: function (args) {
this.account = args.account;
this.whichSide = args.whichSide;
this.restoreCard = args.restoreCard;
var type = this.account.type;
if (type === 'imap+smtp' || type === 'pop3+smtp') {
var l10nString = null;
if (this.whichSide === 'incoming') {
if (type === 'imap+smtp') {
l10nString = 'settings-account-clarify-imap';
} else {
l10nString = 'settings-account-clarify-pop3';
}
} else {
l10nString = 'settings-account-clarify-smtp';
}
mozL10n.setAttributes(this.accountNode, l10nString, { 'account-name': this.account.name });
}
},
onUsePassword: function () {
var password = this.passwordNode.value;
if (password) {
this.account.modifyAccount(this.whichSide === 'incoming' ? { password: password } : { outgoingPassword: password }, this.proceed.bind(this));
} else {
this.proceed();
}
},
proceed: function () {
this.account.clearProblems();
cards.removeCardAndSuccessors(this, 'animate', 1, this.restoreCard);
},
die: function () {
}
};
});

View file

@ -0,0 +1,23 @@
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="error-card form-card skin-organic bbshim" role="region">
<header>
<h1 class="sup-account-header-label header-label"
data-l10n-id="setup-fix-oauth2-header"></h1>
</header>
<div class="scrollregion-below-header">
<p class="sup-oauth-description"
data-l10n-id="setup-fix-oauth2-description">
EmaiL NeedS ReauthorizatioN FoR AccounT
</p>
<p data-prop="oauth2Name" class="sup-oauth2-name"></p>
<div class="actions">
<button data-event="click:onReauth"
class="sup-reauth-btn recommend"
data-l10n-id="setup-fix-oauth2-reauthorize"></button>
</div>
</div>
</section>

View file

@ -0,0 +1,52 @@
define([
'require',
'./oauth2/fetch',
'cards',
'./base_card',
'template!./setup_fix_oauth2.html'
], function (require) {
var oauthFetch = require('./oauth2/fetch'), cards = require('cards');
return [
require('./base_card')(require('template!./setup_fix_oauth2.html')),
{
extraClasses: [
'anim-fade',
'anim-overlay'
],
onArgs: function (args) {
this.account = args.account;
this.restoreCard = args.restoreCard;
this.oauth2Name.textContent = this.account.name;
},
die: function () {
},
onReauth: function (event) {
event.stopPropagation();
event.preventDefault();
var oauth2 = this.account._wireRep.credentials.oauth2;
oauthFetch(oauth2, { login_hint: this.account.username }).then(function (response) {
if (response.status === 'cancel') {
this.delayedClose();
} else if (response.status === 'success') {
this.account.modifyAccount({ oauthTokens: response.tokens });
this.account.clearProblems();
this.delayedClose();
} else {
console.error('Unknown oauthFetch status: ' + response.status);
this.delayedClose();
}
}.bind(this));
},
delayedClose: function () {
setTimeout(this.close.bind(this), 100);
},
close: function (event) {
if (event) {
event.stopPropagation();
event.preventDefault();
}
cards.removeCardAndSuccessors(this, 'animate', 1, this.restoreCard);
}
}
];
});

View file

@ -0,0 +1,28 @@
<section class="error-card form-card card skin-organic bbshim" role="region">
<header>
<h1 class="sup-account-header-label header-label"
data-l10n-id="setup-bad-password-header"></h1>
</header>
<div class="scrollregion-below-header sup-form">
<form>
<h2 class="sup-bad-password-label sup-form-label"
data-l10n-id="setup-bad-password"></h2>
<p data-prop="accountNode" class="sup-bad-password-account"></p>
<p>
<input data-prop="passwordNode"
class="sup-info-password" type="password"
dir="auto"
data-l10n-id="setup-new-password" />
<button type="reset"></button>
</p>
<div class="actions">
<button data-event="click:onUsePassword"
class="sup-use-password-btn recommend"
data-l10n-id="setup-fix-password"></button>
</div>
</form>
</div>
</section>

View file

@ -0,0 +1,11 @@
define([
'require',
'./base_card',
'template!./setup_fix_password.html',
'./setup_fix_mixin'
], function (require) {
return [
require('./base_card')(require('template!./setup_fix_password.html')),
require('./setup_fix_mixin')
];
});

View file

@ -0,0 +1,17 @@
define({
'offline': 'setup-error-offline',
'bad-user-or-pass': 'setup-error-bad-user-or-pass2',
'bad-address': 'setup-error-bad-address',
'not-authorized': 'setup-error-not-authorized',
'unknown': 'setup-error-unknown2',
'needs-app-pass': 'setup-error-needs-app-pass',
'imap-disabled': 'setup-error-imap-disabled',
'pop3-disabled': 'setup-error-pop3-disabled',
'bad-security': 'setup-error-bad-security',
'unresponsive-server': 'setup-error-unresponsive-server',
'pop-server-not-great': 'setup-error-pop-server-not-great',
'server-problem': 'setup-error-server-problem',
'no-config-info': 'setup-error-no-config-info',
'server-maintenance': 'setup-error-server-maintenance',
'user-account-exists': 'setup-error-account-already-exists'
});

View file

@ -0,0 +1,210 @@
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic form-card bbshim" role="region">
<header class="sup-account-header">
<a data-event="click:onBack" href="#" class="sup-back-btn" role="button"
data-l10n-id="back-button">
<span class="icon icon-back"></span>
</a>
<h1 class="sup-account-header-label header-label"
data-l10n-id="setup-manual-config-header"></h1>
</header>
<div data-prop="scrollBelowNode" class="scrollregion-below-header">
<div data-prop="errorRegionNode" class="sup-error-region collapsed">
<div data-prop="errorMessageNode" class="sup-error-message"></div>
</div>
<header class="collapsed"></header>
<form data-prop="formNode" class="sup-manual-form">
<div class="sup-manual-common sup-form">
<p>
<!-- x-inputmode is a temporary v1 B2G stop-gap -->
<input class="sup-info-name" type="text"
data-l10n-id="setup-info-name"
x-inputmode="verbatim"
inputmode="verbatim"
dir="auto"
required />
<button type="reset"></button>
</p>
<p>
<input class="sup-info-email" type="email"
data-l10n-id="setup-info-email"
dir="auto"
required />
<button type="reset"></button>
</p>
<p class="sup-manual-password-wrapper">
<input class="sup-info-password" type="password"
data-l10n-id="setup-info-password"
dir="auto"
required />
<button type="reset"></button>
</p>
</div>
<header>
<h2 data-l10n-id="setup-manual-account-type"></h2>
</header>
<div class="sup-form sup-form-item">
<!-- These don't need L10N as they are protocol names. -->
<li class="sup-manual-account-type-container">
<span class="button icon icon-dialog">
<select data-prop="accountTypeNode"
data-event="change:onChangeAccountType"
data-l10n-id="setup-manual-account-type-select"
class="mail-select sup-manual-account-type">
<option value="imap+smtp">IMAP+SMTP</option>
<option value="pop3+smtp">POP3+SMTP</option>
<option value="activesync">ActiveSync</option>
</select>
</span>
</li>
</div>
<section data-prop="compositeSection"
class="sup-manual-composite" role="region">
<header class="collapsed"></header>
<header>
<h2 data-prop="manualImapTitle" class="sup-manual-imap-title"
data-l10n-id="setup-manual-imap-header"></h2>
<h2 data-prop="manualPop3Title" class="sup-manual-pop3-title"
data-l10n-id="setup-manual-pop3-header"></h2>
</header>
<div class="sup-form">
<p>
<input class="sup-manual-composite-hostname" type="text"
data-l10n-id="setup-manual-hostname"
x-inputmode="verbatim"
inputmode="verbatim"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
<p>
<input class="sup-manual-composite-username" type="text"
data-l10n-id="setup-manual-username"
x-inputmode="verbatim"
inputmode="verbatim"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
<p>
<input class="sup-manual-composite-password" type="password"
data-l10n-id="setup-info-password"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
<li class="sup-socket-select-container">
<span class="button icon icon-dialog">
<select class="mail-select sup-manual-composite-socket sup-socket-select"
data-l10n-id="setup-manual-socket-select">
<option value="SSL" data-l10n-id="setup-manual-socket-ssl">
SsL</option>
<option value="STARTTLS" data-l10n-id="setup-manual-socket-starttls">
StarttlS</option>
<option value="PLAIN" data-l10n-id="setup-manual-socket-plain">
PlaiN</option>
</select>
</span>
</li>
<p>
<input class="sup-manual-composite-port" type="text"
data-l10n-id="setup-manual-port" value="993"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
</div>
<header>
<h2 data-l10n-id="setup-manual-smtp-header"></h2>
</header>
<div class="sup-form">
<p>
<input class="sup-manual-smtp-hostname" type="text"
data-l10n-id="setup-manual-hostname"
x-inputmode="verbatim"
inputmode="verbatim"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
<p>
<input class="sup-manual-smtp-username" type="text"
data-l10n-id="setup-manual-username"
x-inputmode="verbatim"
inputmode="verbatim"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
<p>
<input class="sup-manual-smtp-password" type="password"
data-l10n-id="setup-info-password"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
<li class="sup-socket-select-container">
<span class="button icon icon-dialog">
<select class="mail-select sup-manual-smtp-socket sup-socket-select"
data-l10n-id="setup-manual-socket-select">
<option value="SSL" data-l10n-id="setup-manual-socket-ssl">
SsL</option>
<option value="STARTTLS" data-l10n-id="setup-manual-socket-starttls">
StarttlS</option>
<option value="PLAIN" data-l10n-id="setup-manual-socket-plain">
PlaiN</option>
</select>
</span>
</li>
<p>
<input class="sup-manual-smtp-port" type="text"
data-l10n-id="setup-manual-port" value="465"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
</div>
</section>
<section data-prop="activeSyncSection"
class="sup-manual-activesync collapsed" role="region">
<header class="collapsed"></header>
<header>
<h2 data-l10n-id="setup-manual-activesync-header">
ActivesynC SettingS</h2>
</header>
<div class="sup-form">
<p>
<input class="sup-manual-activesync-hostname" type="text"
data-l10n-id="setup-manual-hostname"
x-inputmode="verbatim"
inputmode="verbatim"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
<p>
<input class="sup-manual-activesync-username" type="text"
data-l10n-id="setup-manual-activesync-username"
dir="auto"
data-maybe-required />
<button type="reset"></button>
</p>
</div>
</section>
<div class="sup-form">
<p>
<button data-prop="nextButton"
data-event="click:onNext"
class="sup-manual-next-btn recommend" disabled>
<span data-l10n-id="setup-info-next"></span>
</button>
</p>
</div>
</form>
</div>
</section>

View file

@ -0,0 +1,235 @@
define([
'require',
'cards',
'confirm_dialog',
'form_navigation',
'tmpl!./tng/plain_socket_warning.html',
'./base_card',
'template!./setup_manual_config.html',
'./setup_account_error_mixin'
], function (require) {
var cards = require('cards'), ConfirmDialog = require('confirm_dialog'), FormNavigation = require('form_navigation'), plainSocketWarningNode = require('tmpl!./tng/plain_socket_warning.html');
return [
require('./base_card')(require('template!./setup_manual_config.html')),
require('./setup_account_error_mixin'),
{
onArgs: function (args) {
this.formItems = {
common: {
displayName: this._fromClass('sup-info-name'),
emailAddress: this._fromClass('sup-info-email'),
password: this._fromClass('sup-info-password'),
passwordWrapper: this._fromClass('sup-manual-password-wrapper')
},
composite: {
hostname: this._fromClass('sup-manual-composite-hostname'),
port: this._fromClass('sup-manual-composite-port'),
socket: this._fromClass('sup-manual-composite-socket'),
username: this._fromClass('sup-manual-composite-username'),
password: this._fromClass('sup-manual-composite-password')
},
smtp: {
hostname: this._fromClass('sup-manual-smtp-hostname'),
port: this._fromClass('sup-manual-smtp-port'),
socket: this._fromClass('sup-manual-smtp-socket'),
username: this._fromClass('sup-manual-smtp-username'),
password: this._fromClass('sup-manual-smtp-password')
},
activeSync: {
hostname: this._fromClass('sup-manual-activesync-hostname'),
username: this._fromClass('sup-manual-activesync-username')
}
};
var password = password || '';
var common = this.formItems.common;
common.displayName.value = args.displayName;
common.emailAddress.value = args.emailAddress;
common.password.value = password;
var composite = this.formItems.composite;
composite.username.value = args.emailAddress;
composite.password.value = password;
var smtp = this.formItems.smtp;
smtp.username.value = args.emailAddress;
smtp.password.value = password;
this.changeIfSame(common.emailAddress, [
composite.username,
smtp.username
]);
this.changeIfSame(composite.username, [smtp.username]);
this.changeIfSame(composite.password, [
smtp.password,
common.password
]);
for (var type in this.formItems) {
for (var field in this.formItems[type]) {
if (this.formItems[type][field].tagName === 'INPUT') {
this.formItems[type][field].addEventListener('input', this.onInfoInput.bind(this));
}
}
}
this.requireFields('composite', true);
this.requireFields('smtp', true);
this.requireFields('activeSync', false);
composite.socket.addEventListener('change', this.onChangeCompositeSocket.bind(this));
smtp.socket.addEventListener('change', this.onChangeSmtpSocket.bind(this));
this.onChangeAccountType({ target: this.accountTypeNode });
this._formNavigation = new FormNavigation({
formElem: this.formNode,
onLast: this.onNext.bind(this)
});
},
_fromClass: function (className) {
return this.getElementsByClassName(className)[0];
},
onBack: function (event) {
cards.removeCardAndSuccessors(this, 'animate', 1);
},
onNext: function (event) {
event.preventDefault();
var config = { type: this.accountTypeNode.value };
if (config.type === 'imap+smtp' || config.type === 'pop3+smtp') {
config.incoming = {
hostname: this.formItems.composite.hostname.value,
port: this.formItems.composite.port.value,
socketType: this.formItems.composite.socket.value,
username: this.formItems.composite.username.value,
password: this.formItems.composite.password.value,
authentication: 'password-cleartext'
};
config.outgoing = {
hostname: this.formItems.smtp.hostname.value,
port: this.formItems.smtp.port.value,
socketType: this.formItems.smtp.socket.value,
username: this.formItems.smtp.username.value,
password: this.formItems.smtp.password.value,
authentication: 'password-cleartext'
};
} else {
config.incoming = {
server: 'https://' + this.formItems.activeSync.hostname.value,
username: this.formItems.activeSync.username.value
};
}
this.pushSetupCard(config);
},
pushSetupCard: function (config) {
var password;
if (this.accountTypeNode.value === 'activesync') {
password = this.formItems.common.password.value;
} else {
password = this.formItems.composite.password.value;
}
cards.pushCard('setup_progress', 'animate', {
displayName: this.formItems.common.displayName.value,
emailAddress: this.formItems.common.emailAddress.value,
password: password,
outgoingPassword: config.outgoing && config.outgoing.password,
configInfo: config,
callingCard: this
}, 'right');
},
onInfoInput: function (ignoredEvent) {
this.nextButton.disabled = !this.formNode.checkValidity();
},
changeIfSame: function (sourceField, destFields) {
sourceField._previousValue = sourceField.value;
sourceField.addEventListener('input', function (e) {
for (var i = 0; i < destFields.length; i++) {
var destField = destFields[i];
if (destField.value === e.target._previousValue) {
destField.value = destField._previousValue = e.target.value;
}
}
sourceField._previousValue = e.target.value;
this.onInfoInput();
}.bind(this));
},
onChangeAccountType: function (event) {
var isComposite = event.target.value === 'imap+smtp' || event.target.value === 'pop3+smtp';
var isImap = event.target.value === 'imap+smtp';
if (isComposite) {
this.compositeSection.classList.remove('collapsed');
this.activeSyncSection.classList.add('collapsed');
this.manualImapTitle.classList.toggle('collapsed', !isImap);
this.manualPop3Title.classList.toggle('collapsed', isImap);
} else {
this.compositeSection.classList.add('collapsed');
this.activeSyncSection.classList.remove('collapsed');
}
this.formItems.common.passwordWrapper.classList.toggle('collapsed', isComposite);
this.requireFields('composite', isComposite);
this.requireFields('smtp', isComposite);
this.requireFields('activeSync', !isComposite);
this.onChangeCompositeSocket({ target: this.formItems.composite.socket });
},
onChangeCompositeSocket: function (event) {
var isImap = this.accountTypeNode.value === 'imap+smtp';
var SSL_VALUE = isImap ? '993' : '995';
var STARTTLS_VALUE = isImap ? '143' : '110';
var socketType = event.target.value;
if (socketType === 'PLAIN') {
this.showPlainSocketWarning();
}
var portField = this.formItems.composite.port;
if (socketType === 'SSL') {
portField.value = SSL_VALUE;
} else if (socketType === 'STARTTLS' || socketType === 'PLAIN') {
portField.value = STARTTLS_VALUE;
}
},
onChangeSmtpSocket: function (event) {
const SSL_VALUE = '465';
const STARTTLS_VALUE = '587';
const PLAIN_VALUE = '25';
var socketType = event.target.value;
var portField = this.formItems.smtp.port;
if (socketType == 'PLAIN') {
this.showPlainSocketWarning();
}
if (socketType === 'SSL' && (portField.value === STARTTLS_VALUE || portField.value === PLAIN_VALUE)) {
portField.value = SSL_VALUE;
} else if (socketType == 'STARTTLS' && (portField.value == SSL_VALUE || portField.value == PLAIN_VALUE)) {
portField.value = STARTTLS_VALUE;
} else if (socketType == 'PLAIN' && (portField.value == SSL_VALUE || portField.value == STARTTLS_VALUE)) {
portField.value = PLAIN_VALUE;
}
},
requireFields: function (type, required) {
for (var field in this.formItems[type]) {
var item = this.formItems[type][field];
if (!item.hasAttribute('data-maybe-required')) {
continue;
}
if (required) {
item.setAttribute('required', '');
} else {
item.removeAttribute('required');
}
}
},
showPlainSocketWarning: function () {
var dialog = plainSocketWarningNode.cloneNode(true);
var learnMoreNode = dialog.querySelector('.tng-plain-socket-learn-more');
learnMoreNode.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
new MozActivity({
name: 'view',
data: {
type: 'url',
url: learnMoreNode.dataset.href
}
});
});
ConfirmDialog.show(dialog, {
id: 'tng-plain-socket-warning-ok',
handler: function () {
}
});
},
die: function () {
this._formNavigation = null;
}
}
];
});

View file

@ -0,0 +1,16 @@
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic bbshim" role="region">
<header class="sup-account-header">
<a data-event="click:onBack"
href="#" class="sup-back-btn">
<span class="icon icon-back"></span>
</a>
<h1 data-prop="headerLabel"
dir="auto"
class="sup-account-header-label header-label"></h1>
</header>
<section data-prop="scrollRegion"
class="scrollregion-below-header skin-organic" role="region">
</section>
</section>

View file

@ -0,0 +1,62 @@
define([
'require',
'app_self',
'cards',
'./base_card',
'template!./setup_oauth2.html'
], function (require) {
var appSelf = require('app_self'), cards = require('cards');
return [
require('./base_card')(require('template!./setup_oauth2.html')),
{
onArgs: function (args) {
this.onBrowserComplete = args.onBrowserComplete;
var browserFrame = document.createElement('iframe');
browserFrame.classList.add('sup-oauth2-browser');
browserFrame.setAttribute('mozbrowser', true);
browserFrame.setAttribute('src', args.url);
browserFrame.addEventListener('mozbrowserlocationchange', this.onLocationChange.bind(this));
this.scrollRegion.appendChild(browserFrame);
},
die: function () {
appSelf.latest('self', function (app) {
console.log('clearing browser data: ' + app);
if (app) {
app.clearBrowserData();
}
console.log('browser data cleared');
});
},
close: function () {
cards.removeCardAndSuccessors(this, 'animate', 1);
},
onBack: function (event) {
if (event) {
event.stopPropagation();
event.preventDefault();
}
this.onBrowserComplete({ type: 'oauth2Cancel' });
this.close();
},
onLocationChange: function (event) {
var url = event.detail;
if (url.indexOf('app:') !== 0 || url.indexOf('cards/oauth2/redirect.html?') === -1) {
this.headerLabel.textContent = url;
return;
}
this.close();
var search = url.split('?')[1] || '';
var result = {
type: 'oauth2Complete',
data: {}
};
var elements = search.split('&');
elements.forEach(function (p) {
var values = p.split('=');
result.data[decodeURIComponent(values[0])] = decodeURIComponent(values[1]);
});
this.onBrowserComplete(result);
}
}
];
});

View file

@ -0,0 +1,17 @@
<!-- need this section element to keep building blocks happy. Too bad it
expects a section element -->
<section class="skin-organic bbshim" role="region">
<header class="sup-account-header">
<h1 class="sup-account-header-label"
data-l10n-id="setup-account-header3"></h1>
</header>
<form role="dialog" data-type="confirm" data-statuscolor="background">
<section>
<h1 data-l10n-id="setup-progress-wait-msg"></h1>
<p class="sup-progress-container"><progress></progress></p>
</section>
<menu><button data-event="click:onBack"
class="sup-back-btn full"
data-l10n-id="message-multiedit-cancel"></button></menu>
</form>
</section>

View file

@ -0,0 +1,121 @@
define([
'require',
'api',
'cards',
'cards/oauth2/fetch',
'./base_card',
'template!./setup_progress.html'
], function (require) {
var MailAPI = require('api'), cards = require('cards'), oauthFetch = require('cards/oauth2/fetch');
return [
require('./base_card')(require('template!./setup_progress.html')),
{
onArgs: function (args) {
this.args = args;
this.callingCard = args.callingCard;
this.creationInProcess = true;
this.pushedSecondaryCard = false;
this.cardHasBeenShown = false;
this.createCanceled = false;
},
extraClasses: [
'anim-fade',
'anim-overlay'
],
cancelCreation: function () {
if (!this.creationInProcess) {
return;
}
},
onCardVisible: function () {
if (this.cardHasBeenShown) {
if (this.pushedSecondaryCard) {
setTimeout(this.onBack.bind(this), 100);
}
} else {
this.cardHasBeenShown = true;
if (!this.args.password) {
this.learnAbout();
} else {
this.tryCreate();
}
}
},
onBack: function (e) {
if (e) {
e.preventDefault();
}
this.cancelCreation();
this.createCanceled = true;
cards.removeCardAndSuccessors(this, 'animate', 1);
},
learnAbout: function () {
MailAPI.learnAboutAccount({ emailAddress: this.args.emailAddress }, function (details) {
var args = this.args;
args.configInfo = details.configInfo;
var result = details.result;
if (result === 'need-oauth2') {
oauthFetch(details.configInfo.oauth2Settings, { login_hint: args.emailAddress }).then(function (response) {
if (response.status === 'cancel') {
this.onBack();
} else if (response.status === 'success') {
args.configInfo.oauth2Secrets = response.secrets;
args.configInfo.oauth2Tokens = response.tokens;
this.tryCreate();
} else {
console.error('Unknown oauthFetch status: ' + response.status);
this._divertToManualConfig();
}
}.bind(this), this.onCreationError.bind(this));
} else if (result === 'need-password') {
this.pushedSecondaryCard = true;
cards.pushCard('setup_account_password', 'animate', {
displayName: args.displayName,
emailAddress: args.emailAddress
}, 'right');
} else {
this._divertToManualConfig();
}
}.bind(this));
},
_divertToManualConfig: function () {
this.pushedSecondaryCard = true;
cards.pushCard('setup_manual_config', 'animate', {
displayName: this.args.displayName,
emailAddress: this.args.emailAddress
}, 'right');
},
tryCreate: function () {
var args = this.args;
var options = {
displayName: args.displayName,
emailAddress: args.emailAddress,
password: args.password,
outgoingPassword: args.outgoingPassword
};
MailAPI.tryToCreateAccount(options, args.configInfo || null, function (err, errDetails, account) {
this.creationInProcess = false;
if (err) {
this.onCreationError(err, errDetails);
} else {
if (this.createCanceled) {
account.deleteAccount();
} else {
this.onCreationSuccess(account);
}
}
}.bind(this));
},
onCreationError: function (err, errDetails) {
this.callingCard.showError(err, errDetails);
cards.removeCardAndSuccessors(this, 'animate', 1);
},
onCreationSuccess: function (account) {
cards.pushCard('setup_account_prefs', 'animate', { account: account });
},
die: function () {
this.cancelCreation();
}
}
];
});

View file

@ -0,0 +1,9 @@
<form role="dialog" class="cmp-draft-menu" data-type="action">
<menu>
<button id="sig-save" data-l10n-id="signature-save"></button>
<button id="sig-discard" class="danger" data-l10n-id="signature-discard-confirm"></button>
<span class="last-button-container">
<button id="sig-cancel" data-l10n-id="message-multiedit-cancel"></button>
</span>
</menu>
</form>

View file

@ -0,0 +1,10 @@
<form role="dialog" class="tng-account-delete-confirm" data-type="confirm">
<section>
<h1 data-l10n-id="confirm-dialog-title"></h1>
<p></p>
</section>
<menu>
<button id="account-delete-cancel" data-l10n-id="message-multiedit-cancel"></button>
<button id="account-delete-ok" class="danger" data-l10n-id="settings-account-delete-confirm"></button>
</menu>
</form>

View file

@ -0,0 +1,4 @@
<li class="tng-account-item item-with-children" role="option">
<a href="#" class="tng-account-item-label list-text"></a>
<!-- we may want to expose some other info in this row -->
</li>

View file

@ -0,0 +1,3 @@
<li class="tng-account-settings-server item-with-children">
<a href="#" class="tng-account-server-label list-text"></a>
</li>

View file

@ -0,0 +1,15 @@
<form role="dialog" class="tng-plain-socket-confirm" data-type="confirm">
<section>
<h1 data-l10n-id="tng-plain-socket-warning-title"></h1>
<p data-l10n-id="tng-plain-socket-warning-message"></p>
<p data-l10n-id="tng-plain-socket-warning-message-options"></p>
</section>
<menu>
<button class="ok-btn recommend tng-plain-socket-learn-more"
data-href="https://support.mozilla.org/kb/how-switch-secure-email-provider-firefox-os"
data-l10n-id="tng-plain-socket-learn-more"></button>
<button class="ok-btn confirm-dialog-ok"
data-l10n-id="tng-plain-socket-warning-ok"></button>
</menu>
</form>

View file

@ -0,0 +1,4 @@
<section role="status" class="toaster collapsed">
<p class="toaster-text"></p>
<div class="toaster-action-target"><button class="toaster-action"></button></div>
</section>

View file

@ -0,0 +1,11 @@
<form class="email-value-selector collapsed" role="dialog" data-type="value-selector">
<section class="scrollable">
<h1></h1>
<ol role="listbox">
</ol>
</section>
<menu>
<button class="full"
data-l10n-id="message-multiedit-cancel"></button>
</menu>
</form>

View file

@ -0,0 +1 @@
<li role="option"><label role="presentation"> <span></span></label></li>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
define({
load: function (id, require, onload, config) {
if (config.isBuild) {
return onload();
}
var style = document.createElement('link');
style.type = 'text/css';
style.rel = 'stylesheet';
style.href = require.toUrl(id + '.css');
style.addEventListener('load', onload, false);
document.head.appendChild(style);
}
});

View file

@ -0,0 +1,47 @@
define([], function () {
return function debugTraceMethods(obj, moduleId, perfLogThreshold) {
perfLogThreshold = perfLogThreshold || 0;
var logQueue = [], logTimeoutId = 0;
function logPerf() {
logQueue.forEach(function (msg) {
console.log(msg);
});
logQueue = [];
logTimeoutId = 0;
}
function queueLog(prop, time, arg0) {
var arg0Type = typeof arg0;
logQueue.push(moduleId + ': ' + prop + (arg0Type === 'number' || arg0Type === 'boolean' || arg0Type === 'string' ? ': (' + arg0 + ')' : '') + (perfLogThreshold === 0 ? '' : ': ' + time));
if (perfLogThreshold === 0) {
logPerf();
} else {
if (!logTimeoutId) {
logTimeoutId = setTimeout(logPerf, 2000);
}
}
}
function perfWrap(prop, fn) {
return function () {
var start = performance.now();
if (perfLogThreshold === 0) {
queueLog(prop, 0, arguments[0]);
}
var result = fn.apply(this, arguments);
var end = performance.now();
var time = end - start;
if (perfLogThreshold > 0 && time > perfLogThreshold) {
queueLog(prop, end - start, arguments[0]);
}
return result;
};
}
if (perfLogThreshold > -1) {
Object.keys(obj).forEach(function (prop) {
var proto = obj;
if (typeof proto[prop] === 'function') {
proto[prop] = perfWrap(prop, proto[prop]);
}
});
}
};
});

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,117 @@
define([
'mailbuild',
'../mailchew',
'../util'
], function (MimeNode, $mailchew, $imaputil) {
var formatAddresses = $imaputil.formatAddresses;
MimeNode.prototype.removeHeader = function (key) {
for (var i = 0, len = this._headers.length; i < len; i++) {
if (this._headers[i].key === key) {
this._headers.splice(i, 1);
break;
}
}
};
function normalizeNewlines(body) {
return body.replace(/\r?\n|\r/g, '\r\n');
}
function Composer(newRecords, account, identity) {
var header = this.header = newRecords.header;
var body = this.body = newRecords.body;
this.account = account;
this.identity = identity;
this.sentDate = new Date(this.header.date);
this._smartWakeLock = null;
this.messageId = '<' + Date.now() + Math.random().toString(16).substr(1) + '@mozgaia>';
var messageNode;
var textContent = body.bodyReps[0].content[1];
if (body.bodyReps.length === 2) {
var htmlContent = body.bodyReps[1].content;
messageNode = new MimeNode('text/html');
messageNode.setContent(normalizeNewlines($mailchew.mergeUserTextWithHTML(textContent, htmlContent)));
} else {
messageNode = new MimeNode('text/plain');
messageNode.setContent(normalizeNewlines(textContent));
}
var root;
if (body.attachments.length) {
root = this._rootNode = new MimeNode('multipart/mixed');
root.appendChild(messageNode);
} else {
root = this._rootNode = messageNode;
}
root.setHeader('From', formatAddresses([this.identity]));
root.setHeader('Subject', header.subject);
if (this.identity.replyTo) {
root.setHeader('Reply-To', this.identity.replyTo);
}
if (header.to && header.to.length) {
root.setHeader('To', formatAddresses(header.to));
}
if (header.cc && header.cc.length) {
root.setHeader('Cc', formatAddresses(header.cc));
}
if (header.bcc && header.bcc.length) {
root.setHeader('Bcc', formatAddresses(header.bcc));
}
root.setHeader('User-Agent', 'GaiaMail/0.2');
root.setHeader('Date', this.sentDate.toUTCString());
root.setHeader('Message-Id', this.messageId);
if (body.references) {
root.setHeader('References', body.references);
}
root.setHeader('Content-Transfer-Encoding', 'quoted-printable');
this._blobReplacements = [];
this._uniqueBlobBoundary = '{{blob!' + Math.random() + Date.now() + '}}';
body.attachments.forEach(function (attachment) {
try {
var attachmentNode = new MimeNode(attachment.type, { filename: attachment.name });
attachmentNode.setHeader('Content-Transfer-Encoding', 'base64');
attachmentNode.setContent(this._uniqueBlobBoundary);
root.appendChild(attachmentNode);
this._blobReplacements.push(new Blob(attachment.file));
} catch (ex) {
console.error('Problem attaching attachment:', ex, '\n', ex.stack);
}
}.bind(this));
}
Composer.prototype = {
getEnvelope: function () {
return this._rootNode.getEnvelope();
},
withMessageBlob: function (opts, callback) {
var TEMP_BCC = 'Bcc-Temp';
var TEMP_BCC_REGEX = /^Bcc-Temp: /m;
var hasBcc = opts.includeBcc && this.header.bcc && this.header.bcc.length;
if (hasBcc) {
this._rootNode.setHeader(TEMP_BCC, formatAddresses(this.header.bcc));
} else {
this._rootNode.removeHeader(TEMP_BCC);
}
var str = this._rootNode.build();
if (opts.smtp) {
str = str.replace(/\n\./g, '\n..');
}
if (hasBcc) {
str = str.replace(TEMP_BCC_REGEX, 'Bcc: ');
}
if (str.slice(-2) !== '\r\n') {
str += '\r\n';
}
var splits = str.split(btoa(this._uniqueBlobBoundary) + '\r\n');
this._blobReplacements.forEach(function (blob, i) {
splits.splice(i * 2 + 1, 0, blob);
});
callback(new Blob(splits, { type: this._rootNode.getHeader('content-type') }));
},
setSmartWakeLock: function (wakeLock) {
this._smartWakeLock = wakeLock;
},
renewSmartWakeLock: function (reason) {
if (this._smartWakeLock) {
this._smartWakeLock.renew(reason);
}
}
};
return { Composer: Composer };
});

View file

@ -0,0 +1,74 @@
(function (root, factory) {
if (typeof exports === 'object') {
define = function (deps, factory) {
deps = deps.map.forEach(function (id) {
return require(id);
});
module.exports = factory(deps);
};
define.amd = {};
}
if (typeof define === 'function' && define.amd) {
define([
'wbxml',
'./codepages/Common',
'./codepages/AirSync',
'./codepages/Contacts',
'./codepages/Email',
'./codepages/Calendar',
'./codepages/Move',
'./codepages/ItemEstimate',
'./codepages/FolderHierarchy',
'./codepages/MeetingResponse',
'./codepages/Tasks',
'./codepages/ResolveRecipients',
'./codepages/ValidateCert',
'./codepages/Contacts2',
'./codepages/Ping',
'./codepages/Provision',
'./codepages/Search',
'./codepages/GAL',
'./codepages/AirSyncBase',
'./codepages/Settings',
'./codepages/DocumentLibrary',
'./codepages/ItemOperations',
'./codepages/ComposeMail',
'./codepages/Email2',
'./codepages/Notes',
'./codepages/RightsManagement'
], factory);
} else {
root.ActiveSyncCodepages = factory(WBXML, ASCPCommon, ASCPAirSync, ASCPContacts, ASCPEmail, ASCPCalendar, ASCPMove, ASCPItemEstimate, ASCPHierarchy, ASCPMeetingResponse, ASCPTasks, ASCPResolveRecipients, ASCPValidateCert, ASCPContacts2, ASCPPing, ASCPProvision, ASCPSearch, ASCPGAL, ASCPAirSyncBase, ASCPSettings, ASCPDocumentLibrary, ASCPItemOperations, ASCPComposeMail, ASCPEmail2, ASCPNotes, ASCPRightsManagement);
}
}(this, function (WBXML, Common, AirSync, Contacts, Email, Calendar, Move, ItemEstimate, FolderHierarchy, MeetingResponse, Tasks, ResolveRecipients, ValidateCert, Contacts2, Ping, Provision, Search, GAL, AirSyncBase, Settings, DocumentLibrary, ItemOperations, ComposeMail, Email2, Notes, RightsManagement) {
'use strict';
var codepages = {
Common: Common,
AirSync: AirSync,
Contacts: Contacts,
Email: Email,
Calendar: Calendar,
Move: Move,
ItemEstimate: ItemEstimate,
FolderHierarchy: FolderHierarchy,
MeetingResponse: MeetingResponse,
Tasks: Tasks,
ResolveRecipients: ResolveRecipients,
ValidateCert: ValidateCert,
Contacts2: Contacts2,
Ping: Ping,
Provision: Provision,
Search: Search,
GAL: GAL,
AirSyncBase: AirSyncBase,
Settings: Settings,
DocumentLibrary: DocumentLibrary,
ItemOperations: ItemOperations,
ComposeMail: ComposeMail,
Email2: Email2,
Notes: Notes,
RightsManagement: RightsManagement
};
WBXML.CompileCodepages(codepages);
return codepages;
}));

View file

@ -0,0 +1,99 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPAirSync = factory();
}(this, function () {
'use strict';
return {
Tags: {
Sync: 5,
Responses: 6,
Add: 7,
Change: 8,
Delete: 9,
Fetch: 10,
SyncKey: 11,
ClientId: 12,
ServerId: 13,
Status: 14,
Collection: 15,
Class: 16,
Version: 17,
CollectionId: 18,
GetChanges: 19,
MoreAvailable: 20,
WindowSize: 21,
Commands: 22,
Options: 23,
FilterType: 24,
Truncation: 25,
RtfTruncation: 26,
Conflict: 27,
Collections: 28,
ApplicationData: 29,
DeletesAsMoves: 30,
NotifyGUID: 31,
Supported: 32,
SoftDelete: 33,
MIMESupport: 34,
MIMETruncation: 35,
Wait: 36,
Limit: 37,
Partial: 38,
ConversationMode: 39,
MaxItems: 40,
HeartbeatInterval: 41
},
Enums: {
Status: {
Success: '1',
InvalidSyncKey: '3',
ProtocolError: '4',
ServerError: '5',
ConversionError: '6',
MatchingConflict: '7',
ObjectNotFound: '8',
OutOfSpace: '9',
HierarchyChanged: '12',
IncompleteRequest: '13',
InvalidInterval: '14',
InvalidRequest: '15',
Retry: '16'
},
FilterType: {
NoFilter: '0',
OneDayBack: '1',
ThreeDaysBack: '2',
OneWeekBack: '3',
TwoWeeksBack: '4',
OneMonthBack: '5',
ThreeMonthsBack: '6',
SixMonthsBack: '7',
IncompleteTasks: '8'
},
Conflict: {
ClientReplacesServer: '0',
ServerReplacesClient: '1'
},
MIMESupport: {
Never: '0',
SMIMEOnly: '1',
Always: '2'
},
MIMETruncation: {
TruncateAll: '0',
Truncate4K: '1',
Truncate5K: '2',
Truncate7K: '3',
Truncate10K: '4',
Truncate20K: '5',
Truncate50K: '6',
Truncate100K: '7',
NoTruncate: '8'
}
}
};
}));

View file

@ -0,0 +1,56 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPAirSyncBase = factory();
}(this, function () {
'use strict';
return {
Tags: {
BodyPreference: 4357,
Type: 4358,
TruncationSize: 4359,
AllOrNone: 4360,
Reserved: 4361,
Body: 4362,
Data: 4363,
EstimatedDataSize: 4364,
Truncated: 4365,
Attachments: 4366,
Attachment: 4367,
DisplayName: 4368,
FileReference: 4369,
Method: 4370,
ContentId: 4371,
ContentLocation: 4372,
IsInline: 4373,
NativeBodyType: 4374,
ContentType: 4375,
Preview: 4376,
BodyPartPreference: 4377,
BodyPart: 4378,
Status: 4379
},
Enums: {
Type: {
PlainText: '1',
HTML: '2',
RTF: '3',
MIME: '4'
},
Method: {
Normal: '1',
EmbeddedMessage: '5',
AttachOLE: '6'
},
NativeBodyType: {
PlainText: '1',
HTML: '2',
RTF: '3'
},
Status: { Success: '1' }
}
};
}));

View file

@ -0,0 +1,69 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPCalendar = factory();
}(this, function () {
'use strict';
return {
Tags: {
TimeZone: 1029,
AllDayEvent: 1030,
Attendees: 1031,
Attendee: 1032,
Email: 1033,
Name: 1034,
Body: 1035,
BodyTruncated: 1036,
BusyStatus: 1037,
Categories: 1038,
Category: 1039,
CompressedRTF: 1040,
DtStamp: 1041,
EndTime: 1042,
Exception: 1043,
Exceptions: 1044,
Deleted: 1045,
ExceptionStartTime: 1046,
Location: 1047,
MeetingStatus: 1048,
OrganizerEmail: 1049,
OrganizerName: 1050,
Recurrence: 1051,
Type: 1052,
Until: 1053,
Occurrences: 1054,
Interval: 1055,
DayOfWeek: 1056,
DayOfMonth: 1057,
WeekOfMonth: 1058,
MonthOfYear: 1059,
Reminder: 1060,
Sensitivity: 1061,
Subject: 1062,
StartTime: 1063,
UID: 1064,
AttendeeStatus: 1065,
AttendeeType: 1066,
Attachment: 1067,
Attachments: 1068,
AttName: 1069,
AttSize: 1070,
AttOid: 1071,
AttMethod: 1072,
AttRemoved: 1073,
DisplayName: 1074,
DisallowNewTimeProposal: 1075,
ResponseRequested: 1076,
AppointmentReplyTime: 1077,
ResponseType: 1078,
CalendarType: 1079,
IsLeapMonth: 1080,
FirstDayOfWeek: 1081,
OnlineMeetingConfLink: 1082,
OnlineMeetingExternalLink: 1083
}
};
}));

View file

@ -0,0 +1,90 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPCommon = factory();
}(this, function () {
'use strict';
return {
Enums: {
Status: {
InvalidContent: '101',
InvalidWBXML: '102',
InvalidXML: '103',
InvalidDateTime: '104',
InvalidCombinationOfIDs: '105',
InvalidIDs: '106',
InvalidMIME: '107',
DeviceIdMissingOrInvalid: '108',
DeviceTypeMissingOrInvalid: '109',
ServerError: '110',
ServerErrorRetryLater: '111',
ActiveDirectoryAccessDenied: '112',
MailboxQuotaExceeded: '113',
MailboxServerOffline: '114',
SendQuotaExceeded: '115',
MessageRecipientUnresolved: '116',
MessageReplyNotAllowed: '117',
MessagePreviouslySent: '118',
MessageHasNoRecipient: '119',
MailSubmissionFailed: '120',
MessageReplyFailed: '121',
AttachmentIsTooLarge: '122',
UserHasNoMailbox: '123',
UserCannotBeAnonymous: '124',
UserPrincipalCouldNotBeFound: '125',
UserDisabledForSync: '126',
UserOnNewMailboxCannotSync: '127',
UserOnLegacyMailboxCannotSync: '128',
DeviceIsBlockedForThisUser: '129',
AccessDenied: '130',
AccountDisabled: '131',
SyncStateNotFound: '132',
SyncStateLocked: '133',
SyncStateCorrupt: '134',
SyncStateAlreadyExists: '135',
SyncStateVersionInvalid: '136',
CommandNotSupported: '137',
VersionNotSupported: '138',
DeviceNotFullyProvisionable: '139',
RemoteWipeRequested: '140',
LegacyDeviceOnStrictPolicy: '141',
DeviceNotProvisioned: '142',
PolicyRefresh: '143',
InvalidPolicyKey: '144',
ExternallyManagedDevicesNotAllowed: '145',
NoRecurrenceInCalendar: '146',
UnexpectedItemClass: '147',
RemoteServerHasNoSSL: '148',
InvalidStoredRequest: '149',
ItemNotFound: '150',
TooManyFolders: '151',
NoFoldersFounds: '152',
ItemsLostAfterMove: '153',
FailureInMoveOperation: '154',
MoveCommandDisallowedForNonPersistentMoveAction: '155',
MoveCommandInvalidDestinationFolder: '156',
AvailabilityTooManyRecipients: '160',
AvailabilityDLLimitReached: '161',
AvailabilityTransientFailure: '162',
AvailabilityFailure: '163',
BodyPartPreferenceTypeNotSupported: '164',
DeviceInformationRequired: '165',
InvalidAccountId: '166',
AccountSendDisabled: '167',
IRM_FeatureDisabled: '168',
IRM_TransientError: '169',
IRM_PermanentError: '170',
IRM_InvalidTemplateID: '171',
IRM_OperationNotPermitted: '172',
NoPicture: '173',
PictureTooLarge: '174',
PictureLimitReached: '175',
BodyPart_ConversationTooLarge: '176',
MaximumDevicesReached: '177'
}
}
};
}));

View file

@ -0,0 +1,28 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPComposeMail = factory();
}(this, function () {
'use strict';
return {
Tags: {
SendMail: 5381,
SmartForward: 5382,
SmartReply: 5383,
SaveInSentItems: 5384,
ReplaceMime: 5385,
Source: 5387,
FolderId: 5388,
ItemId: 5389,
LongId: 5390,
InstanceId: 5391,
Mime: 5392,
ClientId: 5393,
Status: 5394,
AccountId: 5395
}
};
}));

View file

@ -0,0 +1,72 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPContacts = factory();
}(this, function () {
'use strict';
return {
Tags: {
Anniversary: 261,
AssistantName: 262,
AssistantPhoneNumber: 263,
Birthday: 264,
Body: 265,
BodySize: 266,
BodyTruncated: 267,
Business2PhoneNumber: 268,
BusinessAddressCity: 269,
BusinessAddressCountry: 270,
BusinessAddressPostalCode: 271,
BusinessAddressState: 272,
BusinessAddressStreet: 273,
BusinessFaxNumber: 274,
BusinessPhoneNumber: 275,
CarPhoneNumber: 276,
Categories: 277,
Category: 278,
Children: 279,
Child: 280,
CompanyName: 281,
Department: 282,
Email1Address: 283,
Email2Address: 284,
Email3Address: 285,
FileAs: 286,
FirstName: 287,
Home2PhoneNumber: 288,
HomeAddressCity: 289,
HomeAddressCountry: 290,
HomeAddressPostalCode: 291,
HomeAddressState: 292,
HomeAddressStreet: 293,
HomeFaxNumber: 294,
HomePhoneNumber: 295,
JobTitle: 296,
LastName: 297,
MiddleName: 298,
MobilePhoneNumber: 299,
OfficeLocation: 300,
OtherAddressCity: 301,
OtherAddressCountry: 302,
OtherAddressPostalCode: 303,
OtherAddressState: 304,
OtherAddressStreet: 305,
PagerNumber: 306,
RadioPhoneNumber: 307,
Spouse: 308,
Suffix: 309,
Title: 310,
WebPage: 311,
YomiCompanyName: 312,
YomiFirstName: 313,
YomiLastName: 314,
CompressedRTF: 315,
Picture: 316,
Alias: 317,
WeightedRank: 318
}
};
}));

View file

@ -0,0 +1,24 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPContacts2 = factory();
}(this, function () {
'use strict';
return {
Tags: {
CustomerId: 3077,
GovernmentId: 3078,
IMAddress: 3079,
IMAddress2: 3080,
IMAddress3: 3081,
ManagerName: 3082,
CompanyMainPhone: 3083,
AccountName: 3084,
NickName: 3085,
MMS: 3086
}
};
}));

View file

@ -0,0 +1,22 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPDocumentLibrary = factory();
}(this, function () {
'use strict';
return {
Tags: {
LinkId: 4869,
DisplayName: 4870,
IsFolder: 4871,
CreationDate: 4872,
LastModifiedDate: 4873,
IsHidden: 4874,
ContentLength: 4875,
ContentType: 4876
}
};
}));

View file

@ -0,0 +1,111 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPEmail = factory();
}(this, function () {
'use strict';
return {
Tags: {
Attachment: 517,
Attachments: 518,
AttName: 519,
AttSize: 520,
Att0Id: 521,
AttMethod: 522,
AttRemoved: 523,
Body: 524,
BodySize: 525,
BodyTruncated: 526,
DateReceived: 527,
DisplayName: 528,
DisplayTo: 529,
Importance: 530,
MessageClass: 531,
Subject: 532,
Read: 533,
To: 534,
Cc: 535,
From: 536,
ReplyTo: 537,
AllDayEvent: 538,
Categories: 539,
Category: 540,
DTStamp: 541,
EndTime: 542,
InstanceType: 543,
BusyStatus: 544,
Location: 545,
MeetingRequest: 546,
Organizer: 547,
RecurrenceId: 548,
Reminder: 549,
ResponseRequested: 550,
Recurrences: 551,
Recurrence: 552,
Recurrence_Type: 553,
Recurrence_Until: 554,
Recurrence_Occurrences: 555,
Recurrence_Interval: 556,
Recurrence_DayOfWeek: 557,
Recurrence_DayOfMonth: 558,
Recurrence_WeekOfMonth: 559,
Recurrence_MonthOfYear: 560,
StartTime: 561,
Sensitivity: 562,
TimeZone: 563,
GlobalObjId: 564,
ThreadTopic: 565,
MIMEData: 566,
MIMETruncated: 567,
MIMESize: 568,
InternetCPID: 569,
Flag: 570,
Status: 571,
ContentClass: 572,
FlagType: 573,
CompleteTime: 574,
DisallowNewTimeProposal: 575
},
Enums: {
Importance: {
Low: '0',
Normal: '1',
High: '2'
},
InstanceType: {
Single: '0',
RecurringMaster: '1',
RecurringInstance: '2',
RecurringException: '3'
},
BusyStatus: {
Free: '0',
Tentative: '1',
Busy: '2',
Oof: '3'
},
Recurrence_Type: {
Daily: '0',
Weekly: '1',
MonthlyNthDay: '2',
Monthly: '3',
YearlyNthDay: '5',
YearlyNthDayOfWeek: '6'
},
Sensitivity: {
Normal: '0',
Personal: '1',
Private: '2',
Confidential: '3'
},
Status: {
Cleared: '0',
Complete: '1',
Active: '2'
}
}
};
}));

View file

@ -0,0 +1,73 @@
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPEmail2 = factory();
}(this, function () {
'use strict';
return {
Tags: {
UmCallerID: 5637,
UmUserNotes: 5638,
UmAttDuration: 5639,
UmAttOrder: 5640,
ConversationId: 5641,
ConversationIndex: 5642,
LastVerbExecuted: 5643,
LastVerbExecutionTime: 5644,
ReceivedAsBcc: 5645,
Sender: 5646,
CalendarType: 5647,
IsLeapMonth: 5648,
AccountId: 5649,
FirstDayOfWeek: 5650,
MeetingMessageType: 5651
},
Enums: {
LastVerbExecuted: {
Unknown: '0',
ReplyToSender: '1',
ReplyToAll: '2',
Forward: '3'
},
CalendarType: {
Default: '0',
Gregorian: '1',
GregorianUS: '2',
Japan: '3',
Taiwan: '4',
Korea: '5',
Hijri: '6',
Thai: '7',
Hebrew: '8',
GregorianMeFrench: '9',
GregorianArabic: '10',
GregorianTranslatedEnglish: '11',
GregorianTranslatedFrench: '12',
JapaneseLunar: '14',
ChineseLunar: '15',
KoreanLunar: '20'
},
FirstDayOfWeek: {
Sunday: '0',
Monday: '1',
Tuesday: '2',
Wednesday: '3',
Thursday: '4',
Friday: '5',
Saturday: '6'
},
MeetingMessageType: {
Unspecified: '0',
InitialRequest: '1',
FullUpdate: '2',
InformationalUpdate: '3',
Outdated: '4',
DelegatorsCopy: '5',
Delegated: '6'
}
}
};
}));

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