зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1156205: show a scrollbar in the social share dropdown list inside the Loop conversation window when the number of items exceeds the maximum height. r=Standard8
This commit is contained in:
Родитель
f44b959fb7
Коммит
e7926bf434
|
@ -161,6 +161,9 @@ loop.conversation = (function(mozL10n) {
|
||||||
mozLoop: navigator.mozLoop}
|
mozLoop: navigator.mozLoop}
|
||||||
), document.querySelector('#main'));
|
), document.querySelector('#main'));
|
||||||
|
|
||||||
|
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||||
|
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.GetWindowData({
|
dispatcher.dispatch(new sharedActions.GetWindowData({
|
||||||
windowId: windowId
|
windowId: windowId
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -161,6 +161,9 @@ loop.conversation = (function(mozL10n) {
|
||||||
mozLoop={navigator.mozLoop}
|
mozLoop={navigator.mozLoop}
|
||||||
/>, document.querySelector('#main'));
|
/>, document.querySelector('#main'));
|
||||||
|
|
||||||
|
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||||
|
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.GetWindowData({
|
dispatcher.dispatch(new sharedActions.GetWindowData({
|
||||||
windowId: windowId
|
windowId: windowId
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -140,7 +140,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
|
var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
|
||||||
|
|
||||||
var AcceptCallView = React.createClass({displayName: "AcceptCallView",
|
var AcceptCallView = React.createClass({displayName: "AcceptCallView",
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin()],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
callType: React.PropTypes.string.isRequired,
|
callType: React.PropTypes.string.isRequired,
|
||||||
|
|
|
@ -140,7 +140,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||||
var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
|
var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
|
||||||
|
|
||||||
var AcceptCallView = React.createClass({
|
var AcceptCallView = React.createClass({
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin()],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
callType: React.PropTypes.string.isRequired,
|
callType: React.PropTypes.string.isRequired,
|
||||||
|
|
|
@ -117,7 +117,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
* Availability drop down menu subview.
|
* Availability drop down menu subview.
|
||||||
*/
|
*/
|
||||||
var AvailabilityDropdown = React.createClass({displayName: "AvailabilityDropdown",
|
var AvailabilityDropdown = React.createClass({displayName: "AvailabilityDropdown",
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin()],
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
|
@ -322,7 +322,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
mozLoop: React.PropTypes.object.isRequired
|
mozLoop: React.PropTypes.object.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.WindowCloseMixin],
|
mixins: [sharedMixins.DropdownMenuMixin(), sharedMixins.WindowCloseMixin],
|
||||||
|
|
||||||
handleClickSettingsEntry: function() {
|
handleClickSettingsEntry: function() {
|
||||||
// XXX to be implemented at the same time as unhiding the entry
|
// XXX to be implemented at the same time as unhiding the entry
|
||||||
|
@ -941,6 +941,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
), document.querySelector("#main"));
|
), document.querySelector("#main"));
|
||||||
|
|
||||||
document.body.setAttribute("dir", mozL10n.getDirection());
|
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||||
|
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||||
|
|
||||||
// Notify the window that we've finished initalization and initial layout
|
// Notify the window that we've finished initalization and initial layout
|
||||||
var evtObject = document.createEvent('Event');
|
var evtObject = document.createEvent('Event');
|
||||||
|
|
|
@ -117,7 +117,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
* Availability drop down menu subview.
|
* Availability drop down menu subview.
|
||||||
*/
|
*/
|
||||||
var AvailabilityDropdown = React.createClass({
|
var AvailabilityDropdown = React.createClass({
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin()],
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
|
@ -322,7 +322,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
mozLoop: React.PropTypes.object.isRequired
|
mozLoop: React.PropTypes.object.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.WindowCloseMixin],
|
mixins: [sharedMixins.DropdownMenuMixin(), sharedMixins.WindowCloseMixin],
|
||||||
|
|
||||||
handleClickSettingsEntry: function() {
|
handleClickSettingsEntry: function() {
|
||||||
// XXX to be implemented at the same time as unhiding the entry
|
// XXX to be implemented at the same time as unhiding the entry
|
||||||
|
@ -941,6 +941,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
/>, document.querySelector("#main"));
|
/>, document.querySelector("#main"));
|
||||||
|
|
||||||
document.body.setAttribute("dir", mozL10n.getDirection());
|
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||||
|
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||||
|
|
||||||
// Notify the window that we've finished initalization and initial layout
|
// Notify the window that we've finished initalization and initial layout
|
||||||
var evtObject = document.createEvent('Event');
|
var evtObject = document.createEvent('Event');
|
||||||
|
|
|
@ -113,6 +113,7 @@ loop.roomViews = (function(mozL10n) {
|
||||||
var shareDropdown = cx({
|
var shareDropdown = cx({
|
||||||
"share-service-dropdown": true,
|
"share-service-dropdown": true,
|
||||||
"dropdown-menu": true,
|
"dropdown-menu": true,
|
||||||
|
"visually-hidden": true,
|
||||||
"share-button-unavailable": !this.props.socialShareButtonAvailable,
|
"share-button-unavailable": !this.props.socialShareButtonAvailable,
|
||||||
"hide": !this.props.show
|
"hide": !this.props.show
|
||||||
});
|
});
|
||||||
|
@ -170,7 +171,7 @@ loop.roomViews = (function(mozL10n) {
|
||||||
* Desktop room invitation view (overlay).
|
* Desktop room invitation view (overlay).
|
||||||
*/
|
*/
|
||||||
var DesktopRoomInvitationView = React.createClass({displayName: "DesktopRoomInvitationView",
|
var DesktopRoomInvitationView = React.createClass({displayName: "DesktopRoomInvitationView",
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
@ -185,7 +186,8 @@ loop.roomViews = (function(mozL10n) {
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
copiedUrl: false,
|
copiedUrl: false,
|
||||||
editMode: false
|
editMode: false,
|
||||||
|
newRoomName: ""
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -239,31 +241,31 @@ loop.roomViews = (function(mozL10n) {
|
||||||
React.createElement("a", {className: cx({hide: !canAddContext, "room-invitation-addcontext": true}),
|
React.createElement("a", {className: cx({hide: !canAddContext, "room-invitation-addcontext": true}),
|
||||||
onClick: this.handleAddContextClick},
|
onClick: this.handleAddContextClick},
|
||||||
mozL10n.get("context_add_some_label")
|
mozL10n.get("context_add_some_label")
|
||||||
),
|
)
|
||||||
React.createElement("div", {className: "btn-group call-action-group"},
|
|
||||||
React.createElement("button", {className: "btn btn-info btn-email",
|
|
||||||
onClick: this.handleEmailButtonClick},
|
|
||||||
mozL10n.get("email_link_button")
|
|
||||||
),
|
|
||||||
React.createElement("button", {className: "btn btn-info btn-copy",
|
|
||||||
onClick: this.handleCopyButtonClick},
|
|
||||||
this.state.copiedUrl ? mozL10n.get("copied_url_button") :
|
|
||||||
mozL10n.get("copy_url_button2")
|
|
||||||
),
|
|
||||||
React.createElement("button", {className: "btn btn-info btn-share",
|
|
||||||
ref: "anchor",
|
|
||||||
onClick: this.handleShareButtonClick},
|
|
||||||
mozL10n.get("share_button3")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
React.createElement(SocialShareDropdown, {
|
|
||||||
dispatcher: this.props.dispatcher,
|
|
||||||
roomUrl: this.props.roomData.roomUrl,
|
|
||||||
show: this.state.showMenu,
|
|
||||||
socialShareButtonAvailable: this.props.socialShareButtonAvailable,
|
|
||||||
socialShareProviders: this.props.socialShareProviders,
|
|
||||||
ref: "menu"})
|
|
||||||
),
|
),
|
||||||
|
React.createElement("div", {className: "btn-group call-action-group"},
|
||||||
|
React.createElement("button", {className: "btn btn-info btn-email",
|
||||||
|
onClick: this.handleEmailButtonClick},
|
||||||
|
mozL10n.get("email_link_button")
|
||||||
|
),
|
||||||
|
React.createElement("button", {className: "btn btn-info btn-copy",
|
||||||
|
onClick: this.handleCopyButtonClick},
|
||||||
|
this.state.copiedUrl ? mozL10n.get("copied_url_button") :
|
||||||
|
mozL10n.get("copy_url_button2")
|
||||||
|
),
|
||||||
|
React.createElement("button", {className: "btn btn-info btn-share",
|
||||||
|
ref: "anchor",
|
||||||
|
onClick: this.handleShareButtonClick},
|
||||||
|
mozL10n.get("share_button3")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
React.createElement(SocialShareDropdown, {
|
||||||
|
dispatcher: this.props.dispatcher,
|
||||||
|
roomUrl: this.props.roomData.roomUrl,
|
||||||
|
show: this.state.showMenu,
|
||||||
|
socialShareButtonAvailable: this.props.socialShareButtonAvailable,
|
||||||
|
socialShareProviders: this.props.socialShareProviders,
|
||||||
|
ref: "menu"}),
|
||||||
React.createElement(DesktopRoomContextView, {
|
React.createElement(DesktopRoomContextView, {
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
editMode: this.state.editMode,
|
editMode: this.state.editMode,
|
||||||
|
|
|
@ -113,6 +113,7 @@ loop.roomViews = (function(mozL10n) {
|
||||||
var shareDropdown = cx({
|
var shareDropdown = cx({
|
||||||
"share-service-dropdown": true,
|
"share-service-dropdown": true,
|
||||||
"dropdown-menu": true,
|
"dropdown-menu": true,
|
||||||
|
"visually-hidden": true,
|
||||||
"share-button-unavailable": !this.props.socialShareButtonAvailable,
|
"share-button-unavailable": !this.props.socialShareButtonAvailable,
|
||||||
"hide": !this.props.show
|
"hide": !this.props.show
|
||||||
});
|
});
|
||||||
|
@ -170,7 +171,7 @@ loop.roomViews = (function(mozL10n) {
|
||||||
* Desktop room invitation view (overlay).
|
* Desktop room invitation view (overlay).
|
||||||
*/
|
*/
|
||||||
var DesktopRoomInvitationView = React.createClass({
|
var DesktopRoomInvitationView = React.createClass({
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
@ -185,7 +186,8 @@ loop.roomViews = (function(mozL10n) {
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
copiedUrl: false,
|
copiedUrl: false,
|
||||||
editMode: false
|
editMode: false,
|
||||||
|
newRoomName: ""
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -240,30 +242,30 @@ loop.roomViews = (function(mozL10n) {
|
||||||
onClick={this.handleAddContextClick}>
|
onClick={this.handleAddContextClick}>
|
||||||
{mozL10n.get("context_add_some_label")}
|
{mozL10n.get("context_add_some_label")}
|
||||||
</a>
|
</a>
|
||||||
<div className="btn-group call-action-group">
|
|
||||||
<button className="btn btn-info btn-email"
|
|
||||||
onClick={this.handleEmailButtonClick}>
|
|
||||||
{mozL10n.get("email_link_button")}
|
|
||||||
</button>
|
|
||||||
<button className="btn btn-info btn-copy"
|
|
||||||
onClick={this.handleCopyButtonClick}>
|
|
||||||
{this.state.copiedUrl ? mozL10n.get("copied_url_button") :
|
|
||||||
mozL10n.get("copy_url_button2")}
|
|
||||||
</button>
|
|
||||||
<button className="btn btn-info btn-share"
|
|
||||||
ref="anchor"
|
|
||||||
onClick={this.handleShareButtonClick}>
|
|
||||||
{mozL10n.get("share_button3")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<SocialShareDropdown
|
|
||||||
dispatcher={this.props.dispatcher}
|
|
||||||
roomUrl={this.props.roomData.roomUrl}
|
|
||||||
show={this.state.showMenu}
|
|
||||||
socialShareButtonAvailable={this.props.socialShareButtonAvailable}
|
|
||||||
socialShareProviders={this.props.socialShareProviders}
|
|
||||||
ref="menu" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="btn-group call-action-group">
|
||||||
|
<button className="btn btn-info btn-email"
|
||||||
|
onClick={this.handleEmailButtonClick}>
|
||||||
|
{mozL10n.get("email_link_button")}
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-info btn-copy"
|
||||||
|
onClick={this.handleCopyButtonClick}>
|
||||||
|
{this.state.copiedUrl ? mozL10n.get("copied_url_button") :
|
||||||
|
mozL10n.get("copy_url_button2")}
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-info btn-share"
|
||||||
|
ref="anchor"
|
||||||
|
onClick={this.handleShareButtonClick}>
|
||||||
|
{mozL10n.get("share_button3")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<SocialShareDropdown
|
||||||
|
dispatcher={this.props.dispatcher}
|
||||||
|
roomUrl={this.props.roomData.roomUrl}
|
||||||
|
show={this.state.showMenu}
|
||||||
|
socialShareButtonAvailable={this.props.socialShareButtonAvailable}
|
||||||
|
socialShareProviders={this.props.socialShareProviders}
|
||||||
|
ref="menu" />
|
||||||
<DesktopRoomContextView
|
<DesktopRoomContextView
|
||||||
dispatcher={this.props.dispatcher}
|
dispatcher={this.props.dispatcher}
|
||||||
editMode={this.state.editMode}
|
editMode={this.state.editMode}
|
||||||
|
|
|
@ -426,9 +426,9 @@ p {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[dir=rtl] .dropdown-menu-item {
|
body[dir=rtl] .dropdown-menu {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 10px;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu-item {
|
.dropdown-menu-item {
|
||||||
|
|
|
@ -866,7 +866,6 @@ html, .fx-embedded, #main,
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-invitation-content {
|
.room-invitation-content {
|
||||||
order: 1;
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
|
@ -882,7 +881,7 @@ html, .fx-embedded, #main,
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-invitation-overlay .btn-group {
|
.room-invitation-overlay .btn-group {
|
||||||
padding: 0;
|
padding: 0 0 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-invitation-addcontext {
|
.room-invitation-addcontext {
|
||||||
|
@ -913,6 +912,14 @@ body[dir="rtl"] .room-invitation-addcontext {
|
||||||
text-align: start;
|
text-align: start;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When the dropdown is showing a vertical scrollbar, compensate for its width. */
|
||||||
|
body[platform="other"] .share-service-dropdown.overflow > .dropdown-menu-item,
|
||||||
|
body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
||||||
|
-moz-padding-end: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.share-service-dropdown.share-button-unavailable {
|
.share-service-dropdown.share-button-unavailable {
|
||||||
|
@ -988,7 +995,6 @@ body[dir=rtl] .share-service-dropdown .share-panel-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: auto;
|
left: auto;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
order: 2;
|
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,110 +83,165 @@ loop.shared.mixins = (function() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dropdown menu mixin.
|
* Dropdown menu mixin.
|
||||||
|
*
|
||||||
|
* @param {Sring} [boundingBoxSelector] Selector that points to an element that
|
||||||
|
* defines the constraints this dropdown
|
||||||
|
* is shown within. If not provided,
|
||||||
|
* `document.body` is assumed to be the
|
||||||
|
* constraining element.
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
var DropdownMenuMixin = {
|
var DropdownMenuMixin = function(boundingBoxSelector) {
|
||||||
get documentBody() {
|
return {
|
||||||
return rootObject.document.body;
|
get documentBody() {
|
||||||
},
|
return rootObject.document.body;
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {showMenu: false};
|
return {
|
||||||
},
|
showMenu: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
_onBodyClick: function(event) {
|
_onBodyClick: function(event) {
|
||||||
var menuButton = this.refs["menu-button"] && this.refs["menu-button"].getDOMNode();
|
var menuButton = this.refs["menu-button"] && this.refs["menu-button"].getDOMNode();
|
||||||
if (this.refs.anchor) {
|
if (this.refs.anchor) {
|
||||||
menuButton = this.refs.anchor.getDOMNode();
|
menuButton = this.refs.anchor.getDOMNode();
|
||||||
|
}
|
||||||
|
// If a menu button/ anchor is defined and clicked on, it will be in charge
|
||||||
|
// of hiding or showing the popup.
|
||||||
|
if (event.target !== menuButton) {
|
||||||
|
this.setState({ showMenu: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_correctMenuPosition: function() {
|
||||||
|
var menu = this.refs.menu && this.refs.menu.getDOMNode();
|
||||||
|
if (!menu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (menu.style.maxWidth)
|
||||||
|
menu.style.maxWidth = "none";
|
||||||
|
if (menu.style.maxHeight)
|
||||||
|
menu.style.maxHeight = "none";
|
||||||
|
|
||||||
|
// Correct the position of the menu only if necessary.
|
||||||
|
var x, y, boundingBox, boundingRect;
|
||||||
|
// Amount of pixels that the dropdown needs to stay away from the edges of
|
||||||
|
// the page body.
|
||||||
|
var boundOffset = 4;
|
||||||
|
var menuNodeRect = menu.getBoundingClientRect();
|
||||||
|
// If the menu dimensions are constrained to a bounding element, instead of
|
||||||
|
// the document body, find that element.
|
||||||
|
if (boundingBoxSelector) {
|
||||||
|
boundingBox = this.documentBody.querySelector(boundingBoxSelector);
|
||||||
|
if (boundingBox) {
|
||||||
|
boundingRect = boundingBox.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!boundingRect) {
|
||||||
|
boundingRect = {
|
||||||
|
height: this.documentBody.offsetHeight,
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: this.documentBody.offsetWidth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Make sure the menu position will be a certain fixed amount of pixels away
|
||||||
|
// from the border of the bounding box.
|
||||||
|
boundingRect.width -= boundOffset;
|
||||||
|
boundingRect.height -= boundOffset;
|
||||||
|
|
||||||
|
var x = menuNodeRect.left;
|
||||||
|
var y = menuNodeRect.top;
|
||||||
|
|
||||||
|
// If there's an anchor present, position it relative to it first.
|
||||||
|
var anchor = this.refs.anchor && this.refs.anchor.getDOMNode();
|
||||||
|
if (anchor) {
|
||||||
|
// XXXmikedeboer: at the moment we only support positioning centered above
|
||||||
|
// anchor node. Please add more modes as necessary.
|
||||||
|
var anchorNodeRect = anchor.getBoundingClientRect();
|
||||||
|
// Because we're _correcting_ the position of the dropdown, we assume that
|
||||||
|
// the node is positioned absolute at 0,0 coordinates (top left).
|
||||||
|
x = Math.floor(anchorNodeRect.left - (menuNodeRect.width / 2) + (anchorNodeRect.width / 2));
|
||||||
|
y = Math.floor(anchorNodeRect.top - menuNodeRect.height - anchorNodeRect.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
var overflowX = false;
|
||||||
|
var overflowY = false;
|
||||||
|
// Check the horizontal overflow.
|
||||||
|
if (x + menuNodeRect.width > boundingRect.width) {
|
||||||
|
// Anchor positioning is already relative, so don't subtract it again.
|
||||||
|
x = Math.floor(boundingRect.width - ((anchor ? 0 : x) + menuNodeRect.width));
|
||||||
|
overflowX = true;
|
||||||
|
}
|
||||||
|
// Check the vertical overflow.
|
||||||
|
if (y + menuNodeRect.height > boundingRect.height) {
|
||||||
|
// Anchor positioning is already relative, so don't subtract it again.
|
||||||
|
y = Math.floor(boundingRect.height - ((anchor ? 0 : y) + menuNodeRect.height));
|
||||||
|
overflowY = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anchor || overflowX) {
|
||||||
|
// Set the maximum dimensions that the menu DOMNode may grow to. The
|
||||||
|
// content overflow style should be defined in CSS.
|
||||||
|
// Since we don't care much about horizontal overflow currently, this
|
||||||
|
// doesn't really do much for now.
|
||||||
|
if (menuNodeRect.width > boundingRect.width) {
|
||||||
|
menu.classList.add("overflow");
|
||||||
|
menu.style.maxWidth = boundingRect.width + "px";
|
||||||
|
}
|
||||||
|
menu.style.marginLeft = x + "px";
|
||||||
|
} else if (!menu.style.marginLeft) {
|
||||||
|
menu.style.marginLeft = "auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anchor || overflowY) {
|
||||||
|
if (menuNodeRect.height > (boundingRect.height + y)) {
|
||||||
|
menu.classList.add("overflow");
|
||||||
|
// Set the maximum dimensions that the menu DOMNode may grow to. The
|
||||||
|
// content overflow style should be defined in CSS.
|
||||||
|
menu.style.maxHeight = (boundingRect.height + y) + "px";
|
||||||
|
// Since we just adjusted the max-height of the menu - thus its actual
|
||||||
|
// height as well - we need to adjust its vertical offset with the same
|
||||||
|
// amount.
|
||||||
|
y += menuNodeRect.height - (boundingRect.height + y);
|
||||||
|
}
|
||||||
|
menu.style.marginTop = y + "px";
|
||||||
|
} else if (!menu.style.marginLeft) {
|
||||||
|
menu.style.marginTop = "auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.style.visibility = "visible";
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.documentBody.addEventListener("click", this._onBodyClick);
|
||||||
|
rootObject.addEventListener("blur", this.hideDropdownMenu);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this.documentBody.removeEventListener("click", this._onBodyClick);
|
||||||
|
rootObject.removeEventListener("blur", this.hideDropdownMenu);
|
||||||
|
},
|
||||||
|
|
||||||
|
showDropdownMenu: function() {
|
||||||
|
this.setState({showMenu: true}, this._correctMenuPosition);
|
||||||
|
},
|
||||||
|
|
||||||
|
hideDropdownMenu: function() {
|
||||||
|
this.setState({showMenu: false}, function() {
|
||||||
|
var menu = this.refs.menu && this.refs.menu.getDOMNode();
|
||||||
|
if (menu) {
|
||||||
|
menu.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleDropdownMenu: function() {
|
||||||
|
this[this.state.showMenu ? "hideDropdownMenu" : "showDropdownMenu"]();
|
||||||
}
|
}
|
||||||
// If a menu button/ anchor is defined and clicked on, it will be in charge
|
};
|
||||||
// of hiding or showing the popup.
|
|
||||||
if (event.target !== menuButton) {
|
|
||||||
this.setState({ showMenu: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_correctMenuPosition: function() {
|
|
||||||
var menu = this.refs.menu && this.refs.menu.getDOMNode();
|
|
||||||
if (!menu) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Correct the position of the menu only if necessary.
|
|
||||||
var x, y;
|
|
||||||
var menuNodeRect = menu.getBoundingClientRect();
|
|
||||||
var x = menuNodeRect.left;
|
|
||||||
var y = menuNodeRect.top;
|
|
||||||
// Amount of pixels that the dropdown needs to stay away from the edges of
|
|
||||||
// the page body.
|
|
||||||
var bodyMargin = 10;
|
|
||||||
var bodyRect = {
|
|
||||||
height: this.documentBody.offsetHeight - bodyMargin,
|
|
||||||
width: this.documentBody.offsetWidth - bodyMargin
|
|
||||||
};
|
|
||||||
|
|
||||||
// If there's an anchor present, position it relative to it first.
|
|
||||||
var anchor = this.refs.anchor && this.refs.anchor.getDOMNode();
|
|
||||||
if (anchor) {
|
|
||||||
// XXXmikedeboer: at the moment we only support positioning centered above
|
|
||||||
// anchor node. Please add more modes as necessary.
|
|
||||||
var anchorNodeRect = anchor.getBoundingClientRect();
|
|
||||||
// Because we're _correcting_ the position of the dropdown, we assume that
|
|
||||||
// the node is positioned absolute at 0,0 coordinates (top left).
|
|
||||||
x = anchorNodeRect.left - (menuNodeRect.width / 2) + (anchorNodeRect.width / 2);
|
|
||||||
y = anchorNodeRect.top - menuNodeRect.height - anchorNodeRect.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
var overflowX = false;
|
|
||||||
var overflowY = false;
|
|
||||||
// Check the horizontal overflow.
|
|
||||||
if (x + menuNodeRect.width > bodyRect.width) {
|
|
||||||
// Anchor positioning is already relative, so don't subtract it again.
|
|
||||||
x = bodyRect.width - ((anchor ? 0 : x) + menuNodeRect.width);
|
|
||||||
overflowX = true;
|
|
||||||
}
|
|
||||||
// Check the vertical overflow.
|
|
||||||
if (y + menuNodeRect.height > bodyRect.height) {
|
|
||||||
// Anchor positioning is already relative, so don't subtract it again.
|
|
||||||
y = bodyRect.height - ((anchor ? 0 : y) + menuNodeRect.height);
|
|
||||||
overflowY = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anchor || overflowX) {
|
|
||||||
menu.style.marginLeft = x + "px";
|
|
||||||
} else if (!menu.style.marginLeft) {
|
|
||||||
menu.style.marginLeft = "auto";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anchor || overflowY) {
|
|
||||||
menu.style.marginTop = y + "px";
|
|
||||||
} else if (!menu.style.marginLeft) {
|
|
||||||
menu.style.marginTop = "auto";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
this.documentBody.addEventListener("click", this._onBodyClick);
|
|
||||||
rootObject.addEventListener("blur", this.hideDropdownMenu);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
this.documentBody.removeEventListener("click", this._onBodyClick);
|
|
||||||
rootObject.removeEventListener("blur", this.hideDropdownMenu);
|
|
||||||
},
|
|
||||||
|
|
||||||
showDropdownMenu: function() {
|
|
||||||
this.setState({showMenu: true});
|
|
||||||
rootObject.setTimeout(this._correctMenuPosition, 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
hideDropdownMenu: function() {
|
|
||||||
this.setState({showMenu: false});
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleDropdownMenu: function() {
|
|
||||||
this[this.state.showMenu ? "hideDropdownMenu" : "showDropdownMenu"]();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -260,6 +260,25 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||||
return { major: Infinity, minor: 0 };
|
return { major: Infinity, minor: 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get the current short platform string, based on the return value
|
||||||
|
* of `getOS`.
|
||||||
|
* Possible return values are 'mac', 'win' or 'other'.
|
||||||
|
*
|
||||||
|
* @param {String} [os] Optional string for the OS, used in tests only.
|
||||||
|
* @return {String} 'mac', 'win' or 'other'.
|
||||||
|
*/
|
||||||
|
var getPlatform = function(os) {
|
||||||
|
os = getOS(os);
|
||||||
|
var platform = "other";
|
||||||
|
if (os.indexOf("mac") > -1) {
|
||||||
|
platform = "mac";
|
||||||
|
} else if (os.indexOf("win") > -1) {
|
||||||
|
platform = "win";
|
||||||
|
}
|
||||||
|
return platform;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to allow getting some of the location data in a way that's compatible
|
* Helper to allow getting some of the location data in a way that's compatible
|
||||||
* with stubbing for unit tests.
|
* with stubbing for unit tests.
|
||||||
|
@ -632,6 +651,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||||
getBoolPreference: getBoolPreference,
|
getBoolPreference: getBoolPreference,
|
||||||
getOS: getOS,
|
getOS: getOS,
|
||||||
getOSVersion: getOSVersion,
|
getOSVersion: getOSVersion,
|
||||||
|
getPlatform: getPlatform,
|
||||||
isChrome: isChrome,
|
isChrome: isChrome,
|
||||||
isFirefox: isFirefox,
|
isFirefox: isFirefox,
|
||||||
isFirefoxOS: isFirefoxOS,
|
isFirefoxOS: isFirefoxOS,
|
||||||
|
|
|
@ -83,7 +83,7 @@ loop.shared.views = (function(_, l10n) {
|
||||||
* loop.shared.utils.SCREEN_SHARE_STATES
|
* loop.shared.utils.SCREEN_SHARE_STATES
|
||||||
*/
|
*/
|
||||||
var ScreenShareControlButton = React.createClass({displayName: "ScreenShareControlButton",
|
var ScreenShareControlButton = React.createClass({displayName: "ScreenShareControlButton",
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin()],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
@ -151,7 +151,8 @@ loop.shared.views = (function(_, l10n) {
|
||||||
var dropdownMenuClasses = cx({
|
var dropdownMenuClasses = cx({
|
||||||
"native-dropdown-menu": true,
|
"native-dropdown-menu": true,
|
||||||
"conversation-window-dropdown": true,
|
"conversation-window-dropdown": true,
|
||||||
"visually-hidden": !this.state.showMenu
|
"hide": !this.state.showMenu,
|
||||||
|
"visually-hidden": true
|
||||||
});
|
});
|
||||||
var windowSharingClasses = cx({
|
var windowSharingClasses = cx({
|
||||||
"disabled": this.state.windowSharingDisabled
|
"disabled": this.state.windowSharingDisabled
|
||||||
|
|
|
@ -83,7 +83,7 @@ loop.shared.views = (function(_, l10n) {
|
||||||
* loop.shared.utils.SCREEN_SHARE_STATES
|
* loop.shared.utils.SCREEN_SHARE_STATES
|
||||||
*/
|
*/
|
||||||
var ScreenShareControlButton = React.createClass({
|
var ScreenShareControlButton = React.createClass({
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin()],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
@ -151,7 +151,8 @@ loop.shared.views = (function(_, l10n) {
|
||||||
var dropdownMenuClasses = cx({
|
var dropdownMenuClasses = cx({
|
||||||
"native-dropdown-menu": true,
|
"native-dropdown-menu": true,
|
||||||
"conversation-window-dropdown": true,
|
"conversation-window-dropdown": true,
|
||||||
"visually-hidden": !this.state.showMenu
|
"hide": !this.state.showMenu,
|
||||||
|
"visually-hidden": true
|
||||||
});
|
});
|
||||||
var windowSharingClasses = cx({
|
var windowSharingClasses = cx({
|
||||||
"disabled": this.state.windowSharingDisabled
|
"disabled": this.state.windowSharingDisabled
|
||||||
|
|
|
@ -386,7 +386,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
});
|
});
|
||||||
|
|
||||||
var InitiateCallButton = React.createClass({displayName: "InitiateCallButton",
|
var InitiateCallButton = React.createClass({displayName: "InitiateCallButton",
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin()],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
caption: React.PropTypes.string.isRequired,
|
caption: React.PropTypes.string.isRequired,
|
||||||
|
|
|
@ -386,7 +386,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
});
|
});
|
||||||
|
|
||||||
var InitiateCallButton = React.createClass({
|
var InitiateCallButton = React.createClass({
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin()],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
caption: React.PropTypes.string.isRequired,
|
caption: React.PropTypes.string.isRequired,
|
||||||
|
|
|
@ -319,6 +319,43 @@ describe("loop.shared.utils", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#getPlatform", function() {
|
||||||
|
it("should recognize the OSX userAgent string", function() {
|
||||||
|
var UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0";
|
||||||
|
var result = sharedUtils.getPlatform(UA);
|
||||||
|
|
||||||
|
expect(result).eql("mac");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should recognize the Windows userAgent string", function() {
|
||||||
|
var UA = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0";
|
||||||
|
var result = sharedUtils.getPlatform(UA);
|
||||||
|
|
||||||
|
expect(result).eql("win");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should recognize the Linux userAgent string", function() {
|
||||||
|
var UA = "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:10.0) Gecko/20100101 Firefox/10.0";
|
||||||
|
var result = sharedUtils.getPlatform(UA);
|
||||||
|
|
||||||
|
expect(result).eql("other");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should recognize the OSX oscpu string", function() {
|
||||||
|
var oscpu = "Intel Mac OS X 10.10";
|
||||||
|
var result = sharedUtils.getPlatform(oscpu);
|
||||||
|
|
||||||
|
expect(result).eql("mac");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should recognize the Windows oscpu string", function() {
|
||||||
|
var oscpu = "Windows NT 5.3; Win64; x64";
|
||||||
|
var result = sharedUtils.getPlatform(oscpu);
|
||||||
|
|
||||||
|
expect(result).eql("win");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("#objectDiff", function() {
|
describe("#objectDiff", function() {
|
||||||
var a, b, diff;
|
var a, b, diff;
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче