isValidAddress: function(address) { // An address is valid if model.api.parseMailbox thinks it // contains a valid address. (It correctly classifies names that // are not valid addresses.) var mailbox = model.api.parseMailbox(address); return mailbox && mailbox.address; },
createdCallback: function() { this.bindContainerHandler(this.foldersContainer, 'click', this.onClickFolder.bind(this)); this.updateAccount = this.updateAccount.bind(this); model.latest('account', this.updateAccount); this.bindContainerHandler(this.accountListContainer, 'click', this.onClickAccount.bind(this)); this.addEventListener('transitionend', this.onTransitionEnd.bind(this)); // If more than one account, need to show the account dropdown var accountCount = model.getAccountCount(); if (accountCount > 1) { this.classList.remove('one-account'); // Set up size needed to handle translation animation for showing // accounts later. 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); },
onAddressInput: function(evt) { var node = evt.target; var container = evt.target.parentNode; if (this.isEmptyAddress()) { this.sendButton.setAttribute('aria-disabled', 'true'); return; } this.sendButton.setAttribute('aria-disabled', 'false'); var makeBubble = false; // When do we want to tie off this e-mail address, put it into a bubble // and clear the input box so the user can type another address? switch (node.value.slice(-1)) { // If they hit space and we believe they've already typed an email // address! (Space is okay in a display name or to delimit a display // name from the e-mail address) // // We use the presence of an '@' character as indicating that the e-mail // address case ' ': makeBubble = node.value.indexOf('@') !== -1; break; // We started out supporting comma, but now it's not on our keyboard at // all in type=email mode! We aren't terribly concerned about it not // being usable in display names, although we really should check for // quoting... case ',': // Semicolon is on the keyboard, and we also don't care about it not // being usable in display names. case ';': makeBubble = true; break; } if (makeBubble) { // TODO: Need to match the email with contact name. node.style.width = '0.5rem'; var mailbox = model.api.parseMailbox(node.value); this.insertBubble(node, mailbox.name, mailbox.address); node.value = ''; } // XXX: Workaround to get the length of the string. Here we create a dummy // div for computing actual string size for changing input // size dynamically. if (!this.stringContainer) { this.stringContainer = document.createElement('div'); this.domNode.appendChild(this.stringContainer); var inputStyle = window.getComputedStyle(node); this.stringContainer.style.fontSize = inputStyle.fontSize; } this.stringContainer.style.display = 'inline-block'; this.stringContainer.textContent = node.value; node.style.width = (this.stringContainer.clientWidth + 2) + 'px'; },
model.latestOnce('folder', function(folder) { this.composer = model.api.beginMessageComposition(data.message, folder, data.options, function() { if (data.onComposer) data.onComposer(this.composer); this._loadStateFromComposer(); }.bind(this)); }.bind(this));
function FolderPickerCard(domNode, mode, args) { this.domNode = domNode; this.foldersContainer = domNode.getElementsByClassName('fld-folders-container')[0]; bindContainerHandler(this.foldersContainer, 'click', this.onClickFolder.bind(this)); domNode.getElementsByClassName('fld-nav-toolbar')[0] .addEventListener('click', this.onShowSettings.bind(this), false); domNode.getElementsByClassName('fld-header-back')[0] .addEventListener('click', this._closeCard.bind(this), false); domNode.getElementsByClassName('fld-shield')[0] .addEventListener('click', this._closeCard.bind(this), false); this.accountHeader = domNode.getElementsByClassName('fld-acct-header')[0]; this.accountHeader.addEventListener('click', this.toggleAccounts.bind(this), false); this.updateAccount = this.updateAccount.bind(this); model.latest('account', this.updateAccount); this.fldAcctScrollInner = domNode.getElementsByClassName('fld-acct-scrollinner')[0]; this.fldAcctContainer = domNode.getElementsByClassName('fld-acct-container')[0]; this.accountListContainer = domNode.getElementsByClassName('fld-accountlist-container')[0]; bindContainerHandler(this.accountListContainer, 'click', this.onClickAccount.bind(this)); this.domNode.addEventListener('transitionend', this.onTransitionEnd.bind(this)); // If more than one account, need to show the account dropdown var accountCount = model.getAccountCount(); if (accountCount > 1) { removeClass(this.domNode, 'one-account'); // Set up size needed to handle translation animation for showing // accounts later. 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); }
function frobAddressNode(node) { var container = node.parentNode; var addrList = []; var bubbles = container.querySelectorAll('.cmp-peep-bubble'); for (var i = 0; i < bubbles.length; i++) { var dataSet = bubbles[i].dataset; addrList.push({ name: dataSet.name, address: dataSet.address }); } if (node.value.trim().length !== 0) { var mailbox = model.api.parseMailbox(node.value); addrList.push({ name: mailbox.name, address: mailbox.address }); } return addrList; }
showFolder: function(folder, forceNewSlice) { if (folder === this.curFolder && !forceNewSlice) return false; if (this.messagesSlice) { this.messagesSlice.die(); this.messagesSlice = null; this.messagesContainer.innerHTML = ''; } this.curFolder = folder; switch (folder.type) { case 'drafts': case 'localdrafts': case 'sent': this.isIncomingFolder = false; break; default: this.isIncomingFolder = true; break; } this.domNode.getElementsByClassName('msg-list-header-folder-label')[0] .textContent = folder.name; this.hideEmptyLayout(); // you can't refresh the localdrafts folder or move messages out of it. if (folder.type === 'localdrafts') { this.toolbar.refreshBtn.classList.add('collapsed'); this.toolbar.moveBtn.classList.add('collapsed'); } else { this.toolbar.refreshBtn.classList.remove('collapsed'); this.toolbar.moveBtn.classList.remove('collapsed'); } // We are creating a new slice, so any pending snippet requests are moot. this._snippetRequestPending = false; this.messagesSlice = model.api.viewFolderMessages(folder); this.messagesSlice.onsplice = this.onMessagesSplice.bind(this); this.messagesSlice.onchange = this.onMessagesChange.bind(this); this.messagesSlice.onstatus = this.onStatusChange.bind(this); this.messagesSlice.oncomplete = this._boundSliceRequestComplete; return true; },
model.latestOnce('folder', function(folder) { var messagesSlice = model.api.viewFolderMessages(folder); function clear() { messagesSlice.die(); messagesSlice = null; } messagesSlice.onsplice = (function(index, howMany, addedItems, requested, moreExpected) { // Avoid doing work if get called while in the process of // shutting down. if (!messagesSlice) return; if (!this.header && addedItems && addedItems.length) { addedItems.some(function(item) { if (item.id === this.messageSuid) { this._setHeader(item); clear(); return true; } }.bind(this)); // If at the top, and no message was found, then if the UI // wants to go back on missing message, do that now. This // card may have been created from obsolete data, like an // old notification for a message that no longer exists. // This stops atTop since the most likely case for this // entry point is either clicking on a message that is // at the top of the inbox in the HTML cache, or from a // notification for a new message, which would be near // the top. if (messagesSlice && messagesSlice.atTop && !this.header && args.backOnMissingMessage) { clear(); this.onBack(); } } }).bind(this); }.bind(this));
var frobAddressNode = (function(node) { var bubbles = node.parentNode.querySelectorAll('.cmp-peep-bubble'); var addrList = []; for (var i = 0; i < bubbles.length; i++) { var dataSet = bubbles[i].dataset; addrList.push({ name: dataSet.name, address: dataSet.address }); } if (node.value.trim().length !== 0) { var mailbox = model.api.parseMailbox(node.value); addrList.push({ name: mailbox.name, address: mailbox.address }); } addrList.forEach(function(addr) { allAddresses.push(addr); if (!this.isValidAddress(addr.address)) { invalidAddresses.push(addr); } }.bind(this)); return addrList; }.bind(this));
showSearch: function(folder, phrase, filter) { console.log('sf: showSearch. phrase:', phrase, phrase.length); var tab = this.domNode.getElementsByClassName('filter')[0]; var nodes = tab.getElementsByClassName('msg-search-filter'); if (this.messagesSlice) { this.messagesSlice.die(); this.messagesSlice = null; this.messagesContainer.innerHTML = ''; } this.curFolder = folder; this.curPhrase = phrase; this.curFilter = filter; for (var i = 0; i < nodes.length; i++) { if (nodes[i].dataset.filter != this.curFilter) { nodes[i].setAttribute('aria-selected', 'false'); continue; } nodes[i].setAttribute('aria-selected', 'true'); } if (phrase.length < 1) return false; // We are creating a new slice, so any pending snippet requests are moot. this._snippetRequestPending = false; this.messagesSlice = model.api.searchFolderMessages( folder, phrase, { author: filter === 'all' || filter === 'author', recipients: filter === 'all' || filter === 'recipients', subject: filter === 'all' || filter === 'subject', body: filter === 'all' || filter === 'body' }); this.messagesSlice.onsplice = this.onMessagesSplice.bind(this); this.messagesSlice.onchange = this.updateMatchedMessageDom.bind(this, false); this.messagesSlice.onstatus = this.onStatusChange.bind(this); this.messagesSlice.oncomplete = this._boundSliceRequestComplete; return true; },
Cards.folderSelector(function(folder) { var op = model.api.moveMessages(this.selectedMessages, folder); Toaster.logMutation(op); this.setEditMode(false); }.bind(this));
handler: function() { var op = model.api.deleteMessages(this.selectedMessages); Toaster.logMutation(op); this.setEditMode(false); }.bind(this)
onMarkMessagesRead: function() { var op = model.api.markMessagesRead(this.selectedMessages, this.setAsRead); this.setEditMode(false); Toaster.logMutation(op); },
onStarMessages: function() { var op = model.api.markMessagesStarred(this.selectedMessages, this.setAsStarred); this.setEditMode(false); Toaster.logMutation(op); },
freshMessagesSlice: function() { this.bindToSlice(model.api.viewFolderMessages(model.folder)); },
startSearch: function(phrase, whatToSearch) { this.bindToSlice(model.api.searchFolderMessages(model.folder, phrase, whatToSearch)); },