From 49420e152e7e2991dee0a796ee4898f2747e0bca Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 20 Jun 2017 22:30:26 +0700 Subject: [PATCH] Click outside closes TopRightMenu, detect click outside iframe --- .eslintrc.json | 3 +- .../Moderation/components/UserDetail.js | 165 +++++++++--------- .../{TopRightMenu.css => Toggleable.css} | 0 .../src/components/Toggleable.js | 39 +++++ .../src/components/TopRightMenu.js | 36 +--- client/coral-embed/src/index.js | 5 + .../components/ClickOutside.js | 35 ++++ client/coral-plugin-flags/FlagButton.js | 132 +++++++------- .../PermalinkButton.js | 58 +++--- client/coral-ui/components/Drawer.js | 9 +- package.json | 1 - yarn.lock | 8 +- 12 files changed, 267 insertions(+), 224 deletions(-) rename client/coral-embed-stream/src/components/{TopRightMenu.css => Toggleable.css} (100%) create mode 100644 client/coral-embed-stream/src/components/Toggleable.js create mode 100644 client/coral-framework/components/ClickOutside.js diff --git a/.eslintrc.json b/.eslintrc.json index 2186efd8b..12355ac34 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -53,7 +53,8 @@ "no-lonely-if": [2], "curly": [2], "no-unused-vars": ["error", { - "argsIgnorePattern": "next" + "argsIgnorePattern": "^_|next", + "varsIgnorePattern": "^_" }], "no-multiple-empty-lines": ["error", { "max": 1 diff --git a/client/coral-admin/src/routes/Moderation/components/UserDetail.js b/client/coral-admin/src/routes/Moderation/components/UserDetail.js index abc9f4440..b45f20b78 100644 --- a/client/coral-admin/src/routes/Moderation/components/UserDetail.js +++ b/client/coral-admin/src/routes/Moderation/components/UserDetail.js @@ -4,6 +4,7 @@ import styles from './UserDetail.css'; import Slot from 'coral-framework/components/Slot'; import Comment from './Comment'; import {actionsMap} from '../helpers/moderationQueueActionsMap'; +import ClickOutside from 'coral-framework/components/ClickOutside'; export default class UserDetail extends React.Component { @@ -96,92 +97,94 @@ export default class UserDetail extends React.Component { } return ( - -

{user.username}

- {profile && this.profile = ref} value={profile} />} + + +

{user.username}

+ {profile && this.profile = ref} value={profile} />} - this.showCopied()} text={profile} className={styles.profileEmail}> - - + this.showCopied()} text={profile} className={styles.profileEmail}> + + - -

Member since {new Date(user.created_at).toLocaleString()}

-
-

- Account summary -
Data represents the last six months of activity -

-
-
-

Total Comments

-

{totalComments}

-
-
-

Reject Rate

-

{`${(rejectedPercent).toFixed(1)}%`}

-
-
- { - selectedIds.length === 0 - ? ( -
    -
  • All
  • -
  • Rejected
  • -
- ) - : ( -
- - - {`${selectedIds.length} comments selected`} + +

Member since {new Date(user.created_at).toLocaleString()}

+
+

+ Account summary +
Data represents the last six months of activity +

+
+
+

Total Comments

+

{totalComments}

- ) - } - -
+
+

Reject Rate

+

{`${(rejectedPercent).toFixed(1)}%`}

