зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1048162 Part 1 - Add an 'Email Link' button to Loop desktop failed call view. r=Standard8
This commit is contained in:
Родитель
86289da8b5
Коммит
9b7f6776df
|
@ -601,15 +601,18 @@ function injectLoopAPI(targetWindow) {
|
||||||
/**
|
/**
|
||||||
* Composes an email via the external protocol service.
|
* Composes an email via the external protocol service.
|
||||||
*
|
*
|
||||||
* @param {String} subject Subject of the email to send
|
* @param {String} subject Subject of the email to send
|
||||||
* @param {String} body Body message of the email to send
|
* @param {String} body Body message of the email to send
|
||||||
|
* @param {String} recipient Recipient email address (optional)
|
||||||
*/
|
*/
|
||||||
composeEmail: {
|
composeEmail: {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
writable: true,
|
writable: true,
|
||||||
value: function(subject, body) {
|
value: function(subject, body, recipient) {
|
||||||
let mailtoURL = "mailto:?subject=" + encodeURIComponent(subject) + "&" +
|
recipient = recipient || "";
|
||||||
"body=" + encodeURIComponent(body);
|
let mailtoURL = "mailto:" + encodeURIComponent(recipient) +
|
||||||
|
"?subject=" + encodeURIComponent(subject) +
|
||||||
|
"&body=" + encodeURIComponent(body);
|
||||||
extProtocolSvc.loadURI(CommonUtils.makeURI(mailtoURL));
|
extProtocolSvc.loadURI(CommonUtils.makeURI(mailtoURL));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,8 +12,20 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
var CALL_STATES = loop.store.CALL_STATES;
|
var CALL_STATES = loop.store.CALL_STATES;
|
||||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sharedUtils = loop.shared.utils;
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
|
|
||||||
|
// This duplicates a similar function in contacts.jsx that isn't used in the
|
||||||
|
// conversation window. If we get too many of these, we might want to consider
|
||||||
|
// finding a logical place for them to be shared.
|
||||||
|
function _getPreferredEmail(contact) {
|
||||||
|
// A contact may not contain email addresses, but only a phone number.
|
||||||
|
if (!contact.email || contact.email.length === 0) {
|
||||||
|
return { value: "" };
|
||||||
|
}
|
||||||
|
return contact.email.find(e => e.pref) || contact.email[0];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays information about the call
|
* Displays information about the call
|
||||||
* Caller avatar, name & conversation creation date
|
* Caller avatar, name & conversation creation date
|
||||||
|
@ -93,17 +105,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
contact: React.PropTypes.object
|
contact: React.PropTypes.object
|
||||||
},
|
},
|
||||||
|
|
||||||
// This duplicates a similar function in contacts.jsx that isn't used in the
|
|
||||||
// conversation window. If we get too many of these, we might want to consider
|
|
||||||
// finding a logical place for them to be shared.
|
|
||||||
_getPreferredEmail: function(contact) {
|
|
||||||
// A contact may not contain email addresses, but only a phone number.
|
|
||||||
if (!contact.email || contact.email.length == 0) {
|
|
||||||
return { value: "" };
|
|
||||||
}
|
|
||||||
return contact.email.find(e => e.pref) || contact.email[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var contactName;
|
var contactName;
|
||||||
|
|
||||||
|
@ -111,7 +112,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
this.props.contact.name[0]) {
|
this.props.contact.name[0]) {
|
||||||
contactName = this.props.contact.name[0];
|
contactName = this.props.contact.name[0];
|
||||||
} else {
|
} else {
|
||||||
contactName = this._getPreferredEmail(this.props.contact).value;
|
contactName = _getPreferredEmail(this.props.contact).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.title = contactName;
|
document.title = contactName;
|
||||||
|
@ -187,8 +188,33 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
* Call failed view. Displayed when a call fails.
|
* Call failed view. Displayed when a call fails.
|
||||||
*/
|
*/
|
||||||
var CallFailedView = React.createClass({displayName: 'CallFailedView',
|
var CallFailedView = React.createClass({displayName: 'CallFailedView',
|
||||||
|
mixins: [Backbone.Events],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
store: React.PropTypes.instanceOf(
|
||||||
|
loop.store.ConversationStore).isRequired,
|
||||||
|
contact: React.PropTypes.object.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {emailLinkButtonDisabled: false};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.listenTo(this.props.store, "change:emailLink",
|
||||||
|
this._onEmailLinkReceived);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this.stopListening(this.props.store);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onEmailLinkReceived: function() {
|
||||||
|
var emailLink = this.props.store.get("emailLink");
|
||||||
|
var contactEmail = _getPreferredEmail(this.props.contact).value;
|
||||||
|
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
|
||||||
|
window.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
retryCall: function() {
|
retryCall: function() {
|
||||||
|
@ -199,25 +225,33 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
emailLink: function() {
|
||||||
|
this.setState({emailLinkButtonDisabled: true});
|
||||||
|
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
React.DOM.div({className: "call-window"},
|
React.DOM.div({className: "call-window"},
|
||||||
React.DOM.h2(null, mozL10n.get("generic_failure_title")),
|
React.DOM.h2(null, mozL10n.get("generic_failure_title")),
|
||||||
|
|
||||||
React.DOM.p({className: "btn-label"}, mozL10n.get("generic_failure_no_reason2")),
|
React.DOM.p({className: "btn-label"}, mozL10n.get("generic_failure_with_reason2")),
|
||||||
|
|
||||||
React.DOM.div({className: "btn-group call-action-group"},
|
React.DOM.div({className: "btn-group call-action-group"},
|
||||||
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
React.DOM.button({className: "btn btn-cancel",
|
||||||
React.DOM.button({className: "btn btn-accept btn-retry",
|
onClick: this.cancelCall},
|
||||||
onClick: this.retryCall},
|
mozL10n.get("cancel_button")
|
||||||
mozL10n.get("retry_call_button")
|
),
|
||||||
),
|
React.DOM.button({className: "btn btn-info btn-retry",
|
||||||
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
onClick: this.retryCall},
|
||||||
React.DOM.button({className: "btn btn-cancel",
|
mozL10n.get("retry_call_button")
|
||||||
onClick: this.cancelCall},
|
),
|
||||||
mozL10n.get("cancel_button")
|
React.DOM.button({className: "btn btn-info btn-email",
|
||||||
),
|
onClick: this.emailLink,
|
||||||
React.DOM.div({className: "fx-embedded-call-button-spacer"})
|
disabled: this.state.emailLinkButtonDisabled},
|
||||||
|
mozL10n.get("share_button2")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -425,7 +459,9 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
}
|
}
|
||||||
case CALL_STATES.TERMINATED: {
|
case CALL_STATES.TERMINATED: {
|
||||||
return (CallFailedView({
|
return (CallFailedView({
|
||||||
dispatcher: this.props.dispatcher}
|
dispatcher: this.props.dispatcher,
|
||||||
|
store: this.props.store,
|
||||||
|
contact: this.state.contact}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
case CALL_STATES.ONGOING: {
|
case CALL_STATES.ONGOING: {
|
||||||
|
@ -445,7 +481,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
callState: this.state.callState,
|
callState: this.state.callState,
|
||||||
contact: this.state.contact,
|
contact: this.state.contact,
|
||||||
enableCancelButton: this._isCancellable()}
|
enableCancelButton: this._isCancellable()}
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,8 +12,20 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
var CALL_STATES = loop.store.CALL_STATES;
|
var CALL_STATES = loop.store.CALL_STATES;
|
||||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sharedUtils = loop.shared.utils;
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
|
|
||||||
|
// This duplicates a similar function in contacts.jsx that isn't used in the
|
||||||
|
// conversation window. If we get too many of these, we might want to consider
|
||||||
|
// finding a logical place for them to be shared.
|
||||||
|
function _getPreferredEmail(contact) {
|
||||||
|
// A contact may not contain email addresses, but only a phone number.
|
||||||
|
if (!contact.email || contact.email.length === 0) {
|
||||||
|
return { value: "" };
|
||||||
|
}
|
||||||
|
return contact.email.find(e => e.pref) || contact.email[0];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays information about the call
|
* Displays information about the call
|
||||||
* Caller avatar, name & conversation creation date
|
* Caller avatar, name & conversation creation date
|
||||||
|
@ -93,17 +105,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
contact: React.PropTypes.object
|
contact: React.PropTypes.object
|
||||||
},
|
},
|
||||||
|
|
||||||
// This duplicates a similar function in contacts.jsx that isn't used in the
|
|
||||||
// conversation window. If we get too many of these, we might want to consider
|
|
||||||
// finding a logical place for them to be shared.
|
|
||||||
_getPreferredEmail: function(contact) {
|
|
||||||
// A contact may not contain email addresses, but only a phone number.
|
|
||||||
if (!contact.email || contact.email.length == 0) {
|
|
||||||
return { value: "" };
|
|
||||||
}
|
|
||||||
return contact.email.find(e => e.pref) || contact.email[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var contactName;
|
var contactName;
|
||||||
|
|
||||||
|
@ -111,7 +112,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
this.props.contact.name[0]) {
|
this.props.contact.name[0]) {
|
||||||
contactName = this.props.contact.name[0];
|
contactName = this.props.contact.name[0];
|
||||||
} else {
|
} else {
|
||||||
contactName = this._getPreferredEmail(this.props.contact).value;
|
contactName = _getPreferredEmail(this.props.contact).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.title = contactName;
|
document.title = contactName;
|
||||||
|
@ -187,8 +188,33 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
* Call failed view. Displayed when a call fails.
|
* Call failed view. Displayed when a call fails.
|
||||||
*/
|
*/
|
||||||
var CallFailedView = React.createClass({
|
var CallFailedView = React.createClass({
|
||||||
|
mixins: [Backbone.Events],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
store: React.PropTypes.instanceOf(
|
||||||
|
loop.store.ConversationStore).isRequired,
|
||||||
|
contact: React.PropTypes.object.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {emailLinkButtonDisabled: false};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.listenTo(this.props.store, "change:emailLink",
|
||||||
|
this._onEmailLinkReceived);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this.stopListening(this.props.store);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onEmailLinkReceived: function() {
|
||||||
|
var emailLink = this.props.store.get("emailLink");
|
||||||
|
var contactEmail = _getPreferredEmail(this.props.contact).value;
|
||||||
|
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
|
||||||
|
window.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
retryCall: function() {
|
retryCall: function() {
|
||||||
|
@ -199,25 +225,33 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
emailLink: function() {
|
||||||
|
this.setState({emailLinkButtonDisabled: true});
|
||||||
|
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div className="call-window">
|
<div className="call-window">
|
||||||
<h2>{mozL10n.get("generic_failure_title")}</h2>
|
<h2>{mozL10n.get("generic_failure_title")}</h2>
|
||||||
|
|
||||||
<p className="btn-label">{mozL10n.get("generic_failure_no_reason2")}</p>
|
<p className="btn-label">{mozL10n.get("generic_failure_with_reason2")}</p>
|
||||||
|
|
||||||
<div className="btn-group call-action-group">
|
<div className="btn-group call-action-group">
|
||||||
<div className="fx-embedded-call-button-spacer"></div>
|
<button className="btn btn-cancel"
|
||||||
<button className="btn btn-accept btn-retry"
|
onClick={this.cancelCall}>
|
||||||
onClick={this.retryCall}>
|
{mozL10n.get("cancel_button")}
|
||||||
{mozL10n.get("retry_call_button")}
|
</button>
|
||||||
</button>
|
<button className="btn btn-info btn-retry"
|
||||||
<div className="fx-embedded-call-button-spacer"></div>
|
onClick={this.retryCall}>
|
||||||
<button className="btn btn-cancel"
|
{mozL10n.get("retry_call_button")}
|
||||||
onClick={this.cancelCall}>
|
</button>
|
||||||
{mozL10n.get("cancel_button")}
|
<button className="btn btn-info btn-email"
|
||||||
</button>
|
onClick={this.emailLink}
|
||||||
<div className="fx-embedded-call-button-spacer"></div>
|
disabled={this.state.emailLinkButtonDisabled}>
|
||||||
|
{mozL10n.get("share_button2")}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -426,6 +460,8 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
case CALL_STATES.TERMINATED: {
|
case CALL_STATES.TERMINATED: {
|
||||||
return (<CallFailedView
|
return (<CallFailedView
|
||||||
dispatcher={this.props.dispatcher}
|
dispatcher={this.props.dispatcher}
|
||||||
|
store={this.props.store}
|
||||||
|
contact={this.state.contact}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
case CALL_STATES.ONGOING: {
|
case CALL_STATES.ONGOING: {
|
||||||
|
@ -445,7 +481,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
callState={this.state.callState}
|
callState={this.state.callState}
|
||||||
contact={this.state.contact}
|
contact={this.state.contact}
|
||||||
enableCancelButton={this._isCancellable()}
|
enableCancelButton={this._isCancellable()}
|
||||||
/>)
|
/>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,6 +15,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var sharedModels = loop.shared.models;
|
var sharedModels = loop.shared.models;
|
||||||
var sharedMixins = loop.shared.mixins;
|
var sharedMixins = loop.shared.mixins;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sharedUtils = loop.shared.utils;
|
||||||
var Button = sharedViews.Button;
|
var Button = sharedViews.Button;
|
||||||
var ButtonGroup = sharedViews.ButtonGroup;
|
var ButtonGroup = sharedViews.ButtonGroup;
|
||||||
var ContactsList = loop.contacts.ContactsList;
|
var ContactsList = loop.contacts.ContactsList;
|
||||||
|
@ -362,11 +363,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
handleEmailButtonClick: function(event) {
|
handleEmailButtonClick: function(event) {
|
||||||
this.handleLinkExfiltration(event);
|
this.handleLinkExfiltration(event);
|
||||||
|
|
||||||
navigator.mozLoop.composeEmail(
|
sharedUtils.composeCallUrlEmail(this.state.callUrl);
|
||||||
__("share_email_subject4", { clientShortname: __("clientShortname2")}),
|
|
||||||
__("share_email_body4", { callUrl: this.state.callUrl,
|
|
||||||
clientShortname: __("clientShortname2"),
|
|
||||||
learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl") }));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleCopyButtonClick: function(event) {
|
handleCopyButtonClick: function(event) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var sharedModels = loop.shared.models;
|
var sharedModels = loop.shared.models;
|
||||||
var sharedMixins = loop.shared.mixins;
|
var sharedMixins = loop.shared.mixins;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sharedUtils = loop.shared.utils;
|
||||||
var Button = sharedViews.Button;
|
var Button = sharedViews.Button;
|
||||||
var ButtonGroup = sharedViews.ButtonGroup;
|
var ButtonGroup = sharedViews.ButtonGroup;
|
||||||
var ContactsList = loop.contacts.ContactsList;
|
var ContactsList = loop.contacts.ContactsList;
|
||||||
|
@ -362,11 +363,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
handleEmailButtonClick: function(event) {
|
handleEmailButtonClick: function(event) {
|
||||||
this.handleLinkExfiltration(event);
|
this.handleLinkExfiltration(event);
|
||||||
|
|
||||||
navigator.mozLoop.composeEmail(
|
sharedUtils.composeCallUrlEmail(this.state.callUrl);
|
||||||
__("share_email_subject4", { clientShortname: __("clientShortname2")}),
|
|
||||||
__("share_email_body4", { callUrl: this.state.callUrl,
|
|
||||||
clientShortname: __("clientShortname2"),
|
|
||||||
learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl") }));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleCopyButtonClick: function(event) {
|
handleCopyButtonClick: function(event) {
|
||||||
|
|
|
@ -240,16 +240,21 @@
|
||||||
min-height: 230px;
|
min-height: 230px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.call-window > .btn-label {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.call-action-group {
|
.call-action-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 2.5em 0 0 0;
|
padding: 2.5em 4px 0 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-around;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-action-group > .btn {
|
.call-action-group > .btn {
|
||||||
margin-left: .5em;
|
|
||||||
height: 26px;
|
height: 26px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 0 4px;
|
||||||
|
min-width: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-action-group .btn-group-chevron,
|
.call-action-group .btn-group-chevron,
|
||||||
|
|
|
@ -30,6 +30,13 @@ loop.shared.actions = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Fetch a new call url from the server, intended to be sent over email when
|
||||||
|
* a contact can't be reached.
|
||||||
|
*/
|
||||||
|
FetchEmailLink: Action.define("fetchEmailLink", {
|
||||||
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to trigger gathering of initial call data.
|
* Used to trigger gathering of initial call data.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -126,7 +126,8 @@ loop.store.ConversationStore = (function() {
|
||||||
"cancelCall",
|
"cancelCall",
|
||||||
"retryCall",
|
"retryCall",
|
||||||
"mediaConnected",
|
"mediaConnected",
|
||||||
"setMute"
|
"setMute",
|
||||||
|
"fetchEmailLink"
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -303,6 +304,23 @@ loop.store.ConversationStore = (function() {
|
||||||
this.set(muteType, !actionData.enabled);
|
this.set(muteType, !actionData.enabled);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a new call URL intended to be sent over email when a contact
|
||||||
|
* can't be reached.
|
||||||
|
*/
|
||||||
|
fetchEmailLink: function() {
|
||||||
|
// XXX This is an empty string as a conversation identifier. Bug 1015938 implements
|
||||||
|
// a user-set string.
|
||||||
|
this.client.requestCallUrl("", function(err, callUrlData) {
|
||||||
|
if (err) {
|
||||||
|
// XXX better error reporting in the UI
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.set("emailLink", callUrlData.callUrl);
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the outgoing call data from the server and handles the
|
* Obtains the outgoing call data from the server and handles the
|
||||||
* result.
|
* result.
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.shared = loop.shared || {};
|
loop.shared = loop.shared || {};
|
||||||
loop.shared.utils = (function() {
|
loop.shared.utils = (function(mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,11 +96,37 @@ loop.shared.utils = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates and opens a mailto: url with call URL information prefilled.
|
||||||
|
* Note: This only works for Desktop.
|
||||||
|
*
|
||||||
|
* @param {String} callUrl The call URL.
|
||||||
|
* @param {String} recipient The recipient email address (optional).
|
||||||
|
*/
|
||||||
|
function composeCallUrlEmail(callUrl, recipient) {
|
||||||
|
if (typeof navigator.mozLoop === "undefined") {
|
||||||
|
console.warn("composeCallUrlEmail isn't available for Loop standalone.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.mozLoop.composeEmail(
|
||||||
|
mozL10n.get("share_email_subject4", {
|
||||||
|
clientShortname: mozL10n.get("clientShortname2")
|
||||||
|
}),
|
||||||
|
mozL10n.get("share_email_body4", {
|
||||||
|
callUrl: callUrl,
|
||||||
|
clientShortname: mozL10n.get("clientShortname2"),
|
||||||
|
learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl")
|
||||||
|
}),
|
||||||
|
recipient
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
CALL_TYPES: CALL_TYPES,
|
CALL_TYPES: CALL_TYPES,
|
||||||
Helper: Helper,
|
Helper: Helper,
|
||||||
|
composeCallUrlEmail: composeCallUrlEmail,
|
||||||
formatDate: formatDate,
|
formatDate: formatDate,
|
||||||
getTargetPlatform: getTargetPlatform,
|
getTargetPlatform: getTargetPlatform,
|
||||||
getBoolPreference: getBoolPreference
|
getBoolPreference: getBoolPreference
|
||||||
};
|
};
|
||||||
})();
|
})(document.mozL10n || navigator.mozL10n);
|
||||||
|
|
|
@ -6,6 +6,7 @@ var expect = chai.expect;
|
||||||
describe("loop.conversationViews", function () {
|
describe("loop.conversationViews", function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var sharedUtils = loop.shared.utils;
|
||||||
var sandbox, oldTitle, view, dispatcher, contact;
|
var sandbox, oldTitle, view, dispatcher, contact;
|
||||||
|
|
||||||
var CALL_STATES = loop.store.CALL_STATES;
|
var CALL_STATES = loop.store.CALL_STATES;
|
||||||
|
@ -201,13 +202,25 @@ describe("loop.conversationViews", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("CallFailedView", function() {
|
describe("CallFailedView", function() {
|
||||||
|
var store;
|
||||||
|
|
||||||
function mountTestComponent(props) {
|
function mountTestComponent(props) {
|
||||||
return TestUtils.renderIntoDocument(
|
return TestUtils.renderIntoDocument(
|
||||||
loop.conversationViews.CallFailedView({
|
loop.conversationViews.CallFailedView({
|
||||||
dispatcher: dispatcher
|
dispatcher: dispatcher,
|
||||||
|
store: store,
|
||||||
|
contact: {email: [{value: "test@test.tld"}]}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
store = new loop.store.ConversationStore({}, {
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
client: {},
|
||||||
|
sdkDriver: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should dispatch a retryCall action when the retry button is pressed",
|
it("should dispatch a retryCall action when the retry button is pressed",
|
||||||
function() {
|
function() {
|
||||||
view = mountTestComponent();
|
view = mountTestComponent();
|
||||||
|
@ -233,6 +246,48 @@ describe("loop.conversationViews", function () {
|
||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
sinon.match.hasOwn("name", "cancelCall"));
|
sinon.match.hasOwn("name", "cancelCall"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should dispatch a fetchEmailLink action when the cancel button is pressed",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent();
|
||||||
|
|
||||||
|
var emailLinkBtn = view.getDOMNode().querySelector('.btn-email');
|
||||||
|
|
||||||
|
React.addons.TestUtils.Simulate.click(emailLinkBtn);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "fetchEmailLink"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should disable the email link button once the action is dispatched",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent();
|
||||||
|
var emailLinkBtn = view.getDOMNode().querySelector('.btn-email');
|
||||||
|
React.addons.TestUtils.Simulate.click(emailLinkBtn);
|
||||||
|
|
||||||
|
expect(view.getDOMNode().querySelector(".btn-email").disabled).eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should compose an email once the email link is received", function() {
|
||||||
|
var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail");
|
||||||
|
view = mountTestComponent();
|
||||||
|
store.set("emailLink", "http://fake.invalid/");
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(composeCallUrlEmail);
|
||||||
|
sinon.assert.calledWithExactly(composeCallUrlEmail,
|
||||||
|
"http://fake.invalid/", "test@test.tld");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should close the conversation window once the email link is received",
|
||||||
|
function() {
|
||||||
|
sandbox.stub(window, "close");
|
||||||
|
view = mountTestComponent();
|
||||||
|
|
||||||
|
store.set("emailLink", "http://fake.invalid/");
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(window.close);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("OngoingConversationView", function() {
|
describe("OngoingConversationView", function() {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
var TestUtils = React.addons.TestUtils;
|
var TestUtils = React.addons.TestUtils;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sharedUtils = loop.shared.utils;
|
||||||
|
|
||||||
describe("loop.panel", function() {
|
describe("loop.panel", function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
@ -449,6 +450,7 @@ describe("loop.panel", function() {
|
||||||
|
|
||||||
it("should display a share button for email", function() {
|
it("should display a share button for email", function() {
|
||||||
fakeClient.requestCallUrl = sandbox.stub();
|
fakeClient.requestCallUrl = sandbox.stub();
|
||||||
|
var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail");
|
||||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
client: fakeClient
|
client: fakeClient
|
||||||
|
@ -457,7 +459,9 @@ describe("loop.panel", function() {
|
||||||
|
|
||||||
TestUtils.findRenderedDOMComponentWithClass(view, "button-email");
|
TestUtils.findRenderedDOMComponentWithClass(view, "button-email");
|
||||||
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
|
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
|
||||||
sinon.assert.calledOnce(navigator.mozLoop.composeEmail);
|
|
||||||
|
sinon.assert.calledOnce(composeCallUrlEmail);
|
||||||
|
sinon.assert.calledWithExactly(composeCallUrlEmail, "http://example.com");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should feature a copy button capable of copying the call url when clicked", function() {
|
it("should feature a copy button capable of copying the call url when clicked", function() {
|
||||||
|
|
|
@ -38,7 +38,8 @@ describe("loop.store.ConversationStore", function () {
|
||||||
|
|
||||||
dispatcher = new loop.Dispatcher();
|
dispatcher = new loop.Dispatcher();
|
||||||
client = {
|
client = {
|
||||||
setupOutgoingCall: sinon.stub()
|
setupOutgoingCall: sinon.stub(),
|
||||||
|
requestCallUrl: sinon.stub()
|
||||||
};
|
};
|
||||||
sdkDriver = {
|
sdkDriver = {
|
||||||
connectSession: sinon.stub(),
|
connectSession: sinon.stub(),
|
||||||
|
@ -566,6 +567,28 @@ describe("loop.store.ConversationStore", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#fetchEmailLink", function() {
|
||||||
|
it("should request a new call url to the server", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(client.requestCallUrl);
|
||||||
|
sinon.assert.calledWith(client.requestCallUrl, "");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update the emailLink attribute when the new call url is received",
|
||||||
|
function() {
|
||||||
|
client.requestCallUrl = function(callId, cb) {
|
||||||
|
cb(null, {callUrl: "http://fake.invalid/"});
|
||||||
|
};
|
||||||
|
dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||||
|
|
||||||
|
expect(store.get("emailLink")).eql("http://fake.invalid/");
|
||||||
|
});
|
||||||
|
|
||||||
|
// XXX bug 1048162 Part 2
|
||||||
|
it.skip("should trigger an error in case of failure");
|
||||||
|
});
|
||||||
|
|
||||||
describe("Events", function() {
|
describe("Events", function() {
|
||||||
describe("Websocket progress", function() {
|
describe("Websocket progress", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
|
|
@ -18,6 +18,7 @@ describe("loop.shared.utils", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
navigator.mozLoop = undefined;
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -110,7 +111,6 @@ describe("loop.shared.utils", function() {
|
||||||
|
|
||||||
describe("#getBoolPreference", function() {
|
describe("#getBoolPreference", function() {
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
navigator.mozLoop = undefined;
|
|
||||||
localStorage.removeItem("test.true");
|
localStorage.removeItem("test.true");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -142,4 +142,31 @@ describe("loop.shared.utils", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#composeCallUrlEmail", function() {
|
||||||
|
var composeEmail;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
// fake mozL10n
|
||||||
|
sandbox.stub(navigator.mozL10n, "get", function(id) {
|
||||||
|
switch(id) {
|
||||||
|
case "share_email_subject4": return "subject";
|
||||||
|
case "share_email_body4": return "body";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
composeEmail = sandbox.spy();
|
||||||
|
navigator.mozLoop = {
|
||||||
|
getLoopCharPref: sandbox.spy(),
|
||||||
|
composeEmail: composeEmail
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should compose a call url email", function() {
|
||||||
|
sharedUtils.composeCallUrlEmail("http://invalid", "fake@invalid.tld");
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(composeEmail);
|
||||||
|
sinon.assert.calledWith(composeEmail,
|
||||||
|
"subject", "body", "fake@invalid.tld");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Загрузка…
Ссылка в новой задаче