mirror of
https://github.com/wassname/talk.git
synced 2026-06-30 02:56:56 +08:00
Refactor StreamTabPanel and use hoistStatics for all hocs
This commit is contained in:
@@ -72,7 +72,7 @@ const ActionButton = ({children}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default class Comment extends React.Component {
|
||||
export default class Comment extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -10,16 +10,16 @@ import {ModerationLink} from 'talk-plugin-moderation';
|
||||
import RestrictedMessageBox
|
||||
from 'coral-framework/components/RestrictedMessageBox';
|
||||
import t, {timeago} from 'coral-framework/services/i18n';
|
||||
import {getSlotComponents} from 'coral-framework/helpers/plugins';
|
||||
import CommentBox from 'talk-plugin-commentbox/CommentBox';
|
||||
import QuestionBox from 'talk-plugin-questionbox/QuestionBox';
|
||||
import {isCommentActive} from 'coral-framework/utils';
|
||||
import {Button, TabBar, Tab, TabCount, TabContent, TabPane} from 'coral-ui';
|
||||
import {Button, Tab, TabCount, TabPane} from 'coral-ui';
|
||||
import cn from 'classnames';
|
||||
|
||||
import {getTopLevelParent, attachCommentToParent} from '../graphql/utils';
|
||||
import AllCommentsPane from './AllCommentsPane';
|
||||
import AutomaticAssetClosure from '../containers/AutomaticAssetClosure';
|
||||
import StreamTabPanel from '../containers/StreamTabPanel';
|
||||
|
||||
import styles from './Stream.css';
|
||||
|
||||
@@ -35,44 +35,9 @@ class Stream extends React.Component {
|
||||
componentWillReceiveProps(next) {
|
||||
|
||||
// Keep comment box when user was live suspended, banned, ...
|
||||
if (!this.userIsDegraged(this.props) && this.userIsDegraged(next)) {
|
||||
if (!this.props.userIsDegraged && next.userIsDegraged) {
|
||||
this.setState({keepCommentBox: true});
|
||||
}
|
||||
|
||||
this.fallbackAllTab(next);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fallbackAllTab();
|
||||
}
|
||||
|
||||
fallbackAllTab(props = this.props) {
|
||||
if (props.activeStreamTab !== 'all') {
|
||||
const slotPlugins = this.getSlotComponents('streamTabs', props).map((c) => c.talkPluginName);
|
||||
if (slotPlugins.indexOf(props.activeStreamTab) === -1) {
|
||||
props.setActiveStreamTab('all');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSlotProps({data, root, root: {asset}} = this.props) {
|
||||
return {data, root, asset};
|
||||
}
|
||||
|
||||
getSlotComponents(slot, props = this.props) {
|
||||
return getSlotComponents(slot, props.reduxState, this.getSlotProps(props));
|
||||
}
|
||||
|
||||
setActiveReplyBox = (id) => {
|
||||
if (!this.props.auth.user) {
|
||||
this.props.showSignInDialog();
|
||||
} else {
|
||||
this.props.setActiveReplyBox(id);
|
||||
}
|
||||
};
|
||||
|
||||
userIsDegraged({auth: {user}} = this.props) {
|
||||
return !can(user, 'INTERACT_WITH_COMMUNITY');
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -99,7 +64,7 @@ class Stream extends React.Component {
|
||||
loadMoreComments,
|
||||
viewAllComments,
|
||||
auth: {loggedIn, user},
|
||||
editName
|
||||
editName,
|
||||
} = this.props;
|
||||
const {keepCommentBox} = this.state;
|
||||
const open = !asset.isClosed;
|
||||
@@ -133,7 +98,7 @@ class Stream extends React.Component {
|
||||
};
|
||||
|
||||
const showCommentBox = loggedIn && ((!banned && !temporarilySuspended && !highlightedComment) || keepCommentBox);
|
||||
const slotProps = this.getSlotProps();
|
||||
const slotProps = {data, root, asset};
|
||||
|
||||
if (!comment && !comments) {
|
||||
console.error('Talk: No comments came back from the graph given that query. Please, check the query params.');
|
||||
@@ -247,55 +212,49 @@ class Stream extends React.Component {
|
||||
{...slotProps}
|
||||
/>
|
||||
</div>
|
||||
<TabBar activeTab={activeStreamTab} onTabClick={setActiveStreamTab} sub>
|
||||
{this.getSlotComponents('streamTabs').map((PluginComponent) => (
|
||||
<Tab tabId={PluginComponent.talkPluginName} key={PluginComponent.talkPluginName}>
|
||||
<PluginComponent
|
||||
{...slotProps}
|
||||
active={activeStreamTab === PluginComponent.talkPluginName}
|
||||
/>
|
||||
<StreamTabPanel
|
||||
activeTab={activeStreamTab}
|
||||
setActiveTab={setActiveStreamTab}
|
||||
fallbackTab={'all'}
|
||||
tabSlot={'streamTabs'}
|
||||
tabPaneSlot={'streamTabPanes'}
|
||||
slotProps={slotProps}
|
||||
appendTabs={
|
||||
<Tab tabId={'all'}>
|
||||
All Comments <TabCount active={activeStreamTab === 'all'} sub>{totalCommentCount}</TabCount>
|
||||
</Tab>
|
||||
))}
|
||||
<Tab tabId={'all'}>
|
||||
All Comments <TabCount active={activeStreamTab === 'all'} sub>{totalCommentCount}</TabCount>
|
||||
</Tab>
|
||||
</TabBar>
|
||||
<TabContent activeTab={activeStreamTab} sub>
|
||||
{this.getSlotComponents('streamTabPanes').map((PluginComponent) => (
|
||||
<TabPane tabId={PluginComponent.talkPluginName} key={PluginComponent.talkPluginName}>
|
||||
<PluginComponent
|
||||
{...slotProps}
|
||||
}
|
||||
appendTabPanes={
|
||||
<TabPane tabId={'all'}>
|
||||
<AllCommentsPane
|
||||
data={data}
|
||||
root={root}
|
||||
comments={comments}
|
||||
commentClassNames={commentClassNames}
|
||||
ignoreUser={ignoreUser}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
activeReplyBox={activeReplyBox}
|
||||
addNotification={addNotification}
|
||||
disableReply={!open}
|
||||
postComment={postComment}
|
||||
asset={asset}
|
||||
currentUser={user}
|
||||
postFlag={postFlag}
|
||||
postDontAgree={postDontAgree}
|
||||
loadMore={loadMoreComments}
|
||||
loadNewReplies={loadNewReplies}
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
commentIsIgnored={commentIsIgnored}
|
||||
charCountEnable={asset.settings.charCountEnable}
|
||||
maxCharCount={asset.settings.charCount}
|
||||
editComment={editComment}
|
||||
emit={this.props.emit}
|
||||
/>
|
||||
</TabPane>
|
||||
))}
|
||||
<TabPane tabId={'all'}>
|
||||
<AllCommentsPane
|
||||
data={data}
|
||||
root={root}
|
||||
comments={comments}
|
||||
commentClassNames={commentClassNames}
|
||||
ignoreUser={ignoreUser}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
activeReplyBox={activeReplyBox}
|
||||
addNotification={addNotification}
|
||||
disableReply={!open}
|
||||
postComment={postComment}
|
||||
asset={asset}
|
||||
currentUser={user}
|
||||
postFlag={postFlag}
|
||||
postDontAgree={postDontAgree}
|
||||
loadMore={loadMoreComments}
|
||||
loadNewReplies={loadNewReplies}
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
commentIsIgnored={commentIsIgnored}
|
||||
charCountEnable={asset.settings.charCountEnable}
|
||||
maxCharCount={asset.settings.charCount}
|
||||
editComment={editComment}
|
||||
emit={this.props.emit}
|
||||
/>
|
||||
</TabPane>
|
||||
</TabContent>
|
||||
}
|
||||
sub
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import {TabBar, TabContent} from 'coral-ui';
|
||||
|
||||
class StreamTabPanel extends React.Component {
|
||||
|
||||
render() {
|
||||
const {activeTab, setActiveTab, appendTabs, appendTabPanes, pluginTabElements, pluginTabPaneElements, sub} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<TabBar activeTab={activeTab} onTabClick={setActiveTab} sub={sub}>
|
||||
{pluginTabElements}
|
||||
{appendTabs}
|
||||
</TabBar>
|
||||
<TabContent activeTab={activeTab} sub={sub}>
|
||||
{pluginTabPaneElements}
|
||||
{appendTabPanes}
|
||||
</TabContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default StreamTabPanel;
|
||||
@@ -16,6 +16,7 @@ import Comment from './Comment';
|
||||
import {withFragments, withEmit} from 'coral-framework/hocs';
|
||||
import {getDefinitionName, getSlotFragmentSpreads} from 'coral-framework/utils';
|
||||
import {Spinner} from 'coral-ui';
|
||||
import {can} from 'coral-framework/services/perms';
|
||||
import {
|
||||
findCommentInEmbedQuery,
|
||||
insertCommentIntoEmbedQuery,
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
insertFetchedCommentsIntoEmbedQuery,
|
||||
nest,
|
||||
} from '../graphql/utils';
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
const {showSignInDialog, editName} = authActions;
|
||||
const {addNotification} = notificationActions;
|
||||
@@ -140,6 +140,10 @@ class StreamContainer extends React.Component {
|
||||
clearInterval(this.countPoll);
|
||||
}
|
||||
|
||||
userIsDegraged({auth: {user}} = this.props) {
|
||||
return !can(user, 'INTERACT_WITH_COMMUNITY');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.refetching) {
|
||||
return <Spinner />;
|
||||
@@ -149,6 +153,7 @@ class StreamContainer extends React.Component {
|
||||
loadMore={this.loadMore}
|
||||
loadMoreComments={this.loadMoreComments}
|
||||
loadNewReplies={this.loadNewReplies}
|
||||
userIsDegraged={this.userIsDegraged()}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
@@ -310,7 +315,6 @@ const mapStateToProps = (state) => ({
|
||||
previousStreamTab: state.stream.previousTab,
|
||||
commentClassNames: state.stream.commentClassNames,
|
||||
pluginConfig: state.config.plugin_config,
|
||||
reduxState: omit(state, 'apollo'),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import StreamTabPanel from '../components/StreamTabPanel';
|
||||
import {connect} from 'react-redux';
|
||||
import omit from 'lodash/omit';
|
||||
import {getSlotComponents} from 'coral-framework/helpers/plugins';
|
||||
import {Tab, TabPane} from 'coral-ui';
|
||||
import {getShallowChanges} from 'coral-framework/utils';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
class StreamTabPanelContainer extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.fallbackAllTab();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(next) {
|
||||
this.fallbackAllTab(next);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(next) {
|
||||
|
||||
// Prevent Slot from rerendering when only reduxState has changed and
|
||||
// it does not result in a change of slot children.
|
||||
const changes = getShallowChanges(this.props, next);
|
||||
if (changes.length === 1 && changes[0] === 'reduxState') {
|
||||
const prevUuid = this.getSlotComponents(this.props.tabSlot, this.props).map((cmp) => cmp.talkUuid);
|
||||
const nextUuid = this.getSlotComponents(next.tabSlot, next).map((cmp) => cmp.talkUuid);
|
||||
return !isEqual(prevUuid, nextUuid);
|
||||
}
|
||||
|
||||
// Prevent Slot from rerendering when no props has shallowly changed.
|
||||
return changes.length !== 0;
|
||||
}
|
||||
|
||||
fallbackAllTab(props = this.props) {
|
||||
if (props.activeTab !== props.fallbackTab) {
|
||||
const slotPlugins = this.getSlotComponents(props.tabSlot, props).map((c) => c.talkPluginName);
|
||||
if (slotPlugins.indexOf(props.activeTab) === -1) {
|
||||
props.setActiveTab(props.fallbackTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSlotComponents(slot, props = this.props) {
|
||||
return getSlotComponents(slot, props.reduxState, props.slotProps);
|
||||
}
|
||||
|
||||
getPluginTabElements(props = this.props) {
|
||||
return this.getSlotComponents(props.tabSlot).map((PluginComponent) => (
|
||||
<Tab tabId={PluginComponent.talkPluginName} key={PluginComponent.talkPluginName}>
|
||||
<PluginComponent
|
||||
{...props.slotProps}
|
||||
active={this.props.activeTab === PluginComponent.talkPluginName}
|
||||
/>
|
||||
</Tab>
|
||||
));
|
||||
}
|
||||
|
||||
getPluginTabPaneElements(props = this.props) {
|
||||
return this.getSlotComponents(props.tabPaneSlot).map((PluginComponent) => (
|
||||
<TabPane tabId={PluginComponent.talkPluginName} key={PluginComponent.talkPluginName}>
|
||||
<PluginComponent
|
||||
{...props.slotProps}
|
||||
/>
|
||||
</TabPane>
|
||||
));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StreamTabPanel
|
||||
{...this.props}
|
||||
pluginTabElements={this.getPluginTabElements()}
|
||||
pluginTabPaneElements={this.getPluginTabPaneElements()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
reduxState: omit(state, 'apollo'),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(StreamTabPanelContainer);
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Clipboard from 'clipboard';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
|
||||
export default (WrappedComponent) => {
|
||||
export default hoistStatics((WrappedComponent) => {
|
||||
class WithCopyToClipboard extends React.Component {
|
||||
componentDidMount() {
|
||||
const clipboard = new Clipboard(ReactDOM.findDOMNode(this));
|
||||
@@ -26,4 +27,4 @@ export default (WrappedComponent) => {
|
||||
}
|
||||
|
||||
return WithCopyToClipboard;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React from 'react';
|
||||
const PropTypes = require('prop-types');
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* WithEmit provides a property `emit: (eventName, value)`
|
||||
* to the wrapped component.
|
||||
*/
|
||||
export default (WrappedComponent) => {
|
||||
export default hoistStatics((WrappedComponent) => {
|
||||
class WithEmit extends React.Component {
|
||||
static contextTypes = {
|
||||
eventEmitter: PropTypes.object,
|
||||
@@ -24,4 +25,4 @@ export default (WrappedComponent) => {
|
||||
}
|
||||
|
||||
return WithEmit;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
// TODO: revisit `filtering` after https://github.com/apollographql/graphql-anywhere/issues/38.
|
||||
export default (fragments) => (BaseComponent) => {
|
||||
BaseComponent.fragments = fragments;
|
||||
return BaseComponent;
|
||||
};
|
||||
|
||||
import React from 'react';
|
||||
import {resolveFragments} from 'coral-framework/services/graphqlRegistry';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
|
||||
export default (fragments) => hoistStatics((BaseComponent) => {
|
||||
class WithFragments extends React.Component {
|
||||
fragments = mapValues(fragments, (val) => resolveFragments(val));
|
||||
|
||||
render() {
|
||||
return <BaseComponent
|
||||
{...this.props}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
WithFragments.fragments = fragments;
|
||||
return WithFragments;
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {getMutationOptions, resolveFragments} from 'coral-framework/services/gra
|
||||
import {getDefinitionName, getResponseErrors} from '../utils';
|
||||
import PropTypes from 'prop-types';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
|
||||
class ResponseErrors extends Error {
|
||||
constructor(errors) {
|
||||
@@ -30,7 +31,7 @@ class ResponseError {
|
||||
* Exports a HOC with the same signature as `graphql`, that will
|
||||
* apply mutation options registered in the graphRegistry.
|
||||
*/
|
||||
export default (document, config = {}) => (WrappedComponent) => {
|
||||
export default (document, config = {}) => hoistStatics((WrappedComponent) => {
|
||||
config = {
|
||||
...config,
|
||||
options: config.options || {},
|
||||
@@ -147,4 +148,4 @@ export default (document, config = {}) => (WrappedComponent) => {
|
||||
return <Wrapped {...this.props} />;
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {graphql} from 'react-apollo';
|
||||
import {getQueryOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry';
|
||||
import {getDefinitionName, separateDataAndRoot, getResponseErrors} from '../utils';
|
||||
import PropTypes from 'prop-types';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
|
||||
const withSkipOnErrors = (reducer) => (prev, action, ...rest) => {
|
||||
if (action.type === 'APOLLO_MUTATION_RESULT' && getResponseErrors(action.result)) {
|
||||
@@ -35,7 +36,7 @@ function networkStatusToString(networkStatus) {
|
||||
* Exports a HOC with the same signature as `graphql`, that will
|
||||
* apply query options registered in the graphRegistry.
|
||||
*/
|
||||
export default (document, config = {}) => (WrappedComponent) => {
|
||||
export default (document, config = {}) => hoistStatics((WrappedComponent) => {
|
||||
const name = getDefinitionName(document);
|
||||
|
||||
return class WithQuery extends React.Component {
|
||||
@@ -190,4 +191,4 @@ export default (document, config = {}) => (WrappedComponent) => {
|
||||
return <Wrapped {...this.props} />;
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,8 +8,9 @@ import {withAddTag, withRemoveTag} from 'coral-framework/graphql/mutations';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import {addNotification} from 'coral-framework/actions/notification';
|
||||
import {forEachError, isTagged} from 'coral-framework/utils';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
|
||||
export default (tag) => (WrappedComponent) => {
|
||||
export default (tag) => hoistStatics((WrappedComponent) => {
|
||||
if (typeof tag !== 'string') {
|
||||
console.error('Tag must be a valid string');
|
||||
return null;
|
||||
@@ -109,4 +110,4 @@ export default (tag) => (WrappedComponent) => {
|
||||
WithTags.displayName = `WithTags(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
return enhance(WithTags);
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user