+
+
{ - nodes.map((comment, i) => { - const status = comment.action_summaries ? 'FLAGGED' : comment.status; - const selected = selectedIds.indexOf(comment.id) !== -1; - return {}} - actions={actionsMap[status]} - showBanUserDialog={showBanUserDialog} - showSuspendUserDialog={showSuspendUserDialog} - acceptComment={this.acceptThenReload} - rejectComment={this.rejectThenReload} - selected={selected} - toggleSelect={toggleSelect} - currentAsset={null} - currentUserId={this.props.id} - minimal={true} />; - }) + selectedIds.length === 0 + ? ( +
    +
  • All
  • +
  • Rejected
  • +
+ ) + : ( +
+ + + {`${selectedIds.length} comments selected`} +
+ ) } -
- + +
+ { + nodes.map((comment, i) => { + const status = comment.action_summaries ? 'FLAGGED' : comment.status; + const selected = selectedIds.indexOf(comment.id) !== -1; + return {}} + actions={actionsMap[status]} + showBanUserDialog={showBanUserDialog} + showSuspendUserDialog={showSuspendUserDialog} + acceptComment={this.acceptThenReload} + rejectComment={this.rejectThenReload} + selected={selected} + toggleSelect={toggleSelect} + currentAsset={null} + currentUserId={this.props.id} + minimal={true} />; + }) + } +
+ + ); } } diff --git a/client/coral-embed-stream/src/components/TopRightMenu.css b/client/coral-embed-stream/src/components/Toggleable.css similarity index 100% rename from client/coral-embed-stream/src/components/TopRightMenu.css rename to client/coral-embed-stream/src/components/Toggleable.css diff --git a/client/coral-embed-stream/src/components/Toggleable.js b/client/coral-embed-stream/src/components/Toggleable.js new file mode 100644 index 000000000..7dc6af5e9 --- /dev/null +++ b/client/coral-embed-stream/src/components/Toggleable.js @@ -0,0 +1,39 @@ +import React from 'react'; +import ClickOutside from 'coral-framework/components/ClickOutside'; +import styles from './Toggleable.css'; +import classnames from 'classnames'; + +const upArrow = ; +const downArrow = ; +export default class Toggleable extends React.Component { + constructor(props) { + super(props); + this.toggle = this.toggle.bind(this); + this.close = this.close.bind(this); + this.state = { + isOpen: false + }; + } + toggle() { + this.setState({isOpen: !this.state.isOpen}); + } + + close = () => { + this.setState({isOpen: false}); + } + + render() { + const {children} = this.props; + const {isOpen} = this.state; + return ( + + + {isOpen ? upArrow : downArrow} + {isOpen ? children : null} + + + ); + } +} + diff --git a/client/coral-embed-stream/src/components/TopRightMenu.js b/client/coral-embed-stream/src/components/TopRightMenu.js index 84d64f061..d65722b6d 100644 --- a/client/coral-embed-stream/src/components/TopRightMenu.js +++ b/client/coral-embed-stream/src/components/TopRightMenu.js @@ -1,8 +1,6 @@ import React, {PropTypes} from 'react'; -import classnames from 'classnames'; - import {IgnoreUserWizard} from './IgnoreUserWizard'; -import styles from './TopRightMenu.css'; +import Toggleable from './Toggleable'; // TopRightMenu appears as a dropdown in the top right of the comment. // when you click the down cehvron, it expands and shows IgnoreUserWizard @@ -56,38 +54,6 @@ export class TopRightMenu extends React.Component { />
- ); - } -} - -const upArrow = ; -const downArrow = ; -class Toggleable extends React.Component { - constructor(props) { - super(props); - this.toggle = this.toggle.bind(this); - this.close = this.close.bind(this); - this.state = { - isOpen: false - }; - } - toggle() { - this.setState({isOpen: !this.state.isOpen}); - } - close() { - this.setState({isOpen: false}); - } - render() { - const {children} = this.props; - const {isOpen} = this.state; - return ( - - // /*onBlur={ this.close } */ - - {isOpen ? upArrow : downArrow} - {isOpen ? children : null} - ); } } diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js index 14de2756e..5b42fa541 100644 --- a/client/coral-embed/src/index.js +++ b/client/coral-embed/src/index.js @@ -73,6 +73,11 @@ function configurePymParent(pymParent, opts) { window.document.body.appendChild(snackbar); + // Notify embed that there was a click outside. + document.addEventListener('click', () => { + pymParent.sendMessage('click'); + }, true); + // Workaround: IOS Safari ignores `width` but respects `min-width` value. pymParent.el.firstChild.style.width = '1px'; pymParent.el.firstChild.style.minWidth = '100%'; diff --git a/client/coral-framework/components/ClickOutside.js b/client/coral-framework/components/ClickOutside.js new file mode 100644 index 000000000..e13ef4472 --- /dev/null +++ b/client/coral-framework/components/ClickOutside.js @@ -0,0 +1,35 @@ +import {Component, cloneElement, Children} from 'react'; +import PropTypes from 'prop-types'; +import {findDOMNode} from 'react-dom'; +import {pym} from 'coral-framework'; + +export default class ClickOutside extends Component { + static propTypes = { + onClickOutside: PropTypes.func.isRequired + }; + + domNode = null; + + handleClick = (e) => { + const {onClickOutside} = this.props; + if (!e || !this.domNode.contains(e.target)) { + onClickOutside(e); + } + }; + + componentDidMount() { + this.domNode = findDOMNode(this); + document.addEventListener('click', this.handleClick, true); + pym.onMessage('click', this.handleClick); + } + + componentWillUnmount() { + document.removeEventListener('click', this.handleClick, true); + pym.messageHandlers.click = pym.messageHandlers.click.filter((h) => h !== this.handleClick); + } + + render() { + const {children, onClickOutside: _, ...rest} = this.props; + return cloneElement(Children.only(children), rest); + } +} diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index 73ed50e9c..e8a17f201 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -4,11 +4,11 @@ import t from 'coral-framework/services/i18n'; import {can} from 'coral-framework/services/perms'; import {PopupMenu, Button} from 'coral-ui'; -import onClickOutside from 'react-onclickoutside'; +import ClickOutside from 'coral-framework/components/ClickOutside'; const name = 'coral-plugin-flags'; -class FlagButton extends Component { +export default class FlagButton extends Component { state = { showMenu: false, @@ -128,7 +128,7 @@ class FlagButton extends Component { this.setState({message: e.target.value}); } - handleClickOutside () { + handleClickOutside = () => { this.closeMenu(); } @@ -138,79 +138,81 @@ class FlagButton extends Component { const flagged = flaggedByCurrentUser || localPost; const popupMenu = getPopupMenu[this.state.step](this.state.itemType); - return
- - { - this.state.showMenu && -
this.popup = ref}> - -
{popupMenu.header}
+ return ( + +
+ + { + this.state.showMenu && +
this.popup = ref}> + +
{popupMenu.header}
{ - popupMenu.options.map((option) => -
- -
-
- ) + popupMenu.text && +
{popupMenu.text}
} { - this.state.reason &&
-
-