VulpesOS/gaia/apps/email/js/mail_app.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

297 lines
9.4 KiB
JavaScript
Executable file

/**
* Application logic that isn't specific to cards, specifically entailing
* startup and mozSetMessageHandler message listening.
**/
/*global globalOnAppMessage */
'use strict';
define(function(require, exports, module) {
var mozL10n = require('l10n!'),
activityComposerData = require('activity_composer_data'),
cards = require('cards'),
evt = require('evt'),
model = require('model_create').defaultModel,
HeaderCursor = require('header_cursor'),
htmlCache = require('html_cache'),
waitingRawActivity, activityCallback;
require('font_size_utils');
require('metrics');
require('wake_locks');
var started = false;
function pushStartCard(id, addedArgs) {
var args = {
model: model
};
// Mix in addedArgs to the args object that is passed to pushCard. Use a new
// object in case addedArgs is reused again by the caller.
if (addedArgs) {
Object.keys(addedArgs).forEach(function(key) {
args[key] = addedArgs[key];
});
}
if (!started) {
var cachedNode = cards._cardsNode.children[0];
// Add in cached node to use, if it matches the ID type.
if (cachedNode && id === htmlCache.nodeToKey(cachedNode)) {
// l10n may not see this as it was injected before l10n.js was loaded,
// so let it know it needs to translate it.
mozL10n.translateFragment(cachedNode);
args.cachedNode = cachedNode;
}
//Set body class to a solid background, see bug 1077605.
document.body.classList.add('content-visible');
}
cards.pushCard(id, 'immediate', args);
started = true;
}
// Handles visibility changes: if the app becomes visible after starting up
// hidden because of an alarm, start showing some UI.
document.addEventListener('visibilitychange', function onVisibilityChange() {
if (!document.hidden && !started &&
startupData && startupData.entry === 'alarm') {
pushStartCard('message_list');
}
}, false);
/*
* Determines if current card is a nonsearch message_list
* card, which is the default kind of card.
*/
function isCurrentCardMessageList() {
var cardType = cards.getCurrentCardType();
return (cardType && cardType === 'message_list');
}
// The add account UI flow is requested.
evt.on('addAccount', function() {
cards.removeAllCards();
// Show the first setup card again.
pushStartCard('setup_account_info', {
allowBack: true
});
});
function resetApp() {
// Clear any existing local state and reset UI/model state.
activityCallback = waitingRawActivity = undefined;
cards.removeAllCards();
model.init(false, function() {
var cardId = model.hasAccount() ?
'message_list' : 'setup_account_info';
pushStartCard(cardId);
});
}
// An account was deleted. Burn it all to the ground and rise like a phoenix.
// Prefer a UI event vs. a slice listen to give flexibility about UI
// construction: an acctsSlice splice change may not warrant removing all the
// cards.
evt.on('accountDeleted', resetApp);
evt.on('resetApp', resetApp);
// Called when account creation canceled, most likely from setup_account_info.
// Need to complete the activity postError flow if an activity is waiting, then
// update the UI to the latest state.
evt.on('setupAccountCanceled', function(fromCard) {
if (waitingRawActivity) {
waitingRawActivity.postError('cancelled');
}
if (!model.foldersSlice) {
// No account has been formally initialized, but one likely exists given
// that this back button should only be available for cases that have
// accounts. Likely just need the app to reset to load model.
evt.emit('resetApp');
} else {
cards.removeCardAndSuccessors(fromCard, 'animate', 1);
}
});
// A request to show the latest account in the UI. Usually triggered after an
// account has been added.
evt.on('showLatestAccount', function() {
cards.removeAllCards();
model.latestOnce('acctsSlice', function(acctsSlice) {
var account = acctsSlice.items[acctsSlice.items.length - 1];
model.changeAccount(account, function() {
pushStartCard('message_list', {
// If waiting to complete an activity, do so after pushing the message
// list card.
onPushed: function() {
if (activityCallback) {
var activityCb = activityCallback;
activityCallback = null;
activityCb();
return true;
}
return false;
}
});
});
});
});
evt.on('apiBadLogin', function(account, problem, whichSide) {
switch (problem) {
case 'bad-user-or-pass':
cards.pushCard('setup_fix_password', 'animate',
{ account: account,
whichSide: whichSide,
restoreCard: cards.activeCardIndex },
'right');
break;
case 'imap-disabled':
case 'pop3-disabled':
cards.pushCard('setup_fix_gmail', 'animate',
{ account: account, restoreCard: cards.activeCardIndex },
'right');
break;
case 'needs-app-pass':
cards.pushCard('setup_fix_gmail_twofactor', 'animate',
{ account: account, restoreCard: cards.activeCardIndex },
'right');
break;
case 'needs-oauth-reauth':
cards.pushCard('setup_fix_oauth2', 'animate',
{ account: account, restoreCard: cards.activeCardIndex },
'right');
break;
}
});
// Start init of main view/model modules now that all the registrations for
// top level events have happened, and before triggering of entry points start.
cards.init();
// If config could have already started up the model if there was no cache set
// up, so only trigger init if it is not already started up, for efficiency.
if (!model.inited) {
model.init();
}
/**
* Register setMozMessageHandler listeners with the plumbing set up in
* html_cache_restore
*/
var startupData = globalOnAppMessage({
activity: function(rawActivity) {
// Remove previous cards because the card stack could get weird if inserting
// a new card that would not normally be at that stack level. Primary
// concern: going to settings, then trying to add a compose card at that
// stack level. More importantly, the added card could have a "back"
// operation that does not mean "back to previous state", but "back in
// application flowchart". Message list is a good known jump point, so do
// not needlessly wipe that one out if it is the current one.
if (!isCurrentCardMessageList()) {
cards.removeAllCards();
}
function activityCompose() {
var cardArgs = {
activity: rawActivity,
composerData: activityComposerData(rawActivity)
};
pushStartCard('compose', cardArgs);
}
if (globalOnAppMessage.hasAccount()) {
activityCompose();
} else {
activityCallback = activityCompose;
waitingRawActivity = rawActivity;
pushStartCard('setup_account_info', {
allowBack: true,
launchedFromActivity: true
});
}
},
notification: function(data) {
data = data || {};
var type = data.type || '';
var folderType = data.folderType || 'inbox';
model.latestOnce('foldersSlice', function latestFolderSlice() {
function onCorrectFolder() {
// Remove previous cards because the card stack could get weird if
// inserting a new card that would not normally be at that stack level.
// Primary concern: going to settings, then trying to add a reader or
// message list card at that stack level. More importantly, the added
// card could have a "back" operation that does not mean "back to
// previous state", but "back in application flowchart". Message list is
// a good known jump point, so do not needlessly wipe that one out if it
// is the current one.
if (!isCurrentCardMessageList()) {
cards.removeAllCards();
}
if (type === 'message_list') {
pushStartCard('message_list', {});
} else if (type === 'message_reader') {
var headerCursor = new HeaderCursor(model);
headerCursor.setCurrentMessageBySuid(data.messageSuid);
pushStartCard(type, {
messageSuid: data.messageSuid,
headerCursor: headerCursor
});
} else {
console.error('unhandled notification type: ' + type);
}
}
var acctsSlice = model.acctsSlice,
accountId = data.accountId;
if (model.account.id === accountId) {
// folderType will often be 'inbox' (in the case of a new message
// notification) or 'outbox' (in the case of a "failed send"
// notification).
return model.selectFirstFolderWithType(folderType, onCorrectFolder);
} else {
var newAccount;
acctsSlice.items.some(function(account) {
if (account.id === accountId) {
newAccount = account;
return true;
}
});
if (newAccount) {
model.changeAccount(newAccount, function() {
model.selectFirstFolderWithType(folderType, onCorrectFolder);
});
}
}
});
}
});
console.log('startupData: ' + JSON.stringify(startupData, null, ' '));
// If not a mozSetMessageHandler entry point, start up the UI now. Or, if
// an alarm started the app, but the app became visible during the
// startup. In that case, make sure we show something to the user.
if (startupData.entry === 'default' ||
(startupData.entry === 'alarm' && !document.hidden)) {
pushStartCard(startupData.view);
}
});