mirror of
https://github.com/wassname/talk.git
synced 2026-08-16 11:29:31 +08:00
Merge branch 'master' into slots-debug
This commit is contained in:
@@ -14,7 +14,7 @@ const buildUserHistory = (userState = {}) => {
|
||||
return orderBy(
|
||||
flatten(
|
||||
Object.keys(userState.status)
|
||||
.filter(k => k !== '__typename')
|
||||
.filter(k => !k.startsWith('__'))
|
||||
.map(k => userState.status[k].history)
|
||||
),
|
||||
'created_at',
|
||||
|
||||
@@ -25,37 +25,29 @@ class CommentDetails extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data, root, comment, clearHeightCache } = this.props;
|
||||
const { root, comment, clearHeightCache } = this.props;
|
||||
const { showDetail } = this.state;
|
||||
const queryData = {
|
||||
|
||||
const slotPassthrough = {
|
||||
clearHeightCache,
|
||||
root,
|
||||
comment,
|
||||
more: showDetail,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<IfSlotIsNotEmpty
|
||||
queryData={queryData}
|
||||
slot={['adminCommentMoreDetails', 'adminCommentMoreFlagDetails']}
|
||||
passthrough={slotPassthrough}
|
||||
>
|
||||
<a onClick={this.toggleDetail} className={styles.moreDetail}>
|
||||
{showDetail ? t('modqueue.less_detail') : t('modqueue.more_detail')}
|
||||
</a>
|
||||
</IfSlotIsNotEmpty>
|
||||
<Slot
|
||||
fill="adminCommentDetailArea"
|
||||
data={data}
|
||||
clearHeightCache={clearHeightCache}
|
||||
queryData={queryData}
|
||||
more={showDetail}
|
||||
/>
|
||||
<Slot fill="adminCommentDetailArea" passthrough={slotPassthrough} />
|
||||
{showDetail && (
|
||||
<Slot
|
||||
fill="adminCommentMoreDetails"
|
||||
data={data}
|
||||
clearHeightCache={clearHeightCache}
|
||||
queryData={queryData}
|
||||
/>
|
||||
<Slot fill="adminCommentMoreDetails" passthrough={slotPassthrough} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -63,7 +55,6 @@ class CommentDetails extends Component {
|
||||
}
|
||||
|
||||
CommentDetails.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
comment: PropTypes.object.isRequired,
|
||||
clearHeightCache: PropTypes.func,
|
||||
|
||||
@@ -43,6 +43,9 @@ const CommentLabels = ({
|
||||
comment,
|
||||
comment: { className, status, actions, hasParent },
|
||||
}) => {
|
||||
const slotPassthrough = {
|
||||
comment,
|
||||
};
|
||||
return (
|
||||
<div className={cn(className, styles.root)}>
|
||||
<div className={styles.coreLabels}>
|
||||
@@ -69,7 +72,7 @@ const CommentLabels = ({
|
||||
<Slot
|
||||
className={styles.slot}
|
||||
fill="adminCommentLabels"
|
||||
queryData={{ comment }}
|
||||
passthrough={slotPassthrough}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -98,7 +98,6 @@ class UserDetail extends React.Component {
|
||||
|
||||
renderLoaded() {
|
||||
const {
|
||||
data,
|
||||
root,
|
||||
root: { me, user, totalComments, rejectedComments },
|
||||
activeTab,
|
||||
@@ -123,6 +122,11 @@ class UserDetail extends React.Component {
|
||||
const banned = isBanned(user);
|
||||
const suspended = isSuspended(user);
|
||||
|
||||
const slotPassthrough = {
|
||||
root,
|
||||
user,
|
||||
};
|
||||
|
||||
return (
|
||||
<ClickOutside onClickOutside={modal ? null : hideUserDetail}>
|
||||
<Drawer
|
||||
@@ -244,11 +248,7 @@ class UserDetail extends React.Component {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Slot
|
||||
fill="userProfile"
|
||||
data={this.props.data}
|
||||
queryData={{ root, user }}
|
||||
/>
|
||||
<Slot fill="userProfile" passthrough={slotPassthrough} />
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -301,7 +301,6 @@ class UserDetail extends React.Component {
|
||||
<UserDetailCommentList
|
||||
user={user}
|
||||
root={root}
|
||||
data={data}
|
||||
loadMore={loadMore}
|
||||
toggleSelect={toggleSelect}
|
||||
viewUserDetail={viewUserDetail}
|
||||
@@ -320,7 +319,6 @@ class UserDetail extends React.Component {
|
||||
<UserDetailCommentList
|
||||
user={user}
|
||||
root={root}
|
||||
data={data}
|
||||
loadMore={loadMore}
|
||||
toggleSelect={toggleSelect}
|
||||
viewUserDetail={viewUserDetail}
|
||||
@@ -368,9 +366,7 @@ UserDetail.propTypes = {
|
||||
bulkReject: PropTypes.func.isRequired,
|
||||
toggleSelectAll: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
data: PropTypes.shape({
|
||||
refetch: PropTypes.func.isRequired,
|
||||
}),
|
||||
data: PropTypes.object,
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
selectedCommentIds: PropTypes.array.isRequired,
|
||||
viewUserDetail: PropTypes.any.isRequired,
|
||||
|
||||
@@ -32,13 +32,12 @@ class UserDetailComment extends React.Component {
|
||||
selected,
|
||||
toggleSelect,
|
||||
className,
|
||||
data,
|
||||
root: { settings },
|
||||
} = this.props;
|
||||
|
||||
const queryData = { root, comment };
|
||||
|
||||
const formatterSettings = {
|
||||
const slotPassthrough = {
|
||||
root,
|
||||
comment,
|
||||
suspectWords: settings.wordlist.suspect,
|
||||
bannedWords: settings.wordlist.banned,
|
||||
body: comment.body,
|
||||
@@ -89,15 +88,13 @@ class UserDetailComment extends React.Component {
|
||||
<div className={styles.body}>
|
||||
<Slot
|
||||
fill="userDetailCommentContent"
|
||||
data={data}
|
||||
className={cn(
|
||||
styles.commentContent,
|
||||
'talk-admin-user-detail-comment'
|
||||
)}
|
||||
queryData={queryData}
|
||||
slotSize={1}
|
||||
size={1}
|
||||
defaultComponent={CommentFormatter}
|
||||
{...formatterSettings}
|
||||
passthrough={slotPassthrough}
|
||||
/>
|
||||
<a
|
||||
className={styles.external}
|
||||
@@ -130,7 +127,7 @@ class UserDetailComment extends React.Component {
|
||||
</div>
|
||||
</CommentAnimatedEdit>
|
||||
</div>
|
||||
<CommentDetails data={data} root={root} comment={comment} />
|
||||
<CommentDetails root={root} comment={comment} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -138,7 +135,6 @@ class UserDetailComment extends React.Component {
|
||||
|
||||
UserDetailComment.propTypes = {
|
||||
selected: PropTypes.bool,
|
||||
data: PropTypes.object,
|
||||
user: PropTypes.object.isRequired,
|
||||
viewUserDetail: PropTypes.func.isRequired,
|
||||
acceptComment: PropTypes.func.isRequired,
|
||||
|
||||
@@ -9,7 +9,6 @@ import ApproveButton from './ApproveButton';
|
||||
|
||||
const UserDetailCommentList = props => {
|
||||
const {
|
||||
data,
|
||||
root,
|
||||
root: { user, comments: { nodes, hasNextPage } },
|
||||
acceptComment,
|
||||
@@ -70,7 +69,6 @@ const UserDetailCommentList = props => {
|
||||
key={comment.id}
|
||||
user={user}
|
||||
root={root}
|
||||
data={data}
|
||||
comment={comment}
|
||||
acceptComment={acceptComment}
|
||||
rejectComment={rejectComment}
|
||||
@@ -93,7 +91,6 @@ UserDetailCommentList.propTypes = {
|
||||
root: PropTypes.object.isRequired,
|
||||
acceptComment: PropTypes.func.isRequired,
|
||||
rejectComment: PropTypes.func.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
selectedCommentIds: PropTypes.array.isRequired,
|
||||
viewUserDetail: PropTypes.any.isRequired,
|
||||
loadMore: PropTypes.any.isRequired,
|
||||
|
||||
@@ -75,7 +75,6 @@ export default class Configure extends Component {
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
<SectionComponent
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
settings={this.props.settings}
|
||||
/>
|
||||
@@ -88,7 +87,6 @@ export default class Configure extends Component {
|
||||
Configure.propTypes = {
|
||||
savePending: PropTypes.func.isRequired,
|
||||
currentUser: PropTypes.object.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
canSave: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -52,7 +52,7 @@ class ModerationSettings extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { settings, data, root, updatePending, errors } = this.props;
|
||||
const { settings, slotPassthrough } = this.props;
|
||||
|
||||
return (
|
||||
<ConfigurePage title={t('configure.moderation_settings')}>
|
||||
@@ -82,13 +82,7 @@ class ModerationSettings extends React.Component {
|
||||
suspectWords={settings.wordlist.suspect}
|
||||
onChangeWordlist={this.updateWordlist}
|
||||
/>
|
||||
<Slot
|
||||
fill="adminModerationSettings"
|
||||
data={data}
|
||||
queryData={{ root, settings }}
|
||||
updatePending={updatePending}
|
||||
errors={errors}
|
||||
/>
|
||||
<Slot fill="adminModerationSettings" passthrough={slotPassthrough} />
|
||||
</ConfigurePage>
|
||||
);
|
||||
}
|
||||
@@ -97,9 +91,8 @@ class ModerationSettings extends React.Component {
|
||||
ModerationSettings.propTypes = {
|
||||
updatePending: PropTypes.func.isRequired,
|
||||
errors: PropTypes.object.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
slotPassthrough: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default ModerationSettings;
|
||||
|
||||
@@ -107,7 +107,7 @@ class StreamSettings extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { settings, data, root, errors, updatePending } = this.props;
|
||||
const { settings, slotPassthrough, errors } = this.props;
|
||||
|
||||
return (
|
||||
<ConfigurePage title={t('configure.stream_settings')}>
|
||||
@@ -220,13 +220,7 @@ class StreamSettings extends React.Component {
|
||||
</div>
|
||||
</ConfigureCard>
|
||||
{/* the above card should be the last one if at all possible because of z-index issues with the selects */}
|
||||
<Slot
|
||||
fill="adminStreamSettings"
|
||||
data={data}
|
||||
queryData={{ root, settings }}
|
||||
updatePending={updatePending}
|
||||
errors={errors}
|
||||
/>
|
||||
<Slot fill="adminStreamSettings" passthrough={slotPassthrough} />
|
||||
</ConfigurePage>
|
||||
);
|
||||
}
|
||||
@@ -235,9 +229,8 @@ class StreamSettings extends React.Component {
|
||||
StreamSettings.propTypes = {
|
||||
updatePending: PropTypes.func.isRequired,
|
||||
errors: PropTypes.object.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
slotPassthrough: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default StreamSettings;
|
||||
|
||||
@@ -34,7 +34,7 @@ class TechSettings extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { settings, data, root, errors, updatePending } = this.props;
|
||||
const { settings, slotPassthrough } = this.props;
|
||||
return (
|
||||
<ConfigurePage title={t('configure.tech_settings')}>
|
||||
<Domainlist
|
||||
@@ -50,13 +50,7 @@ class TechSettings extends React.Component {
|
||||
onChange={this.updateCustomCssUrl}
|
||||
/>
|
||||
</ConfigureCard>
|
||||
<Slot
|
||||
fill="adminTechSettings"
|
||||
data={data}
|
||||
queryData={{ root, settings }}
|
||||
updatePending={updatePending}
|
||||
errors={errors}
|
||||
/>
|
||||
<Slot fill="adminTechSettings" passthrough={slotPassthrough} />
|
||||
</ConfigurePage>
|
||||
);
|
||||
}
|
||||
@@ -64,10 +58,9 @@ class TechSettings extends React.Component {
|
||||
|
||||
TechSettings.propTypes = {
|
||||
updatePending: PropTypes.func.isRequired,
|
||||
errors: PropTypes.object.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
slotPassthrough: PropTypes.object.isRequired,
|
||||
errors: PropTypes.object,
|
||||
};
|
||||
|
||||
export default TechSettings;
|
||||
|
||||
@@ -31,7 +31,6 @@ class ConfigureContainer extends Component {
|
||||
return (
|
||||
<Configure
|
||||
currentUser={this.props.currentUser}
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
settings={this.props.mergedSettings}
|
||||
canSave={this.props.canSave}
|
||||
|
||||
@@ -5,6 +5,7 @@ import ModerationSettings from '../components/ModerationSettings';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import { getSlotFragmentSpreads } from 'coral-framework/utils';
|
||||
import { updatePending } from '../../../actions/configure';
|
||||
import { mapProps } from 'recompose';
|
||||
|
||||
const slots = ['adminModerationSettings'];
|
||||
|
||||
@@ -41,5 +42,17 @@ export default compose(
|
||||
}
|
||||
`,
|
||||
}),
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
mapProps(({ root, settings, updatePending, errors, ...rest }) => ({
|
||||
slotPassthrough: {
|
||||
root,
|
||||
settings,
|
||||
updatePending,
|
||||
errors,
|
||||
},
|
||||
updatePending,
|
||||
settings,
|
||||
errors,
|
||||
...rest,
|
||||
}))
|
||||
)(ModerationSettings);
|
||||
|
||||
@@ -5,6 +5,7 @@ import StreamSettings from '../components/StreamSettings';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import { getSlotFragmentSpreads } from 'coral-framework/utils';
|
||||
import { updatePending } from '../../../actions/configure';
|
||||
import { mapProps } from 'recompose';
|
||||
|
||||
const slots = ['adminStreamSettings'];
|
||||
|
||||
@@ -42,5 +43,17 @@ export default compose(
|
||||
}
|
||||
`,
|
||||
}),
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
mapProps(({ root, settings, updatePending, errors, ...rest }) => ({
|
||||
slotPassthrough: {
|
||||
root,
|
||||
settings,
|
||||
updatePending,
|
||||
errors,
|
||||
},
|
||||
updatePending,
|
||||
settings,
|
||||
errors,
|
||||
...rest,
|
||||
}))
|
||||
)(StreamSettings);
|
||||
|
||||
@@ -5,6 +5,7 @@ import TechSettings from '../components/TechSettings';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import { getSlotFragmentSpreads } from 'coral-framework/utils';
|
||||
import { updatePending } from '../../../actions/configure';
|
||||
import { mapProps } from 'recompose';
|
||||
|
||||
const slots = ['adminTechSettings'];
|
||||
|
||||
@@ -38,5 +39,17 @@ export default compose(
|
||||
}
|
||||
`,
|
||||
}),
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
mapProps(({ root, settings, updatePending, errors, ...rest }) => ({
|
||||
slotPassthrough: {
|
||||
root,
|
||||
settings,
|
||||
updatePending,
|
||||
errors,
|
||||
},
|
||||
updatePending,
|
||||
settings,
|
||||
errors,
|
||||
...rest,
|
||||
}))
|
||||
)(TechSettings);
|
||||
|
||||
@@ -53,7 +53,6 @@ class Comment extends React.Component {
|
||||
comment,
|
||||
selected,
|
||||
className,
|
||||
data,
|
||||
root,
|
||||
root: { settings },
|
||||
currentAsset,
|
||||
@@ -62,7 +61,6 @@ class Comment extends React.Component {
|
||||
} = this.props;
|
||||
|
||||
const selectionStateCSS = selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp';
|
||||
const queryData = { root, comment, asset: comment.asset };
|
||||
|
||||
const formatterSettings = {
|
||||
suspectWords: settings.wordlist.suspect,
|
||||
@@ -70,6 +68,13 @@ class Comment extends React.Component {
|
||||
body: comment.body,
|
||||
};
|
||||
|
||||
const slotPassthrough = {
|
||||
clearHeightCache,
|
||||
root,
|
||||
comment,
|
||||
asset: comment.asset,
|
||||
};
|
||||
|
||||
return (
|
||||
<li
|
||||
tabIndex={0}
|
||||
@@ -113,9 +118,7 @@ class Comment extends React.Component {
|
||||
<CommentLabels comment={comment} />
|
||||
<Slot
|
||||
fill="adminCommentInfoBar"
|
||||
data={data}
|
||||
clearHeightCache={clearHeightCache}
|
||||
queryData={queryData}
|
||||
passthrough={slotPassthrough}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,13 +138,10 @@ class Comment extends React.Component {
|
||||
<div className={styles.body}>
|
||||
<Slot
|
||||
fill="adminCommentContent"
|
||||
data={data}
|
||||
className={cn(styles.commentContent, 'talk-admin-comment')}
|
||||
clearHeightCache={clearHeightCache}
|
||||
queryData={queryData}
|
||||
slotSize={1}
|
||||
size={1}
|
||||
defaultComponent={CommentFormatter}
|
||||
{...formatterSettings}
|
||||
passthrough={{ ...slotPassthrough, ...formatterSettings }}
|
||||
/>
|
||||
<div className={styles.commentContentFooter}>
|
||||
<a
|
||||
@@ -171,18 +171,12 @@ class Comment extends React.Component {
|
||||
onClick={this.reject}
|
||||
/>
|
||||
</div>
|
||||
<Slot
|
||||
fill="adminSideActions"
|
||||
data={data}
|
||||
clearHeightCache={clearHeightCache}
|
||||
queryData={queryData}
|
||||
/>
|
||||
<Slot fill="adminSideActions" passthrough={slotPassthrough} />
|
||||
</div>
|
||||
</div>
|
||||
</CommentAnimatedEdit>
|
||||
</div>
|
||||
<CommentDetails
|
||||
data={data}
|
||||
root={root}
|
||||
comment={comment}
|
||||
clearHeightCache={clearHeightCache}
|
||||
@@ -220,7 +214,6 @@ Comment.propTypes = {
|
||||
id: PropTypes.string,
|
||||
}),
|
||||
}),
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
selected: PropTypes.bool,
|
||||
};
|
||||
|
||||
@@ -126,7 +126,6 @@ class Moderation extends Component {
|
||||
render() {
|
||||
const {
|
||||
root,
|
||||
data,
|
||||
moderation,
|
||||
viewUserDetail,
|
||||
activeTab,
|
||||
@@ -148,6 +147,13 @@ class Moderation extends Component {
|
||||
count: root[`${queue}Count`],
|
||||
}));
|
||||
|
||||
const slotPassthrough = {
|
||||
root,
|
||||
asset,
|
||||
activeTab,
|
||||
handleCommentChange,
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModerationHeader
|
||||
@@ -171,7 +177,6 @@ class Moderation extends Component {
|
||||
/>
|
||||
<ModerationQueue
|
||||
key={`${activeTab}_${this.props.moderation.sortOrder}`}
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
currentAsset={asset}
|
||||
comments={comments.nodes}
|
||||
@@ -204,13 +209,7 @@ class Moderation extends Component {
|
||||
closeSearch={this.closeSearch}
|
||||
storySearchChange={this.props.storySearchChange}
|
||||
/>
|
||||
<Slot
|
||||
data={data}
|
||||
queryData={{ root, asset }}
|
||||
activeTab={activeTab}
|
||||
handleCommentChange={handleCommentChange}
|
||||
fill="adminModeration"
|
||||
/>
|
||||
<Slot fill="adminModeration" passthrough={slotPassthrough} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -344,7 +344,6 @@ class ModerationQueue extends React.Component {
|
||||
child = (
|
||||
<div style={style}>
|
||||
<Comment
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
comment={comment}
|
||||
dangling={
|
||||
@@ -408,7 +407,6 @@ class ModerationQueue extends React.Component {
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<Comment
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
key={comment.id}
|
||||
comment={comment}
|
||||
@@ -476,7 +474,6 @@ ModerationQueue.propTypes = {
|
||||
hasNextPage: PropTypes.bool,
|
||||
comments: PropTypes.array,
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
@@ -111,14 +111,14 @@ class IndicatorContainer extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const slotPassthrough = {
|
||||
handleCommentChange: this.handleCommentChange,
|
||||
};
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Indicator />
|
||||
<Slot
|
||||
data={this.props.data}
|
||||
handleCommentChange={this.handleCommentChange}
|
||||
fill="adminModerationIndicator"
|
||||
/>
|
||||
<Slot fill="adminModerationIndicator" passthrough={slotPassthrough} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ export default class Embed extends React.Component {
|
||||
} = this.props;
|
||||
const hasHighlightedComment = !!commentId;
|
||||
const popupUrl = `login?parentUrl=${encodeURIComponent(parentUrl)}`;
|
||||
const slotPassthrough = { root };
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -84,7 +85,7 @@ export default class Embed extends React.Component {
|
||||
/>
|
||||
</IfSlotIsNotEmpty>
|
||||
|
||||
<Slot data={data} queryData={{ root }} fill="embed" />
|
||||
<Slot passthrough={slotPassthrough} fill="embed" />
|
||||
|
||||
<ExtendableTabPanel
|
||||
className="talk-embed-stream-tab-bar"
|
||||
@@ -94,8 +95,7 @@ export default class Embed extends React.Component {
|
||||
tabSlot="embedStreamTabs"
|
||||
tabSlotPrepend="embedStreamTabsPrepend"
|
||||
tabPaneSlot="embedStreamTabPanes"
|
||||
slotProps={{ data }}
|
||||
queryData={{ root }}
|
||||
slotPassthrough={slotPassthrough}
|
||||
tabs={this.getTabs()}
|
||||
tabPanes={[
|
||||
<TabPane
|
||||
@@ -138,9 +138,5 @@ Embed.propTypes = {
|
||||
commentId: PropTypes.string,
|
||||
root: PropTypes.object,
|
||||
activeTab: PropTypes.string,
|
||||
data: PropTypes.shape({
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.object,
|
||||
refetch: PropTypes.func,
|
||||
}).isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import React from 'react';
|
||||
import ExtendableTabPanel from '../components/ExtendableTabPanel';
|
||||
import { connect } from 'react-redux';
|
||||
import { TabPane } from 'coral-ui';
|
||||
import ExtendableTab from '../components/ExtendableTab';
|
||||
import { getShallowChanges } from 'coral-framework/utils';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withSlotElements } from 'coral-framework/hocs';
|
||||
import { compose } from 'recompose';
|
||||
|
||||
class ExtendableTabPanelContainer extends React.Component {
|
||||
static contextTypes = {
|
||||
plugins: PropTypes.object,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.handleFallback();
|
||||
}
|
||||
@@ -20,24 +15,6 @@ class ExtendableTabPanelContainer extends React.Component {
|
||||
this.handleFallback(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 prevKeys = this.getSlotElements(this.props.tabSlot, this.props).map(
|
||||
el => el.key
|
||||
);
|
||||
const nextKeys = this.getSlotElements(next.tabSlot, next).map(
|
||||
el => el.key
|
||||
);
|
||||
return !isEqual(prevKeys, nextKeys);
|
||||
}
|
||||
|
||||
// Prevent Slot from rerendering when no props has shallowly changed.
|
||||
return changes.length !== 0;
|
||||
}
|
||||
|
||||
handleFallback(props = this.props) {
|
||||
if (this.getTabNames(props).indexOf(props.activeTab) === -1) {
|
||||
props.setActiveTab(props.fallbackTab);
|
||||
@@ -48,38 +25,26 @@ class ExtendableTabPanelContainer extends React.Component {
|
||||
return this.getTabElements(props).map(el => el.props.tabId);
|
||||
}
|
||||
|
||||
getSlotElements(slot, props = this.props) {
|
||||
const { plugins } = this.context;
|
||||
return plugins.getSlotElements(
|
||||
slot,
|
||||
props.reduxState,
|
||||
props.slotProps,
|
||||
props.queryData
|
||||
);
|
||||
}
|
||||
|
||||
getPluginTabElements(props = this.props) {
|
||||
return this.getSlotTabElements(props.tabSlot);
|
||||
return props.slotElements[0].map(this.createPluginTabFactory(props));
|
||||
}
|
||||
|
||||
getPluginTabElementsPrepend(props = this.props) {
|
||||
return this.getSlotTabElements(props.tabSlotPrepend);
|
||||
return props.slotElements[1].map(this.createPluginTabFactory(props));
|
||||
}
|
||||
|
||||
getSlotTabElements(slot) {
|
||||
return this.getSlotElements(slot).map(el => {
|
||||
return (
|
||||
<ExtendableTab
|
||||
tabId={el.type.talkPluginName}
|
||||
key={el.type.talkPluginName}
|
||||
>
|
||||
{React.cloneElement(el, {
|
||||
active: this.props.activeTab === el.type.talkPluginName,
|
||||
})}
|
||||
</ExtendableTab>
|
||||
);
|
||||
});
|
||||
}
|
||||
createPluginTabFactory = (props = this.props) => el => {
|
||||
return (
|
||||
<ExtendableTab
|
||||
tabId={el.type.talkPluginName}
|
||||
key={el.type.talkPluginName}
|
||||
>
|
||||
{React.cloneElement(el, {
|
||||
active: props.activeTab === el.type.talkPluginName,
|
||||
})}
|
||||
</ExtendableTab>
|
||||
);
|
||||
};
|
||||
|
||||
getTabElements(props = this.props) {
|
||||
const elements = [...this.getPluginTabElementsPrepend(props)];
|
||||
@@ -92,14 +57,16 @@ class ExtendableTabPanelContainer extends React.Component {
|
||||
return elements;
|
||||
}
|
||||
|
||||
createPluginTabPane(el) {
|
||||
return (
|
||||
<TabPane tabId={el.type.talkPluginName} key={el.type.talkPluginName}>
|
||||
{el}
|
||||
</TabPane>
|
||||
);
|
||||
}
|
||||
|
||||
getPluginTabPaneElements(props = this.props) {
|
||||
return this.getSlotElements(props.tabPaneSlot).map(el => {
|
||||
return (
|
||||
<TabPane tabId={el.type.talkPluginName} key={el.type.talkPluginName}>
|
||||
{el}
|
||||
</TabPane>
|
||||
);
|
||||
});
|
||||
return props.slotElements[2].map(this.createPluginTabPane);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -132,15 +99,15 @@ ExtendableTabPanelContainer.propTypes = {
|
||||
tabSlot: PropTypes.string.isRequired,
|
||||
tabSlotPrepend: PropTypes.string.isRequired,
|
||||
tabPaneSlot: PropTypes.string.isRequired,
|
||||
slotProps: PropTypes.object.isRequired,
|
||||
queryData: PropTypes.object,
|
||||
slotPassthrough: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
sub: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
reduxState: state,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(ExtendableTabPanelContainer);
|
||||
export default compose(
|
||||
withSlotElements({
|
||||
slot: props => [props.tabSlot, props.tabSlotPrepend, props.tabPaneSlot],
|
||||
passthroughPropName: 'slotPassthrough',
|
||||
})
|
||||
)(ExtendableTabPanelContainer);
|
||||
|
||||
@@ -7,11 +7,7 @@ class Configure extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="talk-embed-stream-configuration-container">
|
||||
<Settings
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
asset={this.props.asset}
|
||||
/>
|
||||
<Settings root={this.props.root} asset={this.props.asset} />
|
||||
<hr />
|
||||
<AssetStatusInfo asset={this.props.asset} />
|
||||
</div>
|
||||
@@ -20,7 +16,6 @@ class Configure extends React.Component {
|
||||
}
|
||||
|
||||
Configure.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
asset: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@@ -25,9 +25,9 @@ class Settings extends React.Component {
|
||||
onQuestionBoxContentChange,
|
||||
canSave,
|
||||
onApply,
|
||||
slotProps,
|
||||
queryData,
|
||||
slotPassthrough,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.container}>
|
||||
@@ -77,7 +77,7 @@ class Settings extends React.Component {
|
||||
</div>
|
||||
)}
|
||||
</Configuration>
|
||||
<Slot fill="streamSettings" queryData={queryData} {...slotProps} />
|
||||
<Slot fill="streamSettings" passthrough={slotPassthrough} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -85,8 +85,7 @@ class Settings extends React.Component {
|
||||
}
|
||||
|
||||
Settings.propTypes = {
|
||||
queryData: PropTypes.object.isRequired,
|
||||
slotProps: PropTypes.object.isRequired,
|
||||
slotPassthrough: PropTypes.object.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
canSave: PropTypes.bool.isRequired,
|
||||
onToggleModeration: PropTypes.func.isRequired,
|
||||
|
||||
@@ -13,13 +13,7 @@ class ConfigureContainer extends React.Component {
|
||||
return <div>{this.props.data.error.message}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Configure
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
asset={this.props.asset}
|
||||
/>
|
||||
);
|
||||
return <Configure root={this.props.root} asset={this.props.asset} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,17 +57,24 @@ class SettingsContainer extends React.Component {
|
||||
const {
|
||||
mergedSettings,
|
||||
canSave,
|
||||
data,
|
||||
root,
|
||||
asset,
|
||||
errors,
|
||||
updatePending,
|
||||
} = this.props;
|
||||
|
||||
const slotPassthrough = {
|
||||
root,
|
||||
asset,
|
||||
settings: mergedSettings,
|
||||
updatePending,
|
||||
errors,
|
||||
};
|
||||
|
||||
return (
|
||||
<Settings
|
||||
settings={mergedSettings}
|
||||
queryData={{ root, asset, settings: mergedSettings }}
|
||||
slotProps={{ data, updatePending, errors }}
|
||||
slotPassthrough={slotPassthrough}
|
||||
savePending={this.savePending}
|
||||
onToggleModeration={this.toggleModeration}
|
||||
onTogglePremodLinks={this.togglePremodLinks}
|
||||
@@ -82,7 +89,6 @@ class SettingsContainer extends React.Component {
|
||||
}
|
||||
|
||||
SettingsContainer.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
asset: PropTypes.object.isRequired,
|
||||
pending: PropTypes.object.isRequired,
|
||||
|
||||
@@ -22,9 +22,9 @@ class Comment extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { comment, data, root } = this.props;
|
||||
const { comment, root } = this.props;
|
||||
const reactionCount = getTotalReactionsCount(comment.action_summaries);
|
||||
const queryData = { root, comment, asset: comment.asset };
|
||||
const slotPassthrough = { root, comment, asset: comment.asset };
|
||||
|
||||
return (
|
||||
<div className={styles.myComment}>
|
||||
@@ -33,8 +33,7 @@ class Comment extends React.Component {
|
||||
fill="commentContent"
|
||||
defaultComponent={CommentContent}
|
||||
className={cn(styles.commentBody, 'my-comment-body')}
|
||||
data={data}
|
||||
queryData={queryData}
|
||||
passthrough={slotPassthrough}
|
||||
/>
|
||||
<div className={cn(styles.commentSummary, 'comment-summary')}>
|
||||
<span
|
||||
@@ -98,9 +97,10 @@ class Comment extends React.Component {
|
||||
fill="historyCommentTimestamp"
|
||||
defaultComponent={CommentTimestamp}
|
||||
className={'talk-history-comment-published-date'}
|
||||
created_at={comment.created_at}
|
||||
data={data}
|
||||
queryData={queryData}
|
||||
passthrough={{
|
||||
created_at: comment.created_at,
|
||||
...slotPassthrough,
|
||||
}}
|
||||
inline
|
||||
/>
|
||||
</li>
|
||||
@@ -114,7 +114,6 @@ class Comment extends React.Component {
|
||||
Comment.propTypes = {
|
||||
comment: PropTypes.object.isRequired,
|
||||
navigate: PropTypes.func.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class CommentHistory extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigate, comments, data, root } = this.props;
|
||||
const { navigate, comments, root } = this.props;
|
||||
if (!comments.nodes.length) {
|
||||
return <BlankCommentHistory />;
|
||||
}
|
||||
@@ -33,7 +33,6 @@ class CommentHistory extends React.Component {
|
||||
return (
|
||||
<Comment
|
||||
key={i}
|
||||
data={data}
|
||||
root={root}
|
||||
comment={comment}
|
||||
navigate={navigate}
|
||||
@@ -56,7 +55,6 @@ CommentHistory.propTypes = {
|
||||
comments: PropTypes.object.isRequired,
|
||||
loadMore: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,57 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import CommentHistory from '../containers/CommentHistory';
|
||||
import ExtendableTabPanel from '../../../containers/ExtendableTabPanel';
|
||||
import { Tab, TabPane } from 'coral-ui';
|
||||
import styles from './Profile.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import TabPanel from '../containers/TabPanel';
|
||||
|
||||
const Profile = ({
|
||||
username,
|
||||
emailAddress,
|
||||
data,
|
||||
root,
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
}) => (
|
||||
<div className="talk-my-profile talk-profile-container">
|
||||
<div className={styles.userInfo}>
|
||||
<h2 className={styles.username}>{username}</h2>
|
||||
{emailAddress ? <p className={styles.email}>{emailAddress}</p> : null}
|
||||
const Profile = ({ username, emailAddress, root, slotPassthrough }) => {
|
||||
return (
|
||||
<div className="talk-my-profile talk-profile-container">
|
||||
<div className={styles.userInfo}>
|
||||
<h2 className={styles.username}>{username}</h2>
|
||||
{emailAddress ? <p className={styles.email}>{emailAddress}</p> : null}
|
||||
</div>
|
||||
<Slot fill="profileSections" passthrough={slotPassthrough} />
|
||||
<TabPanel root={root} slotPassthrough={slotPassthrough} />
|
||||
</div>
|
||||
<Slot fill="profileSections" data={data} queryData={{ root }} />
|
||||
<ExtendableTabPanel
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
fallbackTab="comments"
|
||||
tabSlot="profileTabs"
|
||||
tabSlotPrepend="profileTabsPrepend"
|
||||
tabPaneSlot="profileTabPanes"
|
||||
slotProps={{ data }}
|
||||
queryData={{ root }}
|
||||
tabs={[
|
||||
<Tab key="comments" tabId="comments">
|
||||
{t('framework.my_comments')}
|
||||
</Tab>,
|
||||
]}
|
||||
tabPanes={[
|
||||
<TabPane key="comments" tabId="comments">
|
||||
<CommentHistory data={data} root={root} />
|
||||
</TabPane>,
|
||||
]}
|
||||
sub
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
Profile.propTypes = {
|
||||
username: PropTypes.string,
|
||||
emailAddress: PropTypes.string,
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
setActiveTab: PropTypes.func.isRequired,
|
||||
slotPassthrough: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Slot } from 'coral-framework/components';
|
||||
|
||||
class Settings extends React.Component {
|
||||
render() {
|
||||
const { root } = this.props;
|
||||
const slotPassthrough = { root };
|
||||
return (
|
||||
<div>
|
||||
<Slot fill="profileSettings" passthrough={slotPassthrough} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Settings.propTypes = {
|
||||
root: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import CommentHistory from '../containers/CommentHistory';
|
||||
import ExtendableTabPanel from '../../../containers/ExtendableTabPanel';
|
||||
import { Tab, TabPane } from 'coral-ui';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import Settings from '../containers/Settings';
|
||||
|
||||
const TabPanel = ({
|
||||
root,
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
showSettingsTab,
|
||||
slotPassthrough,
|
||||
}) => {
|
||||
const tabs = [
|
||||
<Tab key="comments" tabId="comments">
|
||||
{t('framework.my_comments')}
|
||||
</Tab>,
|
||||
];
|
||||
|
||||
if (showSettingsTab) {
|
||||
tabs.push(
|
||||
<Tab key="settings" tabId="settings">
|
||||
{t('profile_settings')}
|
||||
</Tab>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ExtendableTabPanel
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
fallbackTab="comments"
|
||||
tabSlot="profileTabs"
|
||||
tabSlotPrepend="profileTabsPrepend"
|
||||
tabPaneSlot="profileTabPanes"
|
||||
slotPassthrough={slotPassthrough}
|
||||
tabs={tabs}
|
||||
tabPanes={[
|
||||
<TabPane key="comments" tabId="comments">
|
||||
<CommentHistory root={root} />
|
||||
</TabPane>,
|
||||
<TabPane key="settings" tabId="settings">
|
||||
<Settings root={root} />
|
||||
</TabPane>,
|
||||
]}
|
||||
sub
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
TabPanel.propTypes = {
|
||||
root: PropTypes.object,
|
||||
slotPassthrough: PropTypes.object,
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
setActiveTab: PropTypes.func.isRequired,
|
||||
showSettingsTab: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default TabPanel;
|
||||
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import CommentHistory from '../components/CommentHistory';
|
||||
import Comment from './Comment';
|
||||
import { withFragments } from 'coral-framework/hocs';
|
||||
import { withFragments, withFetchMore } from 'coral-framework/hocs';
|
||||
|
||||
import { appendNewNodes } from 'plugin-api/beta/client/utils';
|
||||
import update from 'immutability-helper';
|
||||
@@ -16,7 +16,7 @@ class CommentHistoryContainer extends Component {
|
||||
};
|
||||
|
||||
loadMore = () => {
|
||||
return this.props.data.fetchMore({
|
||||
return this.props.fetchMore({
|
||||
query: LOAD_MORE_QUERY,
|
||||
variables: {
|
||||
limit: 5,
|
||||
@@ -43,7 +43,6 @@ class CommentHistoryContainer extends Component {
|
||||
return (
|
||||
<CommentHistory
|
||||
comments={this.props.root.me.comments}
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
loadMore={this.loadMore}
|
||||
navigate={this.navigate}
|
||||
@@ -57,8 +56,8 @@ CommentHistoryContainer.contextTypes = {
|
||||
};
|
||||
|
||||
CommentHistoryContainer.propTypes = {
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
fetchMore: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const LOAD_MORE_QUERY = gql`
|
||||
@@ -99,5 +98,6 @@ const mapStateToProps = state => ({
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps, null),
|
||||
withCommentHistoryFragments
|
||||
withCommentHistoryFragments,
|
||||
withFetchMore
|
||||
)(CommentHistoryContainer);
|
||||
|
||||
@@ -7,11 +7,10 @@ import { withQuery } from 'coral-framework/hocs';
|
||||
import NotLoggedIn from '../components/NotLoggedIn';
|
||||
import { Spinner } from 'coral-ui';
|
||||
import Profile from '../components/Profile';
|
||||
import CommentHistory from './CommentHistory';
|
||||
import TabPanel from './TabPanel';
|
||||
import { getDefinitionName } from 'coral-framework/utils';
|
||||
|
||||
import { showSignInDialog } from 'coral-embed-stream/src/actions/login';
|
||||
import { setActiveTab } from '../../../actions/profile';
|
||||
import { getSlotFragmentSpreads } from 'coral-framework/utils';
|
||||
|
||||
class ProfileContainer extends Component {
|
||||
@@ -23,7 +22,7 @@ class ProfileContainer extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { currentUser, showSignInDialog, root, data } = this.props;
|
||||
const { currentUser, showSignInDialog, root } = this.props;
|
||||
const { me } = this.props.root;
|
||||
const loading = this.props.data.loading;
|
||||
|
||||
@@ -41,15 +40,14 @@ class ProfileContainer extends Component {
|
||||
|
||||
const localProfile = currentUser.profiles.find(p => p.provider === 'local');
|
||||
const emailAddress = localProfile && localProfile.id;
|
||||
const slotPassthrough = { root };
|
||||
|
||||
return (
|
||||
<Profile
|
||||
username={me.username}
|
||||
emailAddress={emailAddress}
|
||||
data={data}
|
||||
root={root}
|
||||
activeTab={this.props.activeTab}
|
||||
setActiveTab={this.props.setActiveTab}
|
||||
slotPassthrough={slotPassthrough}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -60,16 +58,9 @@ ProfileContainer.propTypes = {
|
||||
root: PropTypes.object,
|
||||
currentUser: PropTypes.object,
|
||||
showSignInDialog: PropTypes.func,
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
setActiveTab: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const slots = [
|
||||
'profileSections',
|
||||
'profileTabs',
|
||||
'profileTabsPrepend',
|
||||
'profileTabPanes',
|
||||
];
|
||||
const slots = ['profileSections'];
|
||||
|
||||
const withProfileQuery = withQuery(
|
||||
gql`
|
||||
@@ -78,10 +69,10 @@ const withProfileQuery = withQuery(
|
||||
id
|
||||
username
|
||||
}
|
||||
...${getDefinitionName(CommentHistory.fragments.root)}
|
||||
...${getDefinitionName(TabPanel.fragments.root)}
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
}
|
||||
${CommentHistory.fragments.root}
|
||||
${TabPanel.fragments.root}
|
||||
`,
|
||||
{
|
||||
options: {
|
||||
@@ -92,11 +83,10 @@ const withProfileQuery = withQuery(
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
currentUser: state.auth.user,
|
||||
activeTab: state.profile.activeTab,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ showSignInDialog, setActiveTab }, dispatch);
|
||||
bindActionCreators({ showSignInDialog }, dispatch);
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import Settings from '../components/Settings';
|
||||
import { withFragments } from 'coral-framework/hocs';
|
||||
import { getSlotFragmentSpreads } from 'coral-framework/utils';
|
||||
|
||||
const slots = ['profileSettings'];
|
||||
|
||||
class SettingsContainer extends React.Component {
|
||||
render() {
|
||||
return <Settings {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhance = compose(
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment TalkEmbedStream_ProfileSettings_root on RootQuery {
|
||||
__typename
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
}
|
||||
`,
|
||||
})
|
||||
);
|
||||
|
||||
export default enhance(SettingsContainer);
|
||||
@@ -0,0 +1,64 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { withSlotElements, withFragments } from 'coral-framework/hocs';
|
||||
import Settings from './Settings';
|
||||
import CommentHistory from './CommentHistory';
|
||||
import { getDefinitionName } from 'coral-framework/utils';
|
||||
import TabPanel from '../components/TabPanel';
|
||||
import { setActiveTab } from '../../../actions/profile';
|
||||
import { getSlotFragmentSpreads } from 'coral-framework/utils';
|
||||
|
||||
class TabPanelContainer extends Component {
|
||||
render() {
|
||||
return (
|
||||
<TabPanel
|
||||
root={this.props.root}
|
||||
slotPassthrough={this.props.slotPassthrough}
|
||||
activeTab={this.props.activeTab}
|
||||
setActiveTab={this.props.setActiveTab}
|
||||
showSettingsTab={this.props.profileSettingsSlotElements.length > 0}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TabPanelContainer.propTypes = {
|
||||
root: PropTypes.object,
|
||||
slotPassthrough: PropTypes.object,
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
setActiveTab: PropTypes.func.isRequired,
|
||||
profileSettingsSlotElements: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
const slots = ['profileTabs', 'profileTabsPrepend', 'profileTabPanes'];
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
activeTab: state.profile.activeTab,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ setActiveTab }, dispatch);
|
||||
|
||||
export default compose(
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment TalkEmbedStream_ProfileTabPanel_root on RootQuery {
|
||||
__typename
|
||||
...${getDefinitionName(CommentHistory.fragments.root)}
|
||||
...${getDefinitionName(Settings.fragments.root)}
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
}
|
||||
${CommentHistory.fragments.root}
|
||||
${Settings.fragments.root}
|
||||
`,
|
||||
}),
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withSlotElements({
|
||||
slot: 'profileSettings',
|
||||
propName: 'profileSettingsSlotElements',
|
||||
passthroughPropName: 'slotPassthrough',
|
||||
})
|
||||
)(TabPanelContainer);
|
||||
@@ -126,7 +126,6 @@ class AllCommentsPane extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
data,
|
||||
root,
|
||||
comments,
|
||||
commentClassNames,
|
||||
@@ -164,7 +163,6 @@ class AllCommentsPane extends React.Component {
|
||||
return (
|
||||
<Comment
|
||||
commentClassNames={commentClassNames}
|
||||
data={data}
|
||||
root={root}
|
||||
disableReply={disableReply}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
@@ -205,7 +203,6 @@ class AllCommentsPane extends React.Component {
|
||||
}
|
||||
|
||||
AllCommentsPane.propTypes = {
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
comments: PropTypes.object,
|
||||
commentClassNames: PropTypes.array,
|
||||
|
||||
@@ -181,7 +181,6 @@ export default class Comment extends React.Component {
|
||||
}),
|
||||
charCountEnable: PropTypes.bool.isRequired,
|
||||
maxCharCount: PropTypes.number,
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
loadMore: PropTypes.func,
|
||||
postDontAgree: PropTypes.func,
|
||||
@@ -417,7 +416,6 @@ export default class Comment extends React.Component {
|
||||
{view.map(reply => {
|
||||
return (
|
||||
<CommentContainer
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
disableReply={disableReply}
|
||||
@@ -487,7 +485,6 @@ export default class Comment extends React.Component {
|
||||
renderComment() {
|
||||
const {
|
||||
asset,
|
||||
data,
|
||||
root,
|
||||
depth,
|
||||
comment,
|
||||
@@ -535,12 +532,8 @@ export default class Comment extends React.Component {
|
||||
);
|
||||
|
||||
// props that are passed down the slots.
|
||||
const slotProps = {
|
||||
data,
|
||||
const slotPassthrough = {
|
||||
depth,
|
||||
};
|
||||
|
||||
const queryData = {
|
||||
root,
|
||||
asset,
|
||||
comment,
|
||||
@@ -551,8 +544,7 @@ export default class Comment extends React.Component {
|
||||
<Slot
|
||||
className={cn(styles.commentAvatar, 'talk-stream-comment-avatar')}
|
||||
fill="commentAvatar"
|
||||
{...slotProps}
|
||||
queryData={queryData}
|
||||
passthrough={slotPassthrough}
|
||||
inline
|
||||
/>
|
||||
|
||||
@@ -573,8 +565,7 @@ export default class Comment extends React.Component {
|
||||
className={cn(styles.username, 'talk-stream-comment-user-name')}
|
||||
fill="commentAuthorName"
|
||||
defaultComponent={CommentAuthorName}
|
||||
queryData={queryData}
|
||||
{...slotProps}
|
||||
passthrough={slotPassthrough}
|
||||
/>
|
||||
|
||||
<div
|
||||
@@ -591,8 +582,7 @@ export default class Comment extends React.Component {
|
||||
'talk-stream-comment-author-tags'
|
||||
)}
|
||||
fill="commentAuthorTags"
|
||||
queryData={queryData}
|
||||
{...slotProps}
|
||||
passthrough={slotPassthrough}
|
||||
inline
|
||||
/>
|
||||
</div>
|
||||
@@ -607,9 +597,10 @@ export default class Comment extends React.Component {
|
||||
fill="commentTimestamp"
|
||||
defaultComponent={CommentTimestamp}
|
||||
className={'talk-stream-comment-published-date'}
|
||||
created_at={comment.created_at}
|
||||
queryData={queryData}
|
||||
{...slotProps}
|
||||
passthrough={{
|
||||
created_at: comment.created_at,
|
||||
...slotPassthrough,
|
||||
}}
|
||||
/>
|
||||
{comment.editing && comment.editing.edited ? (
|
||||
<span>
|
||||
@@ -624,8 +615,7 @@ export default class Comment extends React.Component {
|
||||
<Slot
|
||||
className={styles.commentInfoBar}
|
||||
fill="commentInfoBar"
|
||||
{...slotProps}
|
||||
queryData={queryData}
|
||||
passthrough={slotPassthrough}
|
||||
/>
|
||||
|
||||
{isActive &&
|
||||
@@ -665,9 +655,8 @@ export default class Comment extends React.Component {
|
||||
fill="commentContent"
|
||||
className="talk-stream-comment-content"
|
||||
defaultComponent={CommentContent}
|
||||
{...slotProps}
|
||||
queryData={queryData}
|
||||
slotSize={1}
|
||||
size={1}
|
||||
passthrough={slotPassthrough}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -678,8 +667,7 @@ export default class Comment extends React.Component {
|
||||
<div className="talk-embed-stream-comment-actions-container-left commentActionsLeft comment__action-container">
|
||||
<Slot
|
||||
fill="commentReactions"
|
||||
{...slotProps}
|
||||
queryData={queryData}
|
||||
passthrough={slotPassthrough}
|
||||
inline
|
||||
/>
|
||||
|
||||
@@ -696,9 +684,7 @@ export default class Comment extends React.Component {
|
||||
<div className="talk-embed-stream-comment-actions-container-right commentActionsRight comment__action-container">
|
||||
<Slot
|
||||
fill="commentActions"
|
||||
wrapperComponent={ActionButton}
|
||||
{...slotProps}
|
||||
queryData={queryData}
|
||||
passthrough={slotPassthrough}
|
||||
inline
|
||||
/>
|
||||
<ActionButton>
|
||||
|
||||
@@ -43,20 +43,13 @@ export default class DraftArea extends React.Component {
|
||||
charCountEnable,
|
||||
maxCharCount,
|
||||
onChange,
|
||||
queryData,
|
||||
isReply,
|
||||
registerHook,
|
||||
unregisterHook,
|
||||
root,
|
||||
comment,
|
||||
} = this.props;
|
||||
|
||||
const tASettings = {
|
||||
value,
|
||||
placeholder,
|
||||
id,
|
||||
onChange,
|
||||
rows,
|
||||
disabled,
|
||||
isReply,
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
@@ -69,10 +62,19 @@ export default class DraftArea extends React.Component {
|
||||
fill="draftArea"
|
||||
defaultComponent={DraftAreaContent}
|
||||
className={styles.content}
|
||||
queryData={queryData}
|
||||
registerHook={this.props.registerHook}
|
||||
unregisterHook={this.props.unregisterHook}
|
||||
{...tASettings}
|
||||
passthrough={{
|
||||
root,
|
||||
comment,
|
||||
registerHook,
|
||||
unregisterHook,
|
||||
value,
|
||||
placeholder,
|
||||
id,
|
||||
onChange,
|
||||
rows,
|
||||
disabled,
|
||||
isReply,
|
||||
}}
|
||||
/>
|
||||
<Slot fill="commentInputArea" />
|
||||
</div>
|
||||
@@ -96,7 +98,8 @@ DraftArea.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
rows: PropTypes.number,
|
||||
queryData: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
comment: PropTypes.object,
|
||||
registerHook: PropTypes.func,
|
||||
unregisterHook: PropTypes.func,
|
||||
isReply: PropTypes.bool,
|
||||
|
||||
@@ -40,7 +40,6 @@ class Stream extends React.Component {
|
||||
|
||||
renderHighlightedComment() {
|
||||
const {
|
||||
data,
|
||||
root,
|
||||
activeReplyBox,
|
||||
setActiveReplyBox,
|
||||
@@ -91,7 +90,6 @@ class Stream extends React.Component {
|
||||
</div>
|
||||
|
||||
<Comment
|
||||
data={data}
|
||||
root={root}
|
||||
commentClassNames={commentClassNames}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
@@ -122,7 +120,6 @@ class Stream extends React.Component {
|
||||
|
||||
renderExtendableTabPanel() {
|
||||
const {
|
||||
data,
|
||||
root,
|
||||
activeReplyBox,
|
||||
setActiveReplyBox,
|
||||
@@ -147,15 +144,14 @@ class Stream extends React.Component {
|
||||
loading,
|
||||
} = this.props;
|
||||
|
||||
const slotProps = { data };
|
||||
const slotQueryData = { root, asset };
|
||||
const slotPassthrough = { root, asset };
|
||||
|
||||
// `key` of `ExtendableTabPanel` depends on sorting so that we always reset
|
||||
// the state when changing sorting.
|
||||
return (
|
||||
<div className={cn('talk-stream-tab-container', styles.tabContainer)}>
|
||||
<div className={cn('talk-stream-filter-wrapper', styles.filterWrapper)}>
|
||||
<Slot fill="streamFilter" queryData={slotQueryData} {...slotProps} />
|
||||
<Slot fill="streamFilter" passthrough={slotPassthrough} />
|
||||
</div>
|
||||
|
||||
<ExtendableTabPanel
|
||||
@@ -166,8 +162,7 @@ class Stream extends React.Component {
|
||||
tabSlot="streamTabs"
|
||||
tabSlotPrepend="streamTabsPrepend"
|
||||
tabPaneSlot="streamTabPanes"
|
||||
slotProps={slotProps}
|
||||
queryData={slotQueryData}
|
||||
slotPassthrough={slotPassthrough}
|
||||
loading={loading}
|
||||
tabs={
|
||||
<Tab tabId={'all'} key="all">
|
||||
@@ -180,7 +175,6 @@ class Stream extends React.Component {
|
||||
tabPanes={
|
||||
<TabPane tabId="all" key="all">
|
||||
<AllCommentsPane
|
||||
data={data}
|
||||
root={root}
|
||||
asset={asset}
|
||||
comments={comments}
|
||||
@@ -212,7 +206,6 @@ class Stream extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
data,
|
||||
root,
|
||||
appendItemArray,
|
||||
asset,
|
||||
@@ -243,13 +236,13 @@ class Stream extends React.Component {
|
||||
!changedUsername &&
|
||||
!highlightedComment) ||
|
||||
keepCommentBox);
|
||||
const slotProps = { data };
|
||||
const slotQueryData = { root, asset };
|
||||
|
||||
if (highlightedComment === null) {
|
||||
return <StreamError>{t('stream.comment_not_found')}</StreamError>;
|
||||
}
|
||||
|
||||
const slotPassthrough = { root, asset };
|
||||
|
||||
return (
|
||||
<div id="stream" className={styles.root}>
|
||||
{open ? (
|
||||
@@ -263,11 +256,7 @@ class Stream extends React.Component {
|
||||
content={asset.settings.questionBoxContent}
|
||||
icon={asset.settings.questionBoxIcon}
|
||||
>
|
||||
<Slot
|
||||
fill="streamQuestionArea"
|
||||
queryData={slotQueryData}
|
||||
{...slotProps}
|
||||
/>
|
||||
<Slot fill="streamQuestionArea" passthrough={slotPassthrough} />
|
||||
</QuestionBox>
|
||||
)}
|
||||
{!banned &&
|
||||
@@ -304,7 +293,7 @@ class Stream extends React.Component {
|
||||
<p>{asset.settings.closedMessage}</p>
|
||||
)}
|
||||
|
||||
<Slot fill="stream" queryData={slotQueryData} {...slotProps} />
|
||||
<Slot fill="stream" passthrough={slotPassthrough} />
|
||||
|
||||
{currentUser && (
|
||||
<ModerationLink
|
||||
@@ -324,7 +313,6 @@ class Stream extends React.Component {
|
||||
Stream.propTypes = {
|
||||
asset: PropTypes.object,
|
||||
activeStreamTab: PropTypes.string,
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
activeReplyBox: PropTypes.string,
|
||||
setActiveReplyBox: PropTypes.func,
|
||||
|
||||
@@ -190,9 +190,11 @@ class CommentBox extends React.Component {
|
||||
buttonContainerStart={
|
||||
<Slot
|
||||
fill="commentInputDetailArea"
|
||||
registerHook={this.registerHook}
|
||||
unregisterHook={this.unregisterHook}
|
||||
isReply={isReply}
|
||||
passthrough={{
|
||||
registerHook: this.registerHook,
|
||||
unregisterHook: this.unregisterHook,
|
||||
isReply,
|
||||
}}
|
||||
inline
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -42,11 +42,10 @@ class DraftAreaContainer extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const queryData = { comment: this.props.comment, root: this.props.root };
|
||||
|
||||
return (
|
||||
<DraftArea
|
||||
queryData={queryData}
|
||||
root={this.props.root}
|
||||
comment={this.props.comment}
|
||||
value={this.props.value}
|
||||
placeholder={this.props.placeholder}
|
||||
id={this.props.id}
|
||||
|
||||
@@ -1,39 +1,14 @@
|
||||
import React, { Children } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getShallowChanges } from 'coral-framework/utils';
|
||||
import { withSlotElements, withCompatPassthrough } from '../hocs';
|
||||
import { compose } from 'recompose';
|
||||
|
||||
class IfSlotIsEmpty extends React.Component {
|
||||
static contextTypes = {
|
||||
plugins: PropTypes.object,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(next) {
|
||||
// Prevent Slot from rerendering when only reduxState has changed and
|
||||
// it does not result in a change.
|
||||
const changes = getShallowChanges(this.props, next);
|
||||
if (changes.length === 1 && changes[0] === 'reduxState') {
|
||||
return this.isSlotEmpty(this.props) !== this.isSlotEmpty(next);
|
||||
}
|
||||
|
||||
// Prevent Slot from rerendering when no props has shallowly changed.
|
||||
return changes.length !== 0;
|
||||
}
|
||||
|
||||
isSlotEmpty(props = this.props) {
|
||||
const {
|
||||
slot,
|
||||
className: _a,
|
||||
reduxState,
|
||||
component: _b = 'div',
|
||||
children: _c,
|
||||
queryData,
|
||||
...rest
|
||||
} = props;
|
||||
const slots = Array.isArray(slot) ? slot : [slot];
|
||||
return slots.every(slot =>
|
||||
this.context.plugins.isSlotEmpty(slot, reduxState, rest, queryData)
|
||||
);
|
||||
const { slotElements } = props;
|
||||
return slotElements.length === 0
|
||||
? false
|
||||
: slotElements.every(elements => elements.length === 0);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -44,10 +19,15 @@ class IfSlotIsEmpty extends React.Component {
|
||||
|
||||
IfSlotIsEmpty.propTypes = {
|
||||
slot: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
children: PropTypes.node.isRequired,
|
||||
passthrough: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
reduxState: state,
|
||||
});
|
||||
const omitProps = ['slot', 'children'];
|
||||
|
||||
export default connect(mapStateToProps, null)(IfSlotIsEmpty);
|
||||
export default compose(
|
||||
withCompatPassthrough(omitProps),
|
||||
withSlotElements({
|
||||
slot: props => props.slot,
|
||||
})
|
||||
)(IfSlotIsEmpty);
|
||||
|
||||
@@ -1,39 +1,14 @@
|
||||
import React, { Children } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getShallowChanges } from 'coral-framework/utils';
|
||||
import { withSlotElements, withCompatPassthrough } from '../hocs';
|
||||
import { compose } from 'recompose';
|
||||
|
||||
class IfSlotIsNotEmpty extends React.Component {
|
||||
static contextTypes = {
|
||||
plugins: PropTypes.object,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(next) {
|
||||
// Prevent Slot from rerendering when only reduxState has changed and
|
||||
// it does not result in a change.
|
||||
const changes = getShallowChanges(this.props, next);
|
||||
if (changes.length === 1 && changes[0] === 'reduxState') {
|
||||
return this.isSlotEmpty(this.props) !== this.isSlotEmpty(next);
|
||||
}
|
||||
|
||||
// Prevent Slot from rerendering when no props has shallowly changed.
|
||||
return changes.length !== 0;
|
||||
}
|
||||
|
||||
isSlotEmpty(props = this.props) {
|
||||
const {
|
||||
slot,
|
||||
className: _a,
|
||||
reduxState,
|
||||
component: _b = 'div',
|
||||
children: _c,
|
||||
queryData,
|
||||
...rest
|
||||
} = props;
|
||||
const slots = Array.isArray(slot) ? slot : [slot];
|
||||
return slots.every(slot =>
|
||||
this.context.plugins.isSlotEmpty(slot, reduxState, rest, queryData)
|
||||
);
|
||||
const { slotElements } = props;
|
||||
return slotElements.length === 0
|
||||
? false
|
||||
: slotElements.every(elements => elements.length === 0);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -44,10 +19,15 @@ class IfSlotIsNotEmpty extends React.Component {
|
||||
|
||||
IfSlotIsNotEmpty.propTypes = {
|
||||
slot: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
children: PropTypes.node.isRequired,
|
||||
passthrough: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
reduxState: state,
|
||||
});
|
||||
const omitProps = ['slot', 'children'];
|
||||
|
||||
export default connect(mapStateToProps, null)(IfSlotIsNotEmpty);
|
||||
export default compose(
|
||||
withCompatPassthrough(omitProps),
|
||||
withSlotElements({
|
||||
slot: props => props.slot,
|
||||
})
|
||||
)(IfSlotIsNotEmpty);
|
||||
|
||||
@@ -4,106 +4,29 @@ import styles from './Slot.css';
|
||||
import { connect } from 'react-redux';
|
||||
import kebabCase from 'lodash/kebabCase';
|
||||
import PropTypes from 'prop-types';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import get from 'lodash/get';
|
||||
import { getShallowChanges } from 'coral-framework/utils';
|
||||
import omit from 'lodash/omit';
|
||||
import merge from 'lodash/merge';
|
||||
|
||||
const emptyConfig = {};
|
||||
import { withSlotElements, withCompatPassthrough } from '../hocs';
|
||||
import { compose } from 'recompose';
|
||||
|
||||
class Slot extends React.Component {
|
||||
static contextTypes = {
|
||||
plugins: PropTypes.object,
|
||||
};
|
||||
|
||||
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 prevChildrenKeys = this.getChildren(this.props).map(
|
||||
child => child.key
|
||||
);
|
||||
const nextChildrenKeys = this.getChildren(next).map(child => child.key);
|
||||
return !isEqual(prevChildrenKeys, nextChildrenKeys);
|
||||
}
|
||||
|
||||
// Prevent Slot from rerendering when no props has shallowly changed.
|
||||
return changes.length !== 0;
|
||||
}
|
||||
|
||||
getSlotProps(props = this.props) {
|
||||
return omit(props, [
|
||||
'fill',
|
||||
'inline',
|
||||
'className',
|
||||
'reduxState',
|
||||
'slotSize',
|
||||
'defaultComponent',
|
||||
'queryData',
|
||||
'childFactory',
|
||||
'component',
|
||||
]);
|
||||
}
|
||||
|
||||
getChildren(props = this.props) {
|
||||
const { slotSize = 0 } = props;
|
||||
const { plugins } = this.context;
|
||||
|
||||
return plugins.getSlotElements(
|
||||
props.fill,
|
||||
props.reduxState,
|
||||
this.getSlotProps(props),
|
||||
props.queryData,
|
||||
{ slotSize }
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
inline = false,
|
||||
className,
|
||||
reduxState,
|
||||
debug,
|
||||
component: Component,
|
||||
childFactory,
|
||||
defaultComponent: DefaultComponent,
|
||||
queryData,
|
||||
fill,
|
||||
} = this.props;
|
||||
const { plugins } = this.context;
|
||||
let children = this.getChildren();
|
||||
|
||||
// @Deprecated plugin_config
|
||||
const pluginsConfig =
|
||||
merge(
|
||||
get(reduxState, 'config.plugins_config'),
|
||||
get(reduxState, 'config.plugin_config')
|
||||
) || emptyConfig;
|
||||
|
||||
if (children.length === 0 && DefaultComponent) {
|
||||
const props = plugins.getSlotComponentProps(
|
||||
DefaultComponent,
|
||||
reduxState,
|
||||
this.getSlotProps(this.props),
|
||||
queryData
|
||||
);
|
||||
children = <DefaultComponent {...props} />;
|
||||
}
|
||||
|
||||
let children = this.props.slotElements;
|
||||
if (childFactory) {
|
||||
children = children.map(childFactory);
|
||||
}
|
||||
|
||||
// console.log('pluginsConfig', pluginsConfig);
|
||||
|
||||
return (
|
||||
<Component
|
||||
className={cn(
|
||||
{
|
||||
[styles.inline]: inline,
|
||||
[styles.debug]: pluginsConfig.debug,
|
||||
},
|
||||
{ [styles.inline]: inline, [styles.debug]: debug },
|
||||
className,
|
||||
`talk-slot-${kebabCase(fill)}`
|
||||
)}
|
||||
@@ -122,13 +45,14 @@ Slot.propTypes = {
|
||||
fill: PropTypes.string.isRequired,
|
||||
inline: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
reduxState: PropTypes.object,
|
||||
debug: PropTypes.bool,
|
||||
slotElements: PropTypes.arrayOf(PropTypes.element).isRequired,
|
||||
defaultComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
|
||||
|
||||
/**
|
||||
* Specifies the number of children that can fill the slot.
|
||||
*/
|
||||
slotSize: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
|
||||
/**
|
||||
* You may specify the component to use as the root wrapper.
|
||||
@@ -137,8 +61,12 @@ Slot.propTypes = {
|
||||
component: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
|
||||
|
||||
// props coming from graphql must be passed through this property.
|
||||
// @Deprecated
|
||||
queryData: PropTypes.object,
|
||||
|
||||
// props that are passed to all Slot Components
|
||||
passthrough: PropTypes.object,
|
||||
|
||||
/**
|
||||
* You may need to apply reactive updates to a child as it is exiting.
|
||||
* This is generally done by using `cloneElement` however in the case of an exiting
|
||||
@@ -152,8 +80,27 @@ Slot.propTypes = {
|
||||
childFactory: PropTypes.func,
|
||||
};
|
||||
|
||||
const omitProps = [
|
||||
'fill',
|
||||
'inline',
|
||||
'className',
|
||||
'size',
|
||||
'defaultComponent',
|
||||
'queryData',
|
||||
'childFactory',
|
||||
'component',
|
||||
];
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
reduxState: state,
|
||||
debug: get(state, 'config.plugins_config.debug'),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(Slot);
|
||||
export default compose(
|
||||
withCompatPassthrough(omitProps),
|
||||
withSlotElements({
|
||||
slot: props => props.fill,
|
||||
size: props => props.size,
|
||||
defaultComponent: props => props.defaultComponent,
|
||||
}),
|
||||
connect(mapStateToProps, null)
|
||||
)(Slot);
|
||||
|
||||
@@ -12,6 +12,13 @@ export { default as withForgotPassword } from './withForgotPassword';
|
||||
export { default as withSetUsername } from './withSetUsername';
|
||||
export { default as withPopupAuthHandler } from './withPopupAuthHandler';
|
||||
export { default as withEnumValues } from './withEnumValues';
|
||||
export { default as withCompatPassthrough } from './withCompatPassthrough';
|
||||
export { default as withSlotElements } from './withSlotElements';
|
||||
export { default as withVariables } from './withVariables';
|
||||
export { default as WithRefetch } from './withRefetch';
|
||||
export { default as withFetchMore } from './withFetchMore';
|
||||
export { default as withSubscribeToMore } from './withSubscribeToMore';
|
||||
export { default as withGraphQLExtension } from './withGraphQLExtension';
|
||||
export {
|
||||
default as withResendEmailConfirmation,
|
||||
} from './withResendEmailConfirmation';
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import withProps from 'recompose/withProps';
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
function getPassthrough(props, omitProps) {
|
||||
const slotProps = omit(props, [...omitProps, 'passthrough']);
|
||||
|
||||
// @Deprecated
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (Object.keys(slotProps).length) {
|
||||
/* eslint-disable no-console */
|
||||
console.warn(
|
||||
`Slot '${
|
||||
props.fill
|
||||
}' passing through unknown props is deprecated, please use 'passthrough' instead`,
|
||||
slotProps
|
||||
);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
}
|
||||
|
||||
if (props.passthrough) {
|
||||
return props.passthrough;
|
||||
}
|
||||
|
||||
if (props.queryData) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
/* eslint-disable no-console */
|
||||
console.warn(
|
||||
`Slot '${
|
||||
props.fill
|
||||
}' property 'queryData' is deprecated, please use 'passthrough' instead`
|
||||
);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
return {
|
||||
...props.queryData,
|
||||
...slotProps,
|
||||
};
|
||||
}
|
||||
|
||||
return slotProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Deprecated
|
||||
* withCompatPassthrough is a compatibility HOC that supports our old
|
||||
* API which puts unknown props and `queryData` to `passhtrough` to be
|
||||
* used with HOC `withSlotElements`.
|
||||
*/
|
||||
export default omitProps =>
|
||||
withProps(props => ({
|
||||
passthrough: getPassthrough(props, omitProps),
|
||||
}));
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
import { Subscriber } from 'react-broadcast';
|
||||
import get from 'lodash/get';
|
||||
|
||||
/**
|
||||
* WithFetchMore provides a property `fetchMore` to the wrapped component.
|
||||
* Calling `fetchMore` is the same as calling `data.fetchMore` from the
|
||||
* Apollo React API.
|
||||
*/
|
||||
export default hoistStatics(WrappedComponent => {
|
||||
class WithFetchMore extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Subscriber channel="queryData">
|
||||
{data => (
|
||||
<WrappedComponent
|
||||
{...this.props}
|
||||
fetchMore={get(data, 'fetchMore')}
|
||||
/>
|
||||
)}
|
||||
</Subscriber>
|
||||
);
|
||||
}
|
||||
}
|
||||
return WithFetchMore;
|
||||
});
|
||||
@@ -6,27 +6,21 @@ import { getShallowChanges } from 'coral-framework/utils';
|
||||
import PropTypes from 'prop-types';
|
||||
import union from 'lodash/union';
|
||||
|
||||
// TODO: Should not depend on `props.data`
|
||||
// Currently necessary because of this https://github.com/apollographql/graphql-anywhere/issues/38
|
||||
function filter(doc, data, variables) {
|
||||
function filter(doc, data) {
|
||||
const resolver = (fieldName, root, args, context, info) => {
|
||||
return root[info.resultKey];
|
||||
};
|
||||
|
||||
return graphql(resolver, doc, data, null, variables);
|
||||
return graphql(resolver, doc, data, null, null, { includeAll: true });
|
||||
}
|
||||
|
||||
// filterProps returns only the property as defined in the fragments.
|
||||
// TODO: Should not depend on `props.data`
|
||||
function filterProps(props, fragments) {
|
||||
const filtered = {};
|
||||
Object.keys(fragments).forEach(key => {
|
||||
if (!(key in props)) {
|
||||
if (!(key in props) || props[key] === undefined) {
|
||||
return;
|
||||
}
|
||||
filtered[key] = props.data
|
||||
? filter(fragments[key], props[key], props.data.variables)
|
||||
: props[key];
|
||||
filtered[key] = filter(fragments[key], props[key]);
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
|
||||
/**
|
||||
* WithGraphQLExtension adds graphql configuration to the
|
||||
* GraphQL registry, only works on Components used
|
||||
* directly in a Slot.
|
||||
*/
|
||||
export default extension =>
|
||||
hoistStatics(WrappedComponent => {
|
||||
class WithGraphQLExtension extends React.Component {
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
WrappedComponent.graphqlExtension = extension;
|
||||
return WithGraphQLExtension;
|
||||
});
|
||||
@@ -11,6 +11,8 @@ import { getOperationName } from 'apollo-client/queries/getFromAST';
|
||||
import throttle from 'lodash/throttle';
|
||||
import get from 'lodash/get';
|
||||
import { notify } from 'coral-framework/actions/notification';
|
||||
import { Broadcast } from 'react-broadcast';
|
||||
import { compose } from 'recompose';
|
||||
|
||||
const withSkipOnErrors = reducer => (prev, action, ...rest) => {
|
||||
if (
|
||||
@@ -43,6 +45,15 @@ function networkStatusToString(networkStatus) {
|
||||
}
|
||||
}
|
||||
|
||||
// When wrapped broadcast all data changes to channel "queryData".
|
||||
const withBroadcaster = WrappedComponent => props => (
|
||||
/* eslint-disable react/prop-types */
|
||||
<Broadcast channel="queryData" value={props.data}>
|
||||
<WrappedComponent {...props} />
|
||||
</Broadcast>
|
||||
/* eslint-enable react/prop-types */
|
||||
);
|
||||
|
||||
const createHOC = (document, config, { notifyOnError = true }) =>
|
||||
hoistStatics(WrappedComponent => {
|
||||
return class WithQuery extends React.Component {
|
||||
@@ -283,6 +294,7 @@ const createHOC = (document, config, { notifyOnError = true }) =>
|
||||
props: args => {
|
||||
const nextData = this.nextData(args.data);
|
||||
const { root } = separateDataAndRoot(args.data);
|
||||
|
||||
if (config.props) {
|
||||
// Custom props, in this case we just pass the wrapped args to it.
|
||||
return config.props({
|
||||
@@ -323,10 +335,13 @@ const createHOC = (document, config, { notifyOnError = true }) =>
|
||||
if (!this.memoized) {
|
||||
this.resolvedDocument = this.resolveDocument(document);
|
||||
this.name = getDefinitionName(this.resolvedDocument);
|
||||
this.memoized = graphql(this.resolvedDocument, {
|
||||
...this.wrappedConfig,
|
||||
options: this.wrappedOptions,
|
||||
})(WrappedComponent);
|
||||
this.memoized = compose(
|
||||
graphql(this.resolvedDocument, {
|
||||
...this.wrappedConfig,
|
||||
options: this.wrappedOptions,
|
||||
}),
|
||||
withBroadcaster
|
||||
)(WrappedComponent);
|
||||
}
|
||||
return this.memoized;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
import { Subscriber } from 'react-broadcast';
|
||||
import get from 'lodash/get';
|
||||
|
||||
/**
|
||||
* WithRefetch provides a property `refetch` to the wrapped component.
|
||||
* Calling refetch will perform a refetch of the parent Query.
|
||||
*/
|
||||
export default hoistStatics(WrappedComponent => {
|
||||
class WithRefetch extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Subscriber channel="queryData">
|
||||
{data => (
|
||||
<WrappedComponent {...this.props} refetch={get(data, 'refetch')} />
|
||||
)}
|
||||
</Subscriber>
|
||||
);
|
||||
}
|
||||
}
|
||||
return WithRefetch;
|
||||
});
|
||||
@@ -0,0 +1,195 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { getShallowChanges } from 'coral-framework/utils';
|
||||
import { compose } from 'recompose';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
import isFunction from 'lodash/isFunction';
|
||||
|
||||
function resolvePrimitiveOrFunction(primitiveOrFunction, props) {
|
||||
if (isFunction(primitiveOrFunction)) {
|
||||
return primitiveOrFunction(props);
|
||||
}
|
||||
return primitiveOrFunction;
|
||||
}
|
||||
|
||||
const createHOC = ({
|
||||
slot,
|
||||
defaultComponent = null,
|
||||
passthroughPropName = 'passthrough',
|
||||
size = null,
|
||||
propName = 'slotElements',
|
||||
}) =>
|
||||
hoistStatics(WrappedComponent => {
|
||||
return class withSlotElements extends React.Component {
|
||||
static contextTypes = {
|
||||
plugins: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
reduxState: PropTypes.object,
|
||||
};
|
||||
|
||||
getSlots(props = this.props) {
|
||||
const tmp = resolvePrimitiveOrFunction(slot, props);
|
||||
if (Array.isArray(tmp)) {
|
||||
return tmp;
|
||||
}
|
||||
return [tmp];
|
||||
}
|
||||
|
||||
getDefaultComponents(props = this.props, fill = 1) {
|
||||
const tmp = resolvePrimitiveOrFunction(defaultComponent, props);
|
||||
if (Array.isArray(tmp)) {
|
||||
return tmp;
|
||||
}
|
||||
return new Array(fill).fill(tmp);
|
||||
}
|
||||
|
||||
getSizes(props = this.props, fill = 1) {
|
||||
const tmp = resolvePrimitiveOrFunction(size, props);
|
||||
if (Array.isArray(tmp)) {
|
||||
return tmp;
|
||||
}
|
||||
return new Array(fill).fill(tmp);
|
||||
}
|
||||
|
||||
getPassthrough(props = this.props) {
|
||||
return passthroughPropName ? props[passthroughPropName] : null;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Handle special `passthrough` props.
|
||||
if (passthroughPropName) {
|
||||
const passthroughIndex = changes.indexOf(passthroughPropName);
|
||||
if (passthroughIndex !== -1) {
|
||||
if (!this.props[passthroughPropName] || next[passthroughPropName]) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
getShallowChanges(
|
||||
this.props[passthroughPropName],
|
||||
next[passthroughPropName]
|
||||
).lenght === 0
|
||||
) {
|
||||
changes.splice(passthroughIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.length === 1 && changes[0] === 'reduxState') {
|
||||
const prevChildrenKeys = this.getSlotElements(this.props).map(
|
||||
child => child.key
|
||||
);
|
||||
const nextChildrenKeys = this.getSlotElements(next).map(
|
||||
child => child.key
|
||||
);
|
||||
return !isEqual(prevChildrenKeys, nextChildrenKeys);
|
||||
}
|
||||
|
||||
// Prevent Slot from rerendering when no props has shallowly changed.
|
||||
return changes.length !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns slot elements for configured slots. If only one slot is given
|
||||
* slot elements are returned directly. If more than one slot is specified
|
||||
* returns an array of slot elements.
|
||||
*/
|
||||
getSlotElements(props = this.props) {
|
||||
const { plugins } = this.context;
|
||||
const slots = this.getSlots(props);
|
||||
const sizes = this.getSizes(props, slots.length);
|
||||
const defaultComponents = this.getDefaultComponents(
|
||||
props,
|
||||
slots.length
|
||||
);
|
||||
const slotPassthrough = this.getPassthrough(props);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (slotPassthrough.data && slotPassthrough.data.refetch) {
|
||||
/* eslint-disable no-console */
|
||||
console.warn(
|
||||
'Slots no longer need `data` property.',
|
||||
'Plugins can use the new HOCs `withRefetch`,',
|
||||
'`withFetchMore`, `withSubscribeToMore` and `withVariables` instead.',
|
||||
'Affected slots: ',
|
||||
slots
|
||||
);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
}
|
||||
|
||||
const elements = [];
|
||||
slots.forEach((s, i) => {
|
||||
const DefaultComponent = defaultComponents[i];
|
||||
const size = sizes[i];
|
||||
const slotElements = plugins.getSlotElements(
|
||||
s,
|
||||
props.reduxState,
|
||||
slotPassthrough,
|
||||
{ size }
|
||||
);
|
||||
|
||||
if (slotElements.length === 0 && DefaultComponent) {
|
||||
const p = plugins.getSlotComponentProps(
|
||||
DefaultComponent,
|
||||
props.reduxState,
|
||||
slotPassthrough
|
||||
);
|
||||
slotElements.push(<DefaultComponent key="default" {...p} />);
|
||||
}
|
||||
|
||||
elements.push(slotElements);
|
||||
});
|
||||
|
||||
if (elements.length === 1) {
|
||||
return elements[0];
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { reduxState: _a, ...rest } = this.props;
|
||||
const slotElements = this.getSlotElements();
|
||||
const props = {
|
||||
[propName]: slotElements,
|
||||
...rest,
|
||||
};
|
||||
return <WrappedComponent {...props} />;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
reduxState: state,
|
||||
});
|
||||
|
||||
/**
|
||||
* Exports a HOC that provides a property `slotElements`.
|
||||
* @param {Object} [options] configuration
|
||||
* @param {string|array} [options.slot] Slot name or Array of Slot Names
|
||||
* @param {element|array} [options.defaultComponent] Default Components or Array of such
|
||||
* @param {number|array} [options.size] Slot size or an Array of slot size
|
||||
* @param {string} [options.passthroughPropName] The property to find the passthrough prop
|
||||
* @param {string} [options.propName] New property name, defaults to `slotElements`
|
||||
*
|
||||
* @return {func} Returns a HOC that per default provides the property `slotElements` with an
|
||||
* Array of Slot Elements or in case of multiple slots, an Array of Slot Element
|
||||
* Arrays.
|
||||
*
|
||||
* Example:
|
||||
* withSlotElements({
|
||||
* slot: 'awesomeSlot',
|
||||
* size: 1,
|
||||
* })(MyComponent);
|
||||
*/
|
||||
export default settings => {
|
||||
return compose(connect(mapStateToProps, null), createHOC(settings));
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
import { Subscriber } from 'react-broadcast';
|
||||
import get from 'lodash/get';
|
||||
|
||||
/**
|
||||
* WithSubscribeToMore provides a property `subscribeToMore` to the wrapped component.
|
||||
* Calling `subscribeToMore` is the same as calling `data.subscribeToMore` from the
|
||||
* Apollo React API.
|
||||
*/
|
||||
export default hoistStatics(WrappedComponent => {
|
||||
class WithSubscribeToMore extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Subscriber channel="queryData">
|
||||
{data => (
|
||||
<WrappedComponent
|
||||
{...this.props}
|
||||
subscribeToMore={get(data, 'subscribeToMoreThrottled')}
|
||||
/>
|
||||
)}
|
||||
</Subscriber>
|
||||
);
|
||||
}
|
||||
}
|
||||
return WithSubscribeToMore;
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
import { Subscriber } from 'react-broadcast';
|
||||
import get from 'lodash/get';
|
||||
|
||||
/**
|
||||
* WithVariables provides a property `variables` to the wrapped component.
|
||||
* These are the variables of the parent Query.
|
||||
*/
|
||||
export default hoistStatics(WrappedComponent => {
|
||||
class WithVariables extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Subscriber channel="queryData">
|
||||
{data => (
|
||||
<WrappedComponent
|
||||
{...this.props}
|
||||
variables={get(data, 'variables')}
|
||||
/>
|
||||
)}
|
||||
</Subscriber>
|
||||
);
|
||||
}
|
||||
}
|
||||
return WithVariables;
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import isEmpty from 'lodash/isEmpty';
|
||||
import flatten from 'lodash/flatten';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import get from 'lodash/get';
|
||||
import values from 'lodash/values';
|
||||
import { getDisplayName } from 'coral-framework/helpers/hoc';
|
||||
import camelize from '../helpers/camelize';
|
||||
|
||||
@@ -67,71 +68,102 @@ function addMetaDataToSlotComponents(plugins) {
|
||||
});
|
||||
}
|
||||
|
||||
// @Deprecated
|
||||
const showPluginConfigDeprecationWarningOnce = (() => {
|
||||
let shown = false;
|
||||
return () => {
|
||||
if (!shown) {
|
||||
shown = true;
|
||||
console.warn(
|
||||
`deprecation warning: config.plugin_config will be phased out soon, please replace calls from config.plugin_config to config.plugins_config`
|
||||
);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* getSlotComponentProps calculate the props we would pass to the slot component.
|
||||
* query datas are only passed to the component if it is defined in `component.fragments`.
|
||||
*/
|
||||
function getSlotComponentProps(component, reduxState, props, queryData) {
|
||||
// @Deprecated
|
||||
const pluginsConfig =
|
||||
get(reduxState, 'config.plugins_config') ||
|
||||
get(reduxState, 'config.plugin_config') ||
|
||||
emptyConfig;
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
!!get(reduxState, 'config.plugin_config')
|
||||
) {
|
||||
showPluginConfigDeprecationWarningOnce();
|
||||
}
|
||||
|
||||
console.log('slot plugins_config', get(reduxState, 'config.plugins_config'));
|
||||
|
||||
const debugProps = pluginsConfig.debug
|
||||
? {
|
||||
'data-slot-name': props.fill,
|
||||
}
|
||||
: {};
|
||||
return {
|
||||
...props,
|
||||
...debugProps,
|
||||
config: pluginsConfig,
|
||||
...(component.fragments
|
||||
? pick(queryData, Object.keys(component.fragments))
|
||||
: withWarnings(component, queryData)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* splitProps detects objects coming from the query and
|
||||
* returns `queryData` and `rest`. We use `__typename`
|
||||
* in order to detect objects from the query.
|
||||
*/
|
||||
function splitProps(props) {
|
||||
const rest = { ...props };
|
||||
const queryData = {};
|
||||
Object.keys(props).forEach(k => {
|
||||
if (
|
||||
get(props[k], `__typename`) ||
|
||||
get(props[k], `0.__typename`) // Arrays
|
||||
) {
|
||||
queryData[k] = props[k];
|
||||
delete rest[k];
|
||||
}
|
||||
});
|
||||
return { queryData, rest };
|
||||
}
|
||||
|
||||
class PluginsService {
|
||||
constructor(plugins) {
|
||||
this.plugins = plugins;
|
||||
addMetaDataToSlotComponents(plugins);
|
||||
}
|
||||
|
||||
isSlotEmpty(slot, reduxState, props = {}, queryData = {}) {
|
||||
return (
|
||||
this.getSlotElements(slot, reduxState, props, queryData).length === 0
|
||||
);
|
||||
isSlotEmpty(slot, reduxState, props = {}) {
|
||||
return this.getSlotElements(slot, reduxState, props).length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* getSlotComponentProps calculate the props we would pass to the slot component.
|
||||
* query datas are only passed to the component if it is defined in `component.fragments`.
|
||||
* Returns props that would pass to the given slot component.
|
||||
*/
|
||||
showPluginsConfigWarning = true;
|
||||
|
||||
getSlotComponentProps(component, reduxState, props, queryData) {
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
!!get(reduxState, 'config.plugin_config') &&
|
||||
this.showPluginsConfigWarning
|
||||
) {
|
||||
console.warn(
|
||||
`deprecation warning: config.plugin_config will be phased out soon, please replace calls from config.plugin_config to config.plugins_config`
|
||||
);
|
||||
this.showPluginsConfigWarning = false;
|
||||
}
|
||||
|
||||
console.log(
|
||||
'slot plugins_config',
|
||||
get(reduxState, 'config.plugins_config')
|
||||
);
|
||||
|
||||
// @Deprecated plugin_config
|
||||
const pluginsConfig =
|
||||
merge(
|
||||
get(reduxState, 'config.plugins_config'),
|
||||
get(reduxState, 'config.plugin_config')
|
||||
) || emptyConfig;
|
||||
|
||||
const debugProps = pluginsConfig.debug
|
||||
? {
|
||||
'data-slot-name': props.fill,
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
...props,
|
||||
...debugProps,
|
||||
config: pluginsConfig,
|
||||
...(component.fragments
|
||||
? pick(queryData, Object.keys(component.fragments))
|
||||
: withWarnings(component, queryData)),
|
||||
};
|
||||
getSlotComponentProps(component, reduxState, props) {
|
||||
const { queryData, rest } = splitProps(props);
|
||||
return getSlotComponentProps(component, reduxState, rest, queryData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns React Elements for given slot.
|
||||
*/
|
||||
getSlotElements(slot, reduxState, props = {}, queryData = {}, options = {}) {
|
||||
getSlotElements(slot, reduxState, props = {}, options = {}) {
|
||||
const pluginsConfig =
|
||||
get(reduxState, 'config.plugins_config') || emptyConfig;
|
||||
const { slotSize = 0 } = options;
|
||||
get(reduxState, 'config.plugins_config') ||
|
||||
get(reduxState, 'config.plugin_config') ||
|
||||
emptyConfig;
|
||||
const { size = 0 } = options;
|
||||
const { queryData, rest } = splitProps(props);
|
||||
|
||||
const isDisabled = component => {
|
||||
if (
|
||||
@@ -144,10 +176,10 @@ class PluginsService {
|
||||
|
||||
// Check if component is excluded.
|
||||
if (component.isExcluded) {
|
||||
let resolvedProps = this.getSlotComponentProps(
|
||||
let resolvedProps = getSlotComponentProps(
|
||||
component,
|
||||
reduxState,
|
||||
props,
|
||||
rest,
|
||||
queryData
|
||||
);
|
||||
if (component.mapStateToProps) {
|
||||
@@ -168,15 +200,15 @@ class PluginsService {
|
||||
.map(o => o.module.slots[slot])
|
||||
);
|
||||
|
||||
if (slotSize > 0 && slots.length > slotSize) {
|
||||
if (size > 0 && slots.length > size) {
|
||||
console.warn(
|
||||
`Slot[${slot}] supports a maximum of ${slotSize} plugins providing slots, got ${
|
||||
`Slot[${slot}] supports a maximum of ${size} plugins providing slots, got ${
|
||||
slots.length
|
||||
}, will only use the first ${slotSize}`
|
||||
}, will only use the first ${size}`
|
||||
);
|
||||
}
|
||||
|
||||
return (slotSize > 0 ? slots.slice(0, slotSize) : slots)
|
||||
return (size > 0 ? slots.slice(0, size) : slots)
|
||||
.map((component, i) => ({
|
||||
component,
|
||||
disabled: isDisabled(component),
|
||||
@@ -186,12 +218,7 @@ class PluginsService {
|
||||
.map(({ component, key }) =>
|
||||
React.createElement(component, {
|
||||
key,
|
||||
...this.getSlotComponentProps(
|
||||
component,
|
||||
reduxState,
|
||||
props,
|
||||
queryData
|
||||
),
|
||||
...getSlotComponentProps(component, reduxState, rest, queryData),
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -217,9 +244,16 @@ class PluginsService {
|
||||
}
|
||||
|
||||
getGraphQLExtensions() {
|
||||
return this.plugins
|
||||
.map(o => pick(o.module, ['mutations', 'queries', 'fragments']))
|
||||
.filter(o => !isEmpty(o));
|
||||
return flatten(
|
||||
this.plugins.map(o => {
|
||||
const extension = pick(o.module, ['mutations', 'queries', 'fragments']);
|
||||
// Include extension defined in Slot Components.
|
||||
const slotComponentExtensions = !o.module.slots
|
||||
? []
|
||||
: flatten(values(o.module.slots)).map(cmp => cmp.graphqlExtension);
|
||||
return [extension, ...slotComponentExtensions];
|
||||
})
|
||||
).filter(o => !isEmpty(o));
|
||||
}
|
||||
|
||||
getModQueueConfigs() {
|
||||
|
||||
Reference in New Issue
Block a user