Bug 1215593 - Relayout the room title and context tile in text chat. r=Standard8

This commit is contained in:
Chris Rafuse 2015-10-30 14:43:58 +00:00
parent 1757095da5
commit 53c0c97e3a
18 changed files with 82 additions and 179 deletions

View File

@ -815,7 +815,7 @@ loop.roomViews = (function(mozL10n) {
renderRemoteVideo: this.shouldRenderRemoteVideo(),
screenShareMediaElement: this.state.screenShareMediaElement,
screenSharePosterUrl: null,
showContextRoomName: false,
showInitialContext: false,
useDesktopPaths: true},
React.createElement(sharedViews.ConversationToolbar, {
audio: { enabled: !this.state.audioMuted, visible: true},

View File

@ -815,7 +815,7 @@ loop.roomViews = (function(mozL10n) {
renderRemoteVideo={this.shouldRenderRemoteVideo()}
screenShareMediaElement={this.state.screenShareMediaElement}
screenSharePosterUrl={null}
showContextRoomName={false}
showInitialContext={false}
useDesktopPaths={true}>
<sharedViews.ConversationToolbar
audio={{ enabled: !this.state.audioMuted, visible: true }}

View File

@ -545,14 +545,13 @@ html[dir="rtl"] .context-content {
}
.context-wrapper {
border: 1px solid #5cccee;
border: 2px solid #ebebeb;
border-radius: 4px;
background: #fff;
padding: .8em;
background: #fafafa;
padding: 1.1rem .8rem;
/* Use the flex row mode to position the elements next to each other. */
display: flex;
flex-flow: row nowrap;
line-height: 1.1em;
/* No underline for the text in the context view. */
text-decoration: none;
}
@ -590,9 +589,6 @@ html[dir="rtl"] .context-wrapper > .context-preview {
.clicks-allowed.context-wrapper:hover {
border: 2px solid #5cccee;
/* Due to the increased border width, reduce the padding accordingly so that
the text doesn't move. */
padding: calc(.8em - 1px);
}
/* Only underline the url, not the associated text */

View File

@ -529,14 +529,8 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
background-image: url("../img/icons-16x16.svg#add-active");
}
.context-url-view-wrapper {
padding: 14px 15px;
background-color: #dbf7ff;
}
.showing-room-name > .text-chat-entries > .text-chat-scroller > .context-url-view-wrapper {
padding-top: 0;
margin-bottom: 0;
.context-url-view-wrapper > .context-content {
margin: 0 1rem 1.5rem 1rem;
}
.room-context {
@ -947,6 +941,7 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
.text-chat-entries {
overflow: auto;
padding-top: .6rem;
}
.text-chat-entry,
@ -965,20 +960,16 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
text-align: start;
}
.text-chat-scroller div:nth-child(2) {
margin-top: .5em;
}
/* Sent text chat entries should be on the right */
.text-chat-entry.sent {
/* aligns paragraph to right side */
justify-content: flex-end;
margin-left: 0;
margin-right: 5px;
margin-right: 4px;
}
.text-chat-entry.received {
margin-left: 4px;
margin-left: 2px;
margin-right: 0;
}
@ -1126,20 +1117,16 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
}
.text-chat-header.special.room-name {
color: black;
color: #666;
font-weight: bold;
text-align: start;
background-color: #dbf7ff;
margin-bottom: 0;
margin-right: 0;
}
.text-chat-header.special.room-name p {
background: #dbf7ff;
max-width: 100%;
/* 18px for indent of .text-chat-arrow, 1px for border of .text-chat-entry > p,
0.5rem for padding of .text-chat-entry > p */
padding: calc(18px - 1px - 0.5rem);
width: 100%;
padding: 0.8rem 1rem 1.4rem;
margin: 0;
}
.text-chat-header.special > p {

View File

@ -118,7 +118,8 @@ loop.store.TextChatStore = (function() {
// Notify MozLoopService if appropriate that a message has been appended
// and it should therefore check if we need a different sized window or not.
if (message.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME) {
if (message.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME &&
message.contentType !== CHAT_CONTENT_TYPES.CONTEXT) {
if (this._storeState.textChatEnabled) {
window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
} else {

View File

@ -106,6 +106,7 @@ loop.shared.views.chat = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
messageList: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
showInitialContext: React.PropTypes.bool.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
@ -174,6 +175,7 @@ loop.shared.views.chat = (function(mozL10n) {
this.props.messageList.map(function(entry, i) {
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
if (!this.props.showInitialContext) { return null; }
switch (entry.contentType) {
case CHAT_CONTENT_TYPES.ROOM_NAME:
return (
@ -188,7 +190,6 @@ loop.shared.views.chat = (function(mozL10n) {
allowClick: true,
description: entry.message,
dispatcher: this.props.dispatcher,
showContextTitle: true,
thumbnail: entry.extraData.thumbnail,
url: entry.extraData.location,
useDesktopPaths: this.props.useDesktopPaths})
@ -357,8 +358,8 @@ loop.shared.views.chat = (function(mozL10n) {
* as a field for entering new messages.
*
* @property {loop.Dispatcher} dispatcher
* @property {Boolean} showRoomName Set to true to show the room name
* special list item.
* @property {Boolean} showInitialContext Set to true to show the room name
* and initial context tile for linker clicker's special list items
*/
var TextChatView = React.createClass({displayName: "TextChatView",
mixins: [
@ -368,7 +369,7 @@ loop.shared.views.chat = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
showRoomName: React.PropTypes.bool.isRequired,
showInitialContext: React.PropTypes.bool.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
@ -377,18 +378,15 @@ loop.shared.views.chat = (function(mozL10n) {
},
render: function() {
var messageList;
var showingRoomName = false;
var messageList = this.state.messageList;
if (this.props.showRoomName) {
messageList = this.state.messageList;
showingRoomName = this.state.messageList.some(function(item) {
return item.contentType === CHAT_CONTENT_TYPES.ROOM_NAME;
});
} else {
messageList = this.state.messageList.filter(function(item) {
// Filter out items not displayed when showing initial context.
// We do this here so that we can set the classes correctly on the view.
if (!this.props.showInitialContext) {
messageList = messageList.filter(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
(item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME &&
item.contentType !== CHAT_CONTENT_TYPES.CONTEXT);
});
}
@ -398,10 +396,9 @@ loop.shared.views.chat = (function(mozL10n) {
});
var textChatViewClasses = React.addons.classSet({
"showing-room-name": showingRoomName,
"text-chat-view": true,
"text-chat-disabled": !this.state.textChatEnabled,
"text-chat-entries-empty": !messageList.length
"text-chat-entries-empty": !messageList.length,
"text-chat-disabled": !this.state.textChatEnabled
});
return (
@ -409,6 +406,7 @@ loop.shared.views.chat = (function(mozL10n) {
React.createElement(TextChatEntriesView, {
dispatcher: this.props.dispatcher,
messageList: messageList,
showInitialContext: this.props.showInitialContext,
useDesktopPaths: this.props.useDesktopPaths}),
React.createElement(TextChatInputView, {
dispatcher: this.props.dispatcher,

View File

@ -106,6 +106,7 @@ loop.shared.views.chat = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
messageList: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
showInitialContext: React.PropTypes.bool.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
@ -174,6 +175,7 @@ loop.shared.views.chat = (function(mozL10n) {
{
this.props.messageList.map(function(entry, i) {
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
if (!this.props.showInitialContext) { return null; }
switch (entry.contentType) {
case CHAT_CONTENT_TYPES.ROOM_NAME:
return (
@ -188,7 +190,6 @@ loop.shared.views.chat = (function(mozL10n) {
allowClick={true}
description={entry.message}
dispatcher={this.props.dispatcher}
showContextTitle={true}
thumbnail={entry.extraData.thumbnail}
url={entry.extraData.location}
useDesktopPaths={this.props.useDesktopPaths} />
@ -357,8 +358,8 @@ loop.shared.views.chat = (function(mozL10n) {
* as a field for entering new messages.
*
* @property {loop.Dispatcher} dispatcher
* @property {Boolean} showRoomName Set to true to show the room name
* special list item.
* @property {Boolean} showInitialContext Set to true to show the room name
* and initial context tile for linker clicker's special list items
*/
var TextChatView = React.createClass({
mixins: [
@ -368,7 +369,7 @@ loop.shared.views.chat = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
showRoomName: React.PropTypes.bool.isRequired,
showInitialContext: React.PropTypes.bool.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
@ -377,18 +378,15 @@ loop.shared.views.chat = (function(mozL10n) {
},
render: function() {
var messageList;
var showingRoomName = false;
var messageList = this.state.messageList;
if (this.props.showRoomName) {
messageList = this.state.messageList;
showingRoomName = this.state.messageList.some(function(item) {
return item.contentType === CHAT_CONTENT_TYPES.ROOM_NAME;
});
} else {
messageList = this.state.messageList.filter(function(item) {
// Filter out items not displayed when showing initial context.
// We do this here so that we can set the classes correctly on the view.
if (!this.props.showInitialContext) {
messageList = messageList.filter(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
(item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME &&
item.contentType !== CHAT_CONTENT_TYPES.CONTEXT);
});
}
@ -398,10 +396,9 @@ loop.shared.views.chat = (function(mozL10n) {
});
var textChatViewClasses = React.addons.classSet({
"showing-room-name": showingRoomName,
"text-chat-view": true,
"text-chat-disabled": !this.state.textChatEnabled,
"text-chat-entries-empty": !messageList.length
"text-chat-entries-empty": !messageList.length,
"text-chat-disabled": !this.state.textChatEnabled
});
return (
@ -409,6 +406,7 @@ loop.shared.views.chat = (function(mozL10n) {
<TextChatEntriesView
dispatcher={this.props.dispatcher}
messageList={messageList}
showInitialContext={this.props.showInitialContext}
useDesktopPaths={this.props.useDesktopPaths} />
<TextChatInputView
dispatcher={this.props.dispatcher}

View File

@ -810,7 +810,6 @@ loop.shared.views = (function(_, mozL10n) {
* is specified, then 'dispatcher' is also required.
* @property {String} description The description for the context url.
* @property {loop.Dispatcher} dispatcher
* @property {Boolean} showContextTitle Whether or not to show the "Let's talk about" title.
* @property {String} thumbnail The thumbnail url (expected to be a data url) to
* display. If not specified, a fallback url will be
* shown.
@ -826,7 +825,6 @@ loop.shared.views = (function(_, mozL10n) {
allowClick: React.PropTypes.bool.isRequired,
description: React.PropTypes.string.isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
showContextTitle: React.PropTypes.bool.isRequired,
thumbnail: React.PropTypes.string,
url: React.PropTypes.string,
useDesktopPaths: React.PropTypes.bool.isRequired
@ -845,17 +843,6 @@ loop.shared.views = (function(_, mozL10n) {
}));
},
/**
* Renders the context title ("Let's talk about") if necessary.
*/
renderContextTitle: function() {
if (!this.props.showContextTitle) {
return null;
}
return React.createElement("p", null, mozL10n.get("context_inroom_label2"));
},
render: function() {
var hostname;
@ -880,7 +867,6 @@ loop.shared.views = (function(_, mozL10n) {
return (
React.createElement("div", {className: "context-content"},
this.renderContextTitle(),
React.createElement("a", {className: wrapperClasses,
href: this.props.allowClick ? this.props.url : null,
onClick: this.handleLinkClick,
@ -1031,7 +1017,7 @@ loop.shared.views = (function(_, mozL10n) {
renderRemoteVideo: React.PropTypes.bool.isRequired,
screenShareMediaElement: React.PropTypes.object,
screenSharePosterUrl: React.PropTypes.string,
showContextRoomName: React.PropTypes.bool.isRequired,
showInitialContext: React.PropTypes.bool.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
@ -1135,7 +1121,7 @@ loop.shared.views = (function(_, mozL10n) {
),
React.createElement(loop.shared.views.chat.TextChatView, {
dispatcher: this.props.dispatcher,
showRoomName: this.props.showContextRoomName,
showInitialContext: this.props.showInitialContext,
useDesktopPaths: this.props.useDesktopPaths}),
this.state.localMediaAboslutelyPositioned ?
null : this.renderLocalVideo()

View File

@ -810,7 +810,6 @@ loop.shared.views = (function(_, mozL10n) {
* is specified, then 'dispatcher' is also required.
* @property {String} description The description for the context url.
* @property {loop.Dispatcher} dispatcher
* @property {Boolean} showContextTitle Whether or not to show the "Let's talk about" title.
* @property {String} thumbnail The thumbnail url (expected to be a data url) to
* display. If not specified, a fallback url will be
* shown.
@ -826,7 +825,6 @@ loop.shared.views = (function(_, mozL10n) {
allowClick: React.PropTypes.bool.isRequired,
description: React.PropTypes.string.isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
showContextTitle: React.PropTypes.bool.isRequired,
thumbnail: React.PropTypes.string,
url: React.PropTypes.string,
useDesktopPaths: React.PropTypes.bool.isRequired
@ -845,17 +843,6 @@ loop.shared.views = (function(_, mozL10n) {
}));
},
/**
* Renders the context title ("Let's talk about") if necessary.
*/
renderContextTitle: function() {
if (!this.props.showContextTitle) {
return null;
}
return <p>{mozL10n.get("context_inroom_label2")}</p>;
},
render: function() {
var hostname;
@ -880,7 +867,6 @@ loop.shared.views = (function(_, mozL10n) {
return (
<div className="context-content">
{this.renderContextTitle()}
<a className={wrapperClasses}
href={this.props.allowClick ? this.props.url : null}
onClick={this.handleLinkClick}
@ -1031,7 +1017,7 @@ loop.shared.views = (function(_, mozL10n) {
renderRemoteVideo: React.PropTypes.bool.isRequired,
screenShareMediaElement: React.PropTypes.object,
screenSharePosterUrl: React.PropTypes.string,
showContextRoomName: React.PropTypes.bool.isRequired,
showInitialContext: React.PropTypes.bool.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
@ -1135,7 +1121,7 @@ loop.shared.views = (function(_, mozL10n) {
</div>
<loop.shared.views.chat.TextChatView
dispatcher={this.props.dispatcher}
showRoomName={this.props.showContextRoomName}
showInitialContext={this.props.showInitialContext}
useDesktopPaths={this.props.useDesktopPaths} />
{this.state.localMediaAboslutelyPositioned ?
null : this.renderLocalVideo()}

View File

@ -619,7 +619,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
renderRemoteVideo: this.shouldRenderRemoteVideo(),
screenShareMediaElement: this.state.screenShareMediaElement,
screenSharePosterUrl: this.props.screenSharePosterUrl,
showContextRoomName: true,
showInitialContext: true,
useDesktopPaths: false},
React.createElement(StandaloneOverlayWrapper, {dispatcher: this.props.dispatcher}),
React.createElement(StandaloneRoomInfoArea, {activeRoomStore: this.props.activeRoomStore,

View File

@ -619,7 +619,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
renderRemoteVideo={this.shouldRenderRemoteVideo()}
screenShareMediaElement={this.state.screenShareMediaElement}
screenSharePosterUrl={this.props.screenSharePosterUrl}
showContextRoomName={true}
showInitialContext={true}
useDesktopPaths={false}>
<StandaloneOverlayWrapper dispatcher={this.props.dispatcher} />
<StandaloneRoomInfoArea activeRoomStore={this.props.activeRoomStore}

View File

@ -81,9 +81,3 @@ status_error=Something went wrong
# Text chat strings
chat_textbox_placeholder=Type here…
# LOCALIZATION NOTE (context_inroom_label2): this string is followed by the
# title/domain of the website you are having a conversation about, displayed on a
# separate line. If this structure doesn't work for your locale, you might want
# to consider this as a stand-alone title. See example screenshot:
# https://bug1084991.bugzilla.mozilla.org/attachment.cgi?id=8614721
context_inroom_label2=Let's Talk About:

View File

@ -248,23 +248,6 @@ describe("loop.store.TextChatStore", function() {
}]);
});
it("should dispatch a LoopChatDisabledMessageAppended event", function() {
store.setStoreState({ textChatEnabled: false });
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Let's share!",
roomUrl: "fake",
roomContextUrls: [{
description: "A wonderful event2",
location: "http://wonderful.invalid2",
thumbnail: "fake2"
}]
}));
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent,
new CustomEvent("LoopChatDisabledMessageAppended"));
});
it("should not dispatch a LoopChatMessageAppended event", function() {
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Let's share!",

View File

@ -46,6 +46,7 @@ describe("loop.shared.views.TextChatView", function() {
var basicProps = {
dispatcher: dispatcher,
messageList: [],
showInitialContext: true,
useDesktopPaths: false
};
@ -58,6 +59,7 @@ describe("loop.shared.views.TextChatView", function() {
var basicProps = {
dispatcher: dispatcher,
messageList: [],
showInitialContext: true,
useDesktopPaths: false
};
@ -396,7 +398,7 @@ describe("loop.shared.views.TextChatView", function() {
function mountTestComponent(extraProps) {
var props = _.extend({
dispatcher: dispatcher,
showRoomName: false,
showInitialContext: true,
useDesktopPaths: false,
showAlways: true
}, extraProps);
@ -453,40 +455,6 @@ describe("loop.shared.views.TextChatView", function() {
expect(view.getDOMNode().classList.contains("text-chat-entries-empty")).eql(false);
});
it("should add a showing room name class when the view shows room names and it has a room name", function() {
view = mountTestComponent({
showRoomName: true
});
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Study",
roomUrl: "Fake"
}));
expect(view.getDOMNode().classList.contains("showing-room-name")).eql(true);
});
it("shouldn't add a showing room name class when the view doesn't show room names", function() {
view = mountTestComponent({
showRoomName: false
});
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Study",
roomUrl: "Fake"
}));
expect(view.getDOMNode().classList.contains("showing-room-name")).eql(false);
});
it("shouldn't add a showing room name class when the view doesn't have a name", function() {
view = mountTestComponent({
showRoomName: true
});
expect(view.getDOMNode().classList.contains("showing-room-name")).eql(false);
});
it("should show timestamps from msgs sent more than 1 min apart", function() {
view = mountTestComponent();
@ -569,7 +537,7 @@ describe("loop.shared.views.TextChatView", function() {
it("should render a room name special entry", function() {
view = mountTestComponent({
showRoomName: true
showInitialContext: true
});
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
@ -605,6 +573,27 @@ describe("loop.shared.views.TextChatView", function() {
expect(node.querySelector(".context-url-view-wrapper")).to.not.eql(null);
});
it("should not render a room title and context url when show initial context is false", function() {
view = mountTestComponent({
showInitialContext: false
});
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "A Very Long Conversation Name",
roomUrl: "http://showcase",
roomContextUrls: [{
description: "A wonderful page!",
location: "http://wonderful.invalid"
// use the fallback thumbnail
}]
}));
var node = view.getDOMNode();
expect(node.querySelector(".showing-room-name")).to.eql(null);
expect(node.querySelector(".context-url-view-wrapper")).to.eql(null);
});
it("should dispatch SendTextChatMessage action when enter is pressed", function() {
view = mountTestComponent();

View File

@ -837,15 +837,6 @@ describe("loop.shared.views", function() {
expect(view.getDOMNode().querySelector(".context-content > p")).eql(null);
});
it("should display a title if required", function() {
view = mountTestComponent({
showContextTitle: true,
url: "http://wonderful.invalid"
});
expect(view.getDOMNode().querySelector(".context-content > p")).not.eql(null);
});
it("should set the href on the link if clicks are allowed", function() {
view = mountTestComponent({
allowClick: true,
@ -1024,7 +1015,7 @@ describe("loop.shared.views", function() {
localVideoMuted: false,
matchMedia: window.matchMedia,
renderRemoteVideo: false,
showContextRoomName: false,
showInitialContext: false,
useDesktopPaths: false
};

View File

@ -1288,7 +1288,7 @@
width: 298},
React.createElement("div", {className: "fx-embedded"},
React.createElement(TextChatView, {dispatcher: dispatcher,
showRoomName: false,
showInitialContext: false,
useDesktopPaths: false})
)
),
@ -1302,7 +1302,7 @@
React.createElement("div", {className: "media-wrapper"},
React.createElement(TextChatView, {
dispatcher: dispatcher,
showRoomName: true,
showInitialContext: true,
useDesktopPaths: false})
)
)

View File

@ -1288,7 +1288,7 @@
width={298}>
<div className="fx-embedded">
<TextChatView dispatcher={dispatcher}
showRoomName={false}
showInitialContext={false}
useDesktopPaths={false} />
</div>
</FramedExample>
@ -1302,7 +1302,7 @@
<div className="media-wrapper">
<TextChatView
dispatcher={dispatcher}
showRoomName={true}
showInitialContext={true}
useDesktopPaths={false} />
</div>
</div>

View File

@ -219,12 +219,6 @@ no_conversations_start_message2=Start a new one!
# conversation window when the user edits context. It is a header to the edit
# section.
context_inroom_header=Let's Talk About…
# LOCALIZATION NOTE (context_inroom_label2): this string is followed by the
# title and domain of the website you are having a conversation about, displayed on a
# separate line. If this structure doesn't work for your locale, you might want
# to consider this as a stand-alone title. See example screenshot:
# https://bug1115342.bugzilla.mozilla.org/attachment.cgi?id=8563677
context_inroom_label2=Let's Talk About:
context_edit_name_placeholder=Conversation Name
context_edit_comments_placeholder=Comments
context_cancel_label=Cancel