зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1142687 - Show context information for current page in the rooms view. r=standard8
This commit is contained in:
Родитель
9ae3882abb
Коммит
eefcc0b4fd
|
@ -1700,9 +1700,9 @@ pref("loop.debug.websocket", false);
|
|||
pref("loop.debug.sdk", false);
|
||||
pref("loop.debug.twoWayMediaTelemetry", false);
|
||||
#ifdef DEBUG
|
||||
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: https://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
|
||||
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src *; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
|
||||
#else
|
||||
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: https://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
|
||||
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src *; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
|
||||
#endif
|
||||
pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
|
||||
pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
|
||||
|
|
|
@ -21,6 +21,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
|
|||
"resource:///modules/loop/LoopStorage.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "hookWindowCloseForPanelClose",
|
||||
"resource://gre/modules/MozSocialAPI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
|
||||
"resource://gre/modules/PageMetadata.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
|
||||
|
@ -844,6 +846,24 @@ function injectLoopAPI(targetWindow) {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the metadata related to the currently selected tab in
|
||||
* the most recent window.
|
||||
*
|
||||
* @param {Function} A callback that is passed the metadata.
|
||||
*/
|
||||
getSelectedTabMetadata: {
|
||||
value: function(callback) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
win.messageManager.addMessageListener("PageMetadata:PageDataResult", function onPageDataResult(msg) {
|
||||
win.messageManager.removeMessageListener("PageMetadata:PageDataResult", onPageDataResult);
|
||||
let pageData = msg.json;
|
||||
callback(cloneValueInto(pageData, targetWindow));
|
||||
});
|
||||
win.gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Associates a session-id and a call-id with a window for debugging.
|
||||
*
|
||||
|
|
|
@ -170,25 +170,71 @@ body {
|
|||
/* Rooms */
|
||||
.rooms {
|
||||
min-height: 100px;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.rooms > h1 {
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
|
||||
.rooms > p {
|
||||
padding: .5rem 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rooms > p > .btn {
|
||||
.rooms > div > .context {
|
||||
margin: .5rem 0 0;
|
||||
background-color: #DEEFF7;
|
||||
border-radius: 3px 3px 0 0;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.rooms > div > .context > .context-enabled {
|
||||
margin-bottom: .5rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.rooms > div > .context > .context-enabled > input {
|
||||
-moz-margin-start: 0;
|
||||
}
|
||||
|
||||
.rooms > div > .context > .context-preview {
|
||||
float: right;
|
||||
width: 100px;
|
||||
max-height: 200px;
|
||||
-moz-margin-start: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
body[dir=rtl] .rooms > div > .context > .context-preview {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.rooms > div > .context > .context-preview[src=""] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rooms > div > .context > .context-description {
|
||||
display: block;
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.rooms > div > .context > .context-url {
|
||||
display: block;
|
||||
color: #59A1D7;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.rooms > div > .btn {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
margin: 0 auto;
|
||||
margin: 0 auto .5rem;
|
||||
width: 100%;
|
||||
padding: .5rem 1rem;
|
||||
border-radius: 0 0 3px 3px;
|
||||
}
|
||||
|
||||
/* Remove when bug 1142671 is backed out. */
|
||||
.rooms > div > :not(.context) + .btn {
|
||||
border-radius: 3px;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.room-list {
|
||||
|
@ -197,6 +243,8 @@ body {
|
|||
overflow: auto;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin-left: -1rem;
|
||||
margin-right: -1rem;
|
||||
}
|
||||
|
||||
.room-list:empty {
|
||||
|
|
|
@ -594,6 +594,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
|
||||
|
||||
propTypes: {
|
||||
mozLoop: React.PropTypes.object.isRequired,
|
||||
store: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
userDisplayName: React.PropTypes.string.isRequired // for room creation
|
||||
|
@ -666,7 +667,8 @@ loop.panel = (function(_, mozL10n) {
|
|||
);
|
||||
}, this)
|
||||
),
|
||||
React.createElement("p", null,
|
||||
React.createElement("div", null,
|
||||
React.createElement(ContextInfo, {mozLoop: this.props.mozLoop}),
|
||||
React.createElement("button", {className: "btn btn-info new-room-button",
|
||||
onClick: this.handleCreateButtonClick,
|
||||
disabled: this._hasPendingOperation()},
|
||||
|
@ -678,6 +680,60 @@ loop.panel = (function(_, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Context info that is offered to be part of a Room.
|
||||
*/
|
||||
var ContextInfo = React.createClass({displayName: "ContextInfo",
|
||||
propTypes: {
|
||||
mozLoop: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
mixins: [sharedMixins.DocumentVisibilityMixin],
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
previewImage: "",
|
||||
description: "",
|
||||
url: ""
|
||||
};
|
||||
},
|
||||
|
||||
onDocumentVisible: function() {
|
||||
this.props.mozLoop.getSelectedTabMetadata(function callback(metadata) {
|
||||
var previewImage = metadata.previews.length ? metadata.previews[0] : "";
|
||||
var description = metadata.description || metadata.title;
|
||||
var url = metadata.url;
|
||||
this.setState({previewImage: previewImage,
|
||||
description: description,
|
||||
url: url});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
onDocumentHidden: function() {
|
||||
this.setState({previewImage: "",
|
||||
description: "",
|
||||
url: ""});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.props.mozLoop.getLoopPref("contextInConverations.enabled") ||
|
||||
!this.state.url) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
React.createElement("div", {className: "context"},
|
||||
React.createElement("label", {className: "context-enabled"},
|
||||
React.createElement("input", {type: "checkbox"}),
|
||||
mozL10n.get("context_offer_label")
|
||||
),
|
||||
React.createElement("img", {className: "context-preview", src: this.state.previewImage}),
|
||||
React.createElement("span", {className: "context-description"}, this.state.description),
|
||||
React.createElement("span", {className: "context-url"}, this.state.url)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Panel view.
|
||||
*/
|
||||
|
@ -819,7 +875,8 @@ loop.panel = (function(_, mozL10n) {
|
|||
React.createElement(Tab, {name: "rooms"},
|
||||
React.createElement(RoomList, {dispatcher: this.props.dispatcher,
|
||||
store: this.props.roomStore,
|
||||
userDisplayName: this._getUserDisplayName()}),
|
||||
userDisplayName: this._getUserDisplayName(),
|
||||
mozLoop: this.props.mozLoop}),
|
||||
React.createElement(ToSView, null)
|
||||
),
|
||||
React.createElement(Tab, {name: "contacts"},
|
||||
|
@ -890,6 +947,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
init: init,
|
||||
AuthLink: AuthLink,
|
||||
AvailabilityDropdown: AvailabilityDropdown,
|
||||
ContextInfo: ContextInfo,
|
||||
GettingStartedView: GettingStartedView,
|
||||
PanelView: PanelView,
|
||||
RoomEntry: RoomEntry,
|
||||
|
|
|
@ -594,6 +594,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
|
||||
|
||||
propTypes: {
|
||||
mozLoop: React.PropTypes.object.isRequired,
|
||||
store: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
userDisplayName: React.PropTypes.string.isRequired // for room creation
|
||||
|
@ -666,13 +667,68 @@ loop.panel = (function(_, mozL10n) {
|
|||
/>;
|
||||
}, this)
|
||||
}</div>
|
||||
<p>
|
||||
<div>
|
||||
<ContextInfo mozLoop={this.props.mozLoop} />
|
||||
<button className="btn btn-info new-room-button"
|
||||
onClick={this.handleCreateButtonClick}
|
||||
disabled={this._hasPendingOperation()}>
|
||||
{mozL10n.get("rooms_new_room_button_label")}
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Context info that is offered to be part of a Room.
|
||||
*/
|
||||
var ContextInfo = React.createClass({
|
||||
propTypes: {
|
||||
mozLoop: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
mixins: [sharedMixins.DocumentVisibilityMixin],
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
previewImage: "",
|
||||
description: "",
|
||||
url: ""
|
||||
};
|
||||
},
|
||||
|
||||
onDocumentVisible: function() {
|
||||
this.props.mozLoop.getSelectedTabMetadata(function callback(metadata) {
|
||||
var previewImage = metadata.previews.length ? metadata.previews[0] : "";
|
||||
var description = metadata.description || metadata.title;
|
||||
var url = metadata.url;
|
||||
this.setState({previewImage: previewImage,
|
||||
description: description,
|
||||
url: url});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
onDocumentHidden: function() {
|
||||
this.setState({previewImage: "",
|
||||
description: "",
|
||||
url: ""});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.props.mozLoop.getLoopPref("contextInConverations.enabled") ||
|
||||
!this.state.url) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="context">
|
||||
<label className="context-enabled">
|
||||
<input type="checkbox"/>
|
||||
{mozL10n.get("context_offer_label")}
|
||||
</label>
|
||||
<img className="context-preview" src={this.state.previewImage}/>
|
||||
<span className="context-description">{this.state.description}</span>
|
||||
<span className="context-url">{this.state.url}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -819,7 +875,8 @@ loop.panel = (function(_, mozL10n) {
|
|||
<Tab name="rooms">
|
||||
<RoomList dispatcher={this.props.dispatcher}
|
||||
store={this.props.roomStore}
|
||||
userDisplayName={this._getUserDisplayName()}/>
|
||||
userDisplayName={this._getUserDisplayName()}
|
||||
mozLoop={this.props.mozLoop}/>
|
||||
<ToSView />
|
||||
</Tab>
|
||||
<Tab name="contacts">
|
||||
|
@ -890,6 +947,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
init: init,
|
||||
AuthLink: AuthLink,
|
||||
AvailabilityDropdown: AvailabilityDropdown,
|
||||
ContextInfo: ContextInfo,
|
||||
GettingStartedView: GettingStartedView,
|
||||
PanelView: PanelView,
|
||||
RoomEntry: RoomEntry,
|
||||
|
|
|
@ -651,7 +651,8 @@ describe("loop.panel", function() {
|
|||
React.createElement(loop.panel.RoomList, {
|
||||
store: roomStore,
|
||||
dispatcher: dispatcher,
|
||||
userDisplayName: fakeEmail
|
||||
userDisplayName: fakeEmail,
|
||||
mozLoop: fakeMozLoop
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -708,6 +709,49 @@ describe("loop.panel", function() {
|
|||
var buttonNode = view.getDOMNode().querySelector("button[disabled]");
|
||||
expect(buttonNode).to.not.equal(null);
|
||||
});
|
||||
|
||||
it("should show context information when a URL is available",
|
||||
function() {
|
||||
navigator.mozLoop.getLoopPref = function() {
|
||||
return true;
|
||||
}
|
||||
|
||||
var view = TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.panel.ContextInfo, {
|
||||
mozLoop: navigator.mozLoop
|
||||
})
|
||||
);
|
||||
view.setState({
|
||||
previews: [""],
|
||||
description: "fake description",
|
||||
url: "https://www.example.com"
|
||||
});
|
||||
|
||||
var contextEnabledCheckbox = view.getDOMNode().querySelector(".context-enabled");
|
||||
expect(contextEnabledCheckbox).to.not.equal(null);
|
||||
});
|
||||
|
||||
it("should not show context information when a URL is unavailable",
|
||||
function() {
|
||||
navigator.mozLoop.getLoopPref = function() {
|
||||
return true;
|
||||
}
|
||||
|
||||
var view = TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.panel.ContextInfo, {
|
||||
mozLoop: navigator.mozLoop
|
||||
})
|
||||
);
|
||||
view.setState({
|
||||
previews: [""],
|
||||
description: "fake description",
|
||||
url: ""
|
||||
});
|
||||
|
||||
var contextInfo = view.getDOMNode();
|
||||
expect(contextInfo).to.equal(null);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('loop.panel.ToSView', function() {
|
||||
|
|
|
@ -115,6 +115,7 @@ navigator.mozLoop = {
|
|||
// Ensure we skip FTE completely.
|
||||
case "gettingStarted.seen":
|
||||
case "contacts.gravatars.promo":
|
||||
case "contextInConverations.enabled":
|
||||
return true;
|
||||
case "contacts.gravatars.show":
|
||||
return false;
|
||||
|
@ -127,6 +128,13 @@ navigator.mozLoop = {
|
|||
return "http://www.gravatar.com/avatar/" + (Math.ceil(Math.random() * 3) === 2 ?
|
||||
"0a996f0fe2727ef1668bdb11897e4459" : "foo") + ".jpg?default=blank&s=40";
|
||||
},
|
||||
getSelectedTabMetadata: function(callback) {
|
||||
callback({
|
||||
previews: ["chrome://branding/content/about-logo.png"],
|
||||
description: "sample webpage description",
|
||||
url: "https://www.example.com"
|
||||
});
|
||||
},
|
||||
contacts: {
|
||||
getAll: function(callback) {
|
||||
callback(null, [].concat(fakeContacts));
|
||||
|
|
|
@ -56,6 +56,28 @@
|
|||
|
||||
function noop(){}
|
||||
|
||||
// We save the visibility change listeners so that we can fake an event
|
||||
// to the panel once we've loaded all the views.
|
||||
var visibilityListeners = [];
|
||||
var rootObject = window;
|
||||
|
||||
rootObject.document.addEventListener = function(eventName, func) {
|
||||
if (eventName === "visibilitychange") {
|
||||
visibilityListeners.push(func);
|
||||
}
|
||||
window.addEventListener(eventName, func);
|
||||
};
|
||||
|
||||
rootObject.document.removeEventListener = function(eventName, func) {
|
||||
if (eventName === "visibilitychange") {
|
||||
var index = visibilityListeners.indexOf(func);
|
||||
visibilityListeners.splice(index, 1);
|
||||
}
|
||||
window.removeEventListener(eventName, func);
|
||||
};
|
||||
|
||||
loop.shared.mixins.setRootObject(rootObject);
|
||||
|
||||
// Feedback API client configured to send data to the stage input server,
|
||||
// which is available at https://input.allizom.org
|
||||
var stageFeedbackApiClient = new loop.FeedbackAPIClient(
|
||||
|
@ -757,6 +779,10 @@
|
|||
window.addEventListener("DOMContentLoaded", function() {
|
||||
try {
|
||||
React.renderComponent(React.createElement(App, null), document.getElementById("main"));
|
||||
|
||||
for (var listener of visibilityListeners) {
|
||||
listener({target: {hidden: false}});
|
||||
}
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
uncaughtError = err;
|
||||
|
|
|
@ -56,6 +56,28 @@
|
|||
|
||||
function noop(){}
|
||||
|
||||
// We save the visibility change listeners so that we can fake an event
|
||||
// to the panel once we've loaded all the views.
|
||||
var visibilityListeners = [];
|
||||
var rootObject = window;
|
||||
|
||||
rootObject.document.addEventListener = function(eventName, func) {
|
||||
if (eventName === "visibilitychange") {
|
||||
visibilityListeners.push(func);
|
||||
}
|
||||
window.addEventListener(eventName, func);
|
||||
};
|
||||
|
||||
rootObject.document.removeEventListener = function(eventName, func) {
|
||||
if (eventName === "visibilitychange") {
|
||||
var index = visibilityListeners.indexOf(func);
|
||||
visibilityListeners.splice(index, 1);
|
||||
}
|
||||
window.removeEventListener(eventName, func);
|
||||
};
|
||||
|
||||
loop.shared.mixins.setRootObject(rootObject);
|
||||
|
||||
// Feedback API client configured to send data to the stage input server,
|
||||
// which is available at https://input.allizom.org
|
||||
var stageFeedbackApiClient = new loop.FeedbackAPIClient(
|
||||
|
@ -757,6 +779,10 @@
|
|||
window.addEventListener("DOMContentLoaded", function() {
|
||||
try {
|
||||
React.renderComponent(<App />, document.getElementById("main"));
|
||||
|
||||
for (var listener of visibilityListeners) {
|
||||
listener({target: {hidden: false}});
|
||||
}
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
uncaughtError = err;
|
||||
|
|
|
@ -331,3 +331,6 @@ infobar_button_gotit_label=Got it!
|
|||
infobar_button_gotit_accesskey=G
|
||||
infobar_menuitem_dontshowagain_label=Don't show this again
|
||||
infobar_menuitem_dontshowagain_accesskey=D
|
||||
|
||||
# Context in conversation strings
|
||||
context_offer_label=Let's talk about this page
|
||||
|
|
Загрузка…
Ссылка в новой задаче