From 7bcad6d0c03ce985f40e461b73cff5550eebb4e0 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 9 Mar 2018 14:50:18 +0100 Subject: [PATCH 01/56] Attach query markers --- client/coral-framework/hocs/withQuery.js | 36 +++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index c4b9074b4..2c941dc57 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -22,6 +22,34 @@ const withSkipOnErrors = reducer => (prev, action, ...rest) => { return reducer(prev, action, ...rest); }; +/** + * attachFromQueryMarkers will add a `__unsafeFromQuery` to objects + * inside data, to allow other parts of the framework to easily detect + * that the data came from a query. + */ +function attachFromQueryMarkers(data) { + if (typeof data === 'object') { + if (data === null) { + return data; + } + if (Array.isArray(data)) { + const result = [...data]; + result.forEach((v, k) => { + result[k] = attachFromQueryMarkers(v); + }); + return result; + } else { + const result = { ...data }; + result.__unsafeFromQuery = true; + Object.keys(result).forEach(key => { + result[key] = attachFromQueryMarkers(result[key]); + }); + return result; + } + } + return data; +} + function networkStatusToString(networkStatus) { switch (networkStatus) { case 1: @@ -283,6 +311,12 @@ const createHOC = (document, config, { notifyOnError = true }) => props: args => { const nextData = this.nextData(args.data); const { root } = separateDataAndRoot(args.data); + + // We attach query markes `__fromQuery` to each node in + // the returned `root` result, so the framework can + // easily detect props coming from the query. + const rootWithQueryMarkers = attachFromQueryMarkers(root); + if (config.props) { // Custom props, in this case we just pass the wrapped args to it. return config.props({ @@ -292,7 +326,7 @@ const createHOC = (document, config, { notifyOnError = true }) => } // Return our wrapped data with a separated root. - return { ...args, data: nextData, root }; + return { ...args, data: nextData, root: rootWithQueryMarkers }; }, }; From e0dc87136726e619f654a3575862b57d2c02789c Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 9 Mar 2018 14:50:34 +0100 Subject: [PATCH 02/56] Rename slotSize to size --- client/coral-admin/src/components/UserDetailComment.js | 2 +- .../src/routes/Moderation/components/Comment.js | 2 +- .../src/tabs/stream/components/Comment.js | 2 +- client/coral-framework/components/Slot.js | 8 ++++---- client/coral-framework/services/plugins.js | 10 +++++----- plugins/talk-plugin-member-since/client/index.js | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index ba185a9df..ea898822a 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -94,7 +94,7 @@ class UserDetailComment extends React.Component { 'talk-admin-user-detail-comment' )} queryData={queryData} - slotSize={1} + size={1} defaultComponent={CommentFormatter} {...formatterSettings} /> diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index f865908b2..3fb3debf5 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -138,7 +138,7 @@ class Comment extends React.Component { className={cn(styles.commentContent, 'talk-admin-comment')} clearHeightCache={clearHeightCache} queryData={queryData} - slotSize={1} + size={1} defaultComponent={CommentFormatter} {...formatterSettings} /> diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js index c495b54f9..dd465fed4 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js @@ -667,7 +667,7 @@ export default class Comment extends React.Component { defaultComponent={CommentContent} {...slotProps} queryData={queryData} - slotSize={1} + size={1} /> )} diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 952844ed0..73f6f1bb0 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -38,7 +38,7 @@ class Slot extends React.Component { 'inline', 'className', 'reduxState', - 'slotSize', + 'size', 'defaultComponent', 'queryData', 'childFactory', @@ -47,7 +47,7 @@ class Slot extends React.Component { } getChildren(props = this.props) { - const { slotSize = 0 } = props; + const { size = 0 } = props; const { plugins } = this.context; return plugins.getSlotElements( @@ -55,7 +55,7 @@ class Slot extends React.Component { props.reduxState, this.getSlotProps(props), props.queryData, - { slotSize } + { size } ); } @@ -116,7 +116,7 @@ Slot.propTypes = { /** * 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. diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 222cc1b42..d797e0e3e 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -99,7 +99,7 @@ class PluginsService { */ getSlotElements(slot, reduxState, props = {}, queryData = {}, options = {}) { const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; - const { slotSize = 0 } = options; + const { size = 0 } = options; const isDisabled = component => { if ( @@ -136,15 +136,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), diff --git a/plugins/talk-plugin-member-since/client/index.js b/plugins/talk-plugin-member-since/client/index.js index 2091e4805..fe0b5b14b 100644 --- a/plugins/talk-plugin-member-since/client/index.js +++ b/plugins/talk-plugin-member-since/client/index.js @@ -24,7 +24,7 @@ export default { createComment: { comment: { user: { - created_at: new Date(), + created_at: new Date().toISOString(), __typename: 'User', }, __typename: 'Comment', From 1b55684a739e1b7f736684d75aea33cc0be6a6be Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 9 Mar 2018 17:33:34 +0100 Subject: [PATCH 03/56] New Slot property `passthrough` --- .../components/IfSlotIsEmpty.js | 78 ++++++++++++++++--- .../components/IfSlotIsNotEmpty.js | 78 ++++++++++++++++--- client/coral-framework/components/Slot.js | 67 ++++++++++++++-- client/coral-framework/services/plugins.js | 68 ++++++++++------ 4 files changed, 238 insertions(+), 53 deletions(-) diff --git a/client/coral-framework/components/IfSlotIsEmpty.js b/client/coral-framework/components/IfSlotIsEmpty.js index 9aebd94c6..fa8f2524d 100644 --- a/client/coral-framework/components/IfSlotIsEmpty.js +++ b/client/coral-framework/components/IfSlotIsEmpty.js @@ -2,6 +2,7 @@ import React, { Children } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { getShallowChanges } from 'coral-framework/utils'; +import omit from 'lodash/omit'; class IfSlotIsEmpty extends React.Component { static contextTypes = { @@ -9,9 +10,23 @@ class IfSlotIsEmpty extends React.Component { }; shouldComponentUpdate(next) { + const changes = getShallowChanges(this.props, next); + + // Handle special `passthrough` props. + const passthroughIndex = changes.indexOf('passthrough'); + if (passthroughIndex !== -1) { + if (!this.props.passthrough || next.passthrough) { + return true; + } + if ( + getShallowChanges(this.props.passthrough, next.passthrough).lenght === 0 + ) { + changes.splice(passthroughIndex, 1); + } + } + // 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); } @@ -20,19 +35,58 @@ class IfSlotIsEmpty extends React.Component { return changes.length !== 0; } + getPassthrough(props = this.props) { + const slotProps = omit(props, [ + 'slot', + 'children', + 'reduxState', + 'passthrough', + 'dispatch', + ]); + + // @Deprecated + if (process.env.NODE_ENV !== 'production') { + if (Object.keys(slotProps).length) { + /* eslint-disable no-console */ + console.warn( + `IfSlotIsEmpty '${ + 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; + } + isSlotEmpty(props = this.props) { - const { - slot, - className: _a, - reduxState, - component: _b = 'div', - children: _c, - queryData, - ...rest - } = props; + const { slot, reduxState } = props; + const passthrough = this.getPassthrough(props); const slots = Array.isArray(slot) ? slot : [slot]; return slots.every(slot => - this.context.plugins.isSlotEmpty(slot, reduxState, rest, queryData) + this.context.plugins.isSlotEmpty(slot, reduxState, passthrough) ); } @@ -44,6 +98,8 @@ class IfSlotIsEmpty extends React.Component { IfSlotIsEmpty.propTypes = { slot: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), + children: PropTypes.node.isRequired, + passthrough: PropTypes.object, }; const mapStateToProps = state => ({ diff --git a/client/coral-framework/components/IfSlotIsNotEmpty.js b/client/coral-framework/components/IfSlotIsNotEmpty.js index 03f300858..024877fbc 100644 --- a/client/coral-framework/components/IfSlotIsNotEmpty.js +++ b/client/coral-framework/components/IfSlotIsNotEmpty.js @@ -2,6 +2,7 @@ import React, { Children } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { getShallowChanges } from 'coral-framework/utils'; +import omit from 'lodash/omit'; class IfSlotIsNotEmpty extends React.Component { static contextTypes = { @@ -9,9 +10,23 @@ class IfSlotIsNotEmpty extends React.Component { }; shouldComponentUpdate(next) { + const changes = getShallowChanges(this.props, next); + + // Handle special `passthrough` props. + const passthroughIndex = changes.indexOf('passthrough'); + if (passthroughIndex !== -1) { + if (!this.props.passthrough || next.passthrough) { + return true; + } + if ( + getShallowChanges(this.props.passthrough, next.passthrough).lenght === 0 + ) { + changes.splice(passthroughIndex, 1); + } + } + // 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); } @@ -20,19 +35,58 @@ class IfSlotIsNotEmpty extends React.Component { return changes.length !== 0; } + getPassthrough(props = this.props) { + const slotProps = omit(props, [ + 'slot', + 'children', + 'reduxState', + 'passthrough', + 'dispatch', + ]); + + // @Deprecated + if (process.env.NODE_ENV !== 'production') { + if (Object.keys(slotProps).length) { + /* eslint-disable no-console */ + console.warn( + `IfSlotIsEmpty '${ + 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; + } + isSlotEmpty(props = this.props) { - const { - slot, - className: _a, - reduxState, - component: _b = 'div', - children: _c, - queryData, - ...rest - } = props; + const { slot, reduxState } = props; + const passthrough = this.getPassthrough(props); const slots = Array.isArray(slot) ? slot : [slot]; return slots.every(slot => - this.context.plugins.isSlotEmpty(slot, reduxState, rest, queryData) + this.context.plugins.isSlotEmpty(slot, reduxState, passthrough) ); } @@ -44,6 +98,8 @@ class IfSlotIsNotEmpty extends React.Component { IfSlotIsNotEmpty.propTypes = { slot: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), + children: PropTypes.node.isRequired, + passthrough: PropTypes.object, }; const mapStateToProps = state => ({ diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 73f6f1bb0..02f182dc3 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -20,6 +20,20 @@ class Slot extends React.Component { // 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. + const passthroughIndex = changes.indexOf('passthrough'); + if (passthroughIndex !== -1) { + if (!this.props.passthrough || next.passthrough) { + return true; + } + if ( + getShallowChanges(this.props.passthrough, next.passthrough).lenght === 0 + ) { + changes.splice(passthroughIndex, 1); + } + } + if (changes.length === 1 && changes[0] === 'reduxState') { const prevChildrenKeys = this.getChildren(this.props).map( child => child.key @@ -32,8 +46,8 @@ class Slot extends React.Component { return changes.length !== 0; } - getSlotProps(props = this.props) { - return omit(props, [ + getPassthrough(props = this.props) { + const slotProps = omit(props, [ 'fill', 'inline', 'className', @@ -43,7 +57,45 @@ class Slot extends React.Component { 'queryData', 'childFactory', 'component', + 'passthrough', + 'dispatch', ]); + + // @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; } getChildren(props = this.props) { @@ -53,8 +105,7 @@ class Slot extends React.Component { return plugins.getSlotElements( props.fill, props.reduxState, - this.getSlotProps(props), - props.queryData, + this.getPassthrough(props), { size } ); } @@ -67,7 +118,6 @@ class Slot extends React.Component { component: Component, childFactory, defaultComponent: DefaultComponent, - queryData, fill, } = this.props; const { plugins } = this.context; @@ -78,8 +128,7 @@ class Slot extends React.Component { const props = plugins.getSlotComponentProps( DefaultComponent, reduxState, - this.getSlotProps(this.props), - queryData + this.getPassthrough() ); children = ; } @@ -125,8 +174,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 diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index d797e0e3e..3879249ac 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -67,39 +67,64 @@ function addMetaDataToSlotComponents(plugins) { }); } +/** + * 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) { + const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; + return { + ...props, + config: pluginConfig, + ...(component.fragments + ? pick(queryData, Object.keys(component.fragments)) + : withWarnings(component, queryData)), + }; +} + +/** + * splitProps detects props coming from the query and + * returns `queryData` and `rest`. + */ +function splitProps(props) { + const rest = { ...props }; + const queryData = {}; + if (props.passthrough) { + Object.keys(props).forEach(k => { + if (props[k].__unsafeFromQuery) { + 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. */ - getSlotComponentProps(component, reduxState, props, queryData) { - const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; - return { - ...props, - config: pluginConfig, - ...(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 pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; const { size = 0 } = options; + const { queryData, rest } = splitProps(props); const isDisabled = component => { if ( @@ -112,10 +137,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) { @@ -154,12 +179,7 @@ class PluginsService { .map(({ component, key }) => React.createElement(component, { key, - ...this.getSlotComponentProps( - component, - reduxState, - props, - queryData - ), + ...getSlotComponentProps(component, reduxState, rest, queryData), }) ); } From 44b8809db1448ee8edcae064d5e8f7349c415e7f Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 9 Mar 2018 17:34:05 +0100 Subject: [PATCH 04/56] Port to new Slot property `passthrough` --- .../src/components/Embed.js | 6 +- .../src/containers/ExtendableTabPanel.js | 6 +- .../src/tabs/configure/components/Settings.js | 9 ++- .../src/tabs/configure/containers/Settings.js | 13 +++- .../src/tabs/profile/components/Comment.js | 12 ++-- .../src/tabs/profile/components/Profile.js | 63 ++++++++++--------- .../src/tabs/stream/components/Comment.js | 34 ++++------ .../src/tabs/stream/components/DraftArea.js | 35 ++++++----- .../src/tabs/stream/components/Stream.js | 20 +++--- .../src/tabs/stream/containers/CommentBox.js | 8 ++- .../src/tabs/stream/containers/DraftArea.js | 5 +- .../client/components/AuthorName.js | 30 +++++---- .../client/components/Menu.js | 22 ++++--- .../client/containers/AuthorName.js | 33 ++++++++-- .../client/components/Comment.js | 17 ++--- .../client/components/FlagDetails.js | 8 +-- .../client/components/ModerationActions.js | 9 ++- .../client/components/Settings.js | 14 +++-- .../client/components/TabPane.js | 3 +- .../client/components/Category.js | 14 +++-- .../client/components/Menu.js | 8 +++ .../client/components/ViewingOptions.js | 12 +++- .../client/containers/ViewingOptions.js | 13 +++- 23 files changed, 228 insertions(+), 166 deletions(-) diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 0e251e9a3..c2badd984 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -63,6 +63,7 @@ export default class Embed extends React.Component { } = this.props; const hasHighlightedComment = !!commentId; const popupUrl = `login?parentUrl=${encodeURIComponent(parentUrl)}`; + const slotPassthrough = { data, root }; return (
- +
@@ -77,7 +77,7 @@ class Settings extends React.Component {
)} - +
); @@ -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, diff --git a/client/coral-embed-stream/src/tabs/configure/containers/Settings.js b/client/coral-embed-stream/src/tabs/configure/containers/Settings.js index 2058ceaa2..652550a5d 100644 --- a/client/coral-embed-stream/src/tabs/configure/containers/Settings.js +++ b/client/coral-embed-stream/src/tabs/configure/containers/Settings.js @@ -63,11 +63,20 @@ class SettingsContainer extends React.Component { errors, updatePending, } = this.props; + + const slotPassthrough = { + root, + asset, + settings: mergedSettings, + data, + updatePending, + errors, + }; + return ( @@ -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} />
diff --git a/client/coral-embed-stream/src/tabs/profile/components/Profile.js b/client/coral-embed-stream/src/tabs/profile/components/Profile.js index 3b707ebe7..bde624063 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Profile.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Profile.js @@ -14,36 +14,41 @@ const Profile = ({ root, activeTab, setActiveTab, -}) => ( -
-
-

{username}

- {emailAddress ?

{emailAddress}

: null} +}) => { + const slotPassthrough = { + data, + root, + }; + return ( +
+
+

{username}

+ {emailAddress ?

{emailAddress}

: null} +
+ + + {t('framework.my_comments')} + , + ]} + tabPanes={[ + + + , + ]} + sub + />
- - - {t('framework.my_comments')} - , - ]} - tabPanes={[ - - - , - ]} - sub - /> -
-); + ); +}; Profile.propTypes = { username: PropTypes.string, diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js index dd465fed4..cc1809f6e 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js @@ -535,12 +535,9 @@ export default class Comment extends React.Component { ); // props that are passed down the slots. - const slotProps = { + const slotPassthrough = { data, depth, - }; - - const queryData = { root, asset, comment, @@ -551,8 +548,7 @@ export default class Comment extends React.Component { @@ -573,8 +569,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} />
@@ -607,9 +601,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 ? ( @@ -624,8 +619,7 @@ export default class Comment extends React.Component { {isActive && @@ -665,9 +659,8 @@ export default class Comment extends React.Component { fill="commentContent" className="talk-stream-comment-content" defaultComponent={CommentContent} - {...slotProps} - queryData={queryData} size={1} + passthrough={slotPassthrough} />
)} @@ -678,8 +671,7 @@ export default class Comment extends React.Component {
@@ -696,9 +688,7 @@ export default class Comment extends React.Component {
diff --git a/client/coral-embed-stream/src/tabs/stream/components/DraftArea.js b/client/coral-embed-stream/src/tabs/stream/components/DraftArea.js index 202349370..7150a124d 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/DraftArea.js +++ b/client/coral-embed-stream/src/tabs/stream/components/DraftArea.js @@ -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 (
@@ -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, diff --git a/client/coral-embed-stream/src/tabs/stream/components/Stream.js b/client/coral-embed-stream/src/tabs/stream/components/Stream.js index adcc4aae8..a2d1475ce 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Stream.js @@ -147,15 +147,14 @@ class Stream extends React.Component { loading, } = this.props; - const slotProps = { data }; - const slotQueryData = { root, asset }; + const slotPassthrough = { data, root, asset }; // `key` of `ExtendableTabPanel` depends on sorting so that we always reset // the state when changing sorting. return (
- +
@@ -243,13 +241,13 @@ class Stream extends React.Component { !changedUsername && !highlightedComment) || keepCommentBox); - const slotProps = { data }; - const slotQueryData = { root, asset }; if (highlightedComment === null) { return {t('stream.comment_not_found')}; } + const slotPassthrough = { data, root, asset }; + return (
{open ? ( @@ -263,11 +261,7 @@ class Stream extends React.Component { content={asset.settings.questionBoxContent} icon={asset.settings.questionBoxIcon} > - + )} {!banned && @@ -304,7 +298,7 @@ class Stream extends React.Component {

{asset.settings.closedMessage}

)} - + {currentUser && ( } diff --git a/client/coral-embed-stream/src/tabs/stream/containers/DraftArea.js b/client/coral-embed-stream/src/tabs/stream/containers/DraftArea.js index 47338fc8c..f68c41dda 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/DraftArea.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/DraftArea.js @@ -42,11 +42,10 @@ class DraftAreaContainer extends React.Component { } render() { - const queryData = { comment: this.props.comment, root: this.props.root }; - return ( - {comment.user.username} + {username} {menuVisible && ( - + )}
); }; + +AuthorName.propTypes = { + slotPassthrough: PropTypes.object.isRequired, + username: PropTypes.string.isRequired, + menuVisible: PropTypes.bool.isRequired, + toggleMenu: PropTypes.func.isRequired, + hideMenu: PropTypes.func.isRequired, + contentSlot: PropTypes.string, +}; + +export default AuthorName; diff --git a/plugins/talk-plugin-author-menu/client/components/Menu.js b/plugins/talk-plugin-author-menu/client/components/Menu.js index e041720eb..6c46fb1c3 100644 --- a/plugins/talk-plugin-author-menu/client/components/Menu.js +++ b/plugins/talk-plugin-author-menu/client/components/Menu.js @@ -1,17 +1,14 @@ import React from 'react'; +import PropTypes from 'prop-types'; import styles from './Menu.css'; import { Slot } from 'plugin-api/beta/client/components'; import cn from 'classnames'; -export default ({ data, root, asset, comment, contentSlot }) => { +const Menu = ({ slotPassthrough, contentSlot }) => { if (contentSlot) { return (
- +
); } @@ -21,15 +18,20 @@ export default ({ data, root, asset, comment, contentSlot }) => {
); }; + +Menu.propTypes = { + slotPassthrough: PropTypes.object.isRequired, + contentSlot: PropTypes.string, +}; + +export default Menu; diff --git a/plugins/talk-plugin-author-menu/client/containers/AuthorName.js b/plugins/talk-plugin-author-menu/client/containers/AuthorName.js index b06dbf1bb..958610093 100644 --- a/plugins/talk-plugin-author-menu/client/containers/AuthorName.js +++ b/plugins/talk-plugin-author-menu/client/containers/AuthorName.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { connect, withFragments } from 'plugin-api/beta/client/hocs'; import { bindActionCreators } from 'redux'; import AuthorName from '../components/AuthorName'; @@ -47,21 +48,41 @@ class AuthorNameContainer extends React.Component { }; render() { + const { + data, + root, + asset, + comment, + contentSlot, + showMenuForComment, + } = this.props; + + const slotPassthrough = { data, root, asset, comment }; + return ( ); } } +AuthorNameContainer.propTypes = { + data: PropTypes.object.isRequired, + root: PropTypes.object.isRequired, + asset: PropTypes.object.isRequired, + comment: PropTypes.object.isRequired, + contentSlot: PropTypes.string, + showMenuForComment: PropTypes.string, + openMenu: PropTypes.func.isRequired, + closeMenu: PropTypes.func.isRequired, +}; + const slots = ['authorMenuInfos', 'authorMenuActions']; const mapStateToProps = ({ talkPluginAuthorMenu: state }) => ({ diff --git a/plugins/talk-plugin-featured-comments/client/components/Comment.js b/plugins/talk-plugin-featured-comments/client/components/Comment.js index ce4aa7a5c..9b1a63d6a 100644 --- a/plugins/talk-plugin-featured-comments/client/components/Comment.js +++ b/plugins/talk-plugin-featured-comments/client/components/Comment.js @@ -19,7 +19,7 @@ class Comment extends React.Component { render() { const { comment, asset, root, data } = this.props; - const queryData = { comment, asset, root }; + const slotPassthrough = { data, comment, asset, root }; return (
@@ -36,8 +35,7 @@ class Comment extends React.Component { className={cn(styles.username, `${pluginName}-comment-username`)} fill="commentAuthorName" defaultComponent={CommentAuthorName} - queryData={queryData} - data={data} + passthrough={slotPassthrough} inline /> @@ -45,9 +43,7 @@ class Comment extends React.Component { fill="commentTimestamp" defaultComponent={CommentTimestamp} className={cn(styles.timestamp, `${pluginName}-comment-timestamp`)} - created_at={comment.created_at} - data={data} - queryData={queryData} + passthrough={{ created_at: comment.created_at, ...slotPassthrough }} inline />
@@ -62,10 +58,7 @@ class Comment extends React.Component { > diff --git a/plugins/talk-plugin-flag-details/client/components/FlagDetails.js b/plugins/talk-plugin-flag-details/client/components/FlagDetails.js index 607ba48a2..7197c0360 100644 --- a/plugins/talk-plugin-flag-details/client/components/FlagDetails.js +++ b/plugins/talk-plugin-flag-details/client/components/FlagDetails.js @@ -26,7 +26,8 @@ class FlagDetails extends Component { }, {}); const reasons = Object.keys(summaries); - const queryData = { + const slotPassthrough = { + data, root, comment, }; @@ -52,12 +53,11 @@ class FlagDetails extends Component { {more && ( )} diff --git a/plugins/talk-plugin-moderation-actions/client/components/ModerationActions.js b/plugins/talk-plugin-moderation-actions/client/components/ModerationActions.js index 68abce826..6cf2558c1 100644 --- a/plugins/talk-plugin-moderation-actions/client/components/ModerationActions.js +++ b/plugins/talk-plugin-moderation-actions/client/components/ModerationActions.js @@ -22,6 +22,12 @@ export default class ModerationActions extends React.Component { hideMenu, } = this.props; + const slotPassthrough = { + comment, + asset, + data, + }; + return (
diff --git a/plugins/talk-plugin-notifications/client/components/Settings.js b/plugins/talk-plugin-notifications/client/components/Settings.js index 010422491..9fd16bc0d 100644 --- a/plugins/talk-plugin-notifications/client/components/Settings.js +++ b/plugins/talk-plugin-notifications/client/components/Settings.js @@ -29,8 +29,15 @@ class Settings extends React.Component { email, } = this.props; + const slotPassthrough = { + root, + setTurnOffInputFragment, + updateNotificationSettings, + disabled: needEmailVerification, + }; + return ( - +

{t('talk-plugin-notifications.settings_title')}

{needEmailVerification && } @@ -45,11 +52,8 @@ class Settings extends React.Component { - +
); } diff --git a/plugins/talk-plugin-viewing-options/client/components/Category.js b/plugins/talk-plugin-viewing-options/client/components/Category.js index 0fc13d16f..78ce4dae3 100644 --- a/plugins/talk-plugin-viewing-options/client/components/Category.js +++ b/plugins/talk-plugin-viewing-options/client/components/Category.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import styles from './Category.css'; import { Slot } from 'plugin-api/beta/client/components'; @@ -8,7 +9,7 @@ const childFactory = child => ( ); -const ViewingOptions = ({ slot, title, data, asset, root }) => { +const Category = ({ slot, title, slotPassthrough }) => { return (
{title}
@@ -17,11 +18,16 @@ const ViewingOptions = ({ slot, title, data, asset, root }) => { childFactory={childFactory} className={styles.list} component={'ul'} - data={data} - queryData={{ asset, root }} + passthrough={slotPassthrough} />
); }; -export default ViewingOptions; +Category.propTypes = { + slot: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + slotPassthrough: PropTypes.object.isRequired, +}; + +export default Category; diff --git a/plugins/talk-plugin-viewing-options/client/components/Menu.js b/plugins/talk-plugin-viewing-options/client/components/Menu.js index 7ff5ac316..bb44ce775 100644 --- a/plugins/talk-plugin-viewing-options/client/components/Menu.js +++ b/plugins/talk-plugin-viewing-options/client/components/Menu.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import cn from 'classnames'; import styles from './Menu.css'; import { capitalize } from 'plugin-api/beta/client/utils'; @@ -13,16 +14,19 @@ class Menu extends React.Component { }; render() { + const { slotPassthrough } = this.props; return (
{Object.keys(this.categories).map(category => ( ))} @@ -31,4 +35,8 @@ class Menu extends React.Component { } } +Menu.propTypes = { + slotPassthrough: PropTypes.object.isRequired, +}; + export default Menu; diff --git a/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.js b/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.js index 8a0c09f61..40aa96d1e 100644 --- a/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.js +++ b/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import cn from 'classnames'; import styles from './ViewingOptions.css'; import { t } from 'plugin-api/beta/client/services'; @@ -24,7 +25,7 @@ class ViewingOptions extends React.Component { }; render() { - const { open, data, root, asset } = this.props; + const { open, slotPassthrough } = this.props; return (
@@ -41,11 +42,18 @@ class ViewingOptions extends React.Component { )}
- {open && } + {open && }
); } } +ViewingOptions.propTypes = { + slotPassthrough: PropTypes.object.isRequired, + open: PropTypes.bool.isRequired, + openMenu: PropTypes.func.isRequired, + closeMenu: PropTypes.func.isRequired, +}; + export default ViewingOptions; diff --git a/plugins/talk-plugin-viewing-options/client/containers/ViewingOptions.js b/plugins/talk-plugin-viewing-options/client/containers/ViewingOptions.js index b30721346..3c1d0d2af 100644 --- a/plugins/talk-plugin-viewing-options/client/containers/ViewingOptions.js +++ b/plugins/talk-plugin-viewing-options/client/containers/ViewingOptions.js @@ -4,6 +4,7 @@ import ViewingOptions from '../components/ViewingOptions'; import { openMenu, closeMenu } from '../actions'; import { compose, gql } from 'react-apollo'; import { getSlotFragmentSpreads } from 'plugin-api/beta/client/utils'; +import { mapProps } from 'recompose'; const slots = ['viewingOptionsSort', 'viewingOptionsFilter']; @@ -29,7 +30,17 @@ const withViewingOptionsFragments = withFragments({ const enhance = compose( connect(mapStateToProps, mapDispatchToProps), - withViewingOptionsFragments + withViewingOptionsFragments, + mapProps(({ root, asset, data, open, openMenu, closeMenu }) => ({ + slotPassthrough: { + data, + root, + asset, + }, + open, + openMenu, + closeMenu, + })) ); export default enhance(ViewingOptions); From 0397906907b006133e04a68855f5081dc8cf5ee8 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 9 Mar 2018 19:15:33 +0100 Subject: [PATCH 05/56] Port coral-admin to new `passthrough` prop --- .../src/components/AccountHistory.js | 2 +- .../src/components/CommentDetails.js | 23 ++++++----------- .../src/components/CommentLabels.js | 5 +++- .../coral-admin/src/components/UserDetail.js | 16 ++++++------ .../src/components/UserDetailComment.js | 11 ++++---- .../components/ModerationSettings.js | 13 +++------- .../Configure/components/StreamSettings.js | 13 +++------- .../Configure/components/TechSettings.js | 15 +++-------- .../containers/ModerationSettings.js | 16 +++++++++++- .../Configure/containers/StreamSettings.js | 16 +++++++++++- .../Configure/containers/TechSettings.js | 16 +++++++++++- .../routes/Moderation/components/Comment.js | 25 ++++++++----------- .../Moderation/components/Moderation.js | 16 ++++++------ .../routes/Moderation/containers/Indicator.js | 11 ++++---- .../client/containers/ViewingOptions.js | 6 ++--- 15 files changed, 109 insertions(+), 95 deletions(-) diff --git a/client/coral-admin/src/components/AccountHistory.js b/client/coral-admin/src/components/AccountHistory.js index a16b334ca..0d85b62af 100644 --- a/client/coral-admin/src/components/AccountHistory.js +++ b/client/coral-admin/src/components/AccountHistory.js @@ -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', diff --git a/client/coral-admin/src/components/CommentDetails.js b/client/coral-admin/src/components/CommentDetails.js index dc1be2d85..7a45219af 100644 --- a/client/coral-admin/src/components/CommentDetails.js +++ b/client/coral-admin/src/components/CommentDetails.js @@ -27,35 +27,28 @@ class CommentDetails extends Component { render() { const { data, root, comment, clearHeightCache } = this.props; const { showDetail } = this.state; - const queryData = { + + const slotPassthrough = { + data, + clearHeightCache, root, comment, + more: showDetail, }; return ( ); diff --git a/client/coral-admin/src/components/CommentLabels.js b/client/coral-admin/src/components/CommentLabels.js index 8eac152bd..1f71d9771 100644 --- a/client/coral-admin/src/components/CommentLabels.js +++ b/client/coral-admin/src/components/CommentLabels.js @@ -43,6 +43,9 @@ const CommentLabels = ({ comment, comment: { className, status, actions, hasParent }, }) => { + const slotPassthrough = { + comment, + }; return (
@@ -69,7 +72,7 @@ const CommentLabels = ({
); diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index b1f68bac2..db71b3b78 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -123,6 +123,12 @@ class UserDetail extends React.Component { const banned = isBanned(user); const suspended = isSuspended(user); + const slotPassthrough = { + data, + root, + user, + }; + return (
- +
@@ -368,9 +370,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, diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index ea898822a..a9e8c7ba9 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -36,9 +36,10 @@ class UserDetailComment extends React.Component { root: { settings }, } = this.props; - const queryData = { root, comment }; - - const formatterSettings = { + const slotPassthrough = { + data, + root, + comment, suspectWords: settings.wordlist.suspect, bannedWords: settings.wordlist.banned, body: comment.body, @@ -88,15 +89,13 @@ class UserDetailComment extends React.Component {
@@ -82,13 +82,7 @@ class ModerationSettings extends React.Component { suspectWords={settings.wordlist.suspect} onChangeWordlist={this.updateWordlist} /> - + ); } @@ -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; diff --git a/client/coral-admin/src/routes/Configure/components/StreamSettings.js b/client/coral-admin/src/routes/Configure/components/StreamSettings.js index c447b8453..e6424848f 100644 --- a/client/coral-admin/src/routes/Configure/components/StreamSettings.js +++ b/client/coral-admin/src/routes/Configure/components/StreamSettings.js @@ -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 ( @@ -220,13 +220,7 @@ class StreamSettings extends React.Component {
{/* the above card should be the last one if at all possible because of z-index issues with the selects */} - + ); } @@ -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; diff --git a/client/coral-admin/src/routes/Configure/components/TechSettings.js b/client/coral-admin/src/routes/Configure/components/TechSettings.js index 8bb10e9a3..e6d9ba8af 100644 --- a/client/coral-admin/src/routes/Configure/components/TechSettings.js +++ b/client/coral-admin/src/routes/Configure/components/TechSettings.js @@ -34,7 +34,7 @@ class TechSettings extends React.Component { }; render() { - const { settings, data, root, errors, updatePending } = this.props; + const { settings, slotPassthrough } = this.props; return ( - + ); } @@ -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; diff --git a/client/coral-admin/src/routes/Configure/containers/ModerationSettings.js b/client/coral-admin/src/routes/Configure/containers/ModerationSettings.js index c5e1e401a..1bbf0444d 100644 --- a/client/coral-admin/src/routes/Configure/containers/ModerationSettings.js +++ b/client/coral-admin/src/routes/Configure/containers/ModerationSettings.js @@ -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,18 @@ export default compose( } `, }), - connect(mapStateToProps, mapDispatchToProps) + connect(mapStateToProps, mapDispatchToProps), + mapProps(({ root, settings, data, updatePending, errors, ...rest }) => ({ + slotPassthrough: { + data, + root, + settings, + updatePending, + errors, + }, + updatePending, + settings, + errors, + ...rest, + })) )(ModerationSettings); diff --git a/client/coral-admin/src/routes/Configure/containers/StreamSettings.js b/client/coral-admin/src/routes/Configure/containers/StreamSettings.js index 24e8ad459..314dfa3ab 100644 --- a/client/coral-admin/src/routes/Configure/containers/StreamSettings.js +++ b/client/coral-admin/src/routes/Configure/containers/StreamSettings.js @@ -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,18 @@ export default compose( } `, }), - connect(mapStateToProps, mapDispatchToProps) + connect(mapStateToProps, mapDispatchToProps), + mapProps(({ root, settings, data, updatePending, errors, ...rest }) => ({ + slotPassthrough: { + data, + root, + settings, + updatePending, + errors, + }, + updatePending, + settings, + errors, + ...rest, + })) )(StreamSettings); diff --git a/client/coral-admin/src/routes/Configure/containers/TechSettings.js b/client/coral-admin/src/routes/Configure/containers/TechSettings.js index c096d1af9..d8a957cbf 100644 --- a/client/coral-admin/src/routes/Configure/containers/TechSettings.js +++ b/client/coral-admin/src/routes/Configure/containers/TechSettings.js @@ -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,18 @@ export default compose( } `, }), - connect(mapStateToProps, mapDispatchToProps) + connect(mapStateToProps, mapDispatchToProps), + mapProps(({ root, settings, data, updatePending, errors, ...rest }) => ({ + slotPassthrough: { + data, + root, + settings, + updatePending, + errors, + }, + updatePending, + settings, + errors, + ...rest, + })) )(TechSettings); diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 3fb3debf5..8a107ac24 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -62,7 +62,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 +69,14 @@ class Comment extends React.Component { body: comment.body, }; + const slotPassthrough = { + data, + clearHeightCache, + root, + comment, + asset: comment.asset, + }; + return (
  • @@ -134,13 +139,10 @@ class Comment extends React.Component {
    diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js index 2d9b793ff..615493189 100644 --- a/client/coral-admin/src/routes/Moderation/components/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js @@ -148,6 +148,14 @@ class Moderation extends Component { count: root[`${queue}Count`], })); + const slotPassthrough = { + data, + root, + asset, + activeTab, + handleCommentChange, + }; + return (
    - +
    ); } diff --git a/client/coral-admin/src/routes/Moderation/containers/Indicator.js b/client/coral-admin/src/routes/Moderation/containers/Indicator.js index 1475b6e2d..d1114ebb4 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Indicator.js +++ b/client/coral-admin/src/routes/Moderation/containers/Indicator.js @@ -111,14 +111,15 @@ class IndicatorContainer extends Component { return null; } + const slotPassthrough = { + data: this.props.data, + handleCommentChange: this.handleCommentChange, + }; + return ( - + ); } diff --git a/plugins/talk-plugin-viewing-options/client/containers/ViewingOptions.js b/plugins/talk-plugin-viewing-options/client/containers/ViewingOptions.js index 3c1d0d2af..1117ee562 100644 --- a/plugins/talk-plugin-viewing-options/client/containers/ViewingOptions.js +++ b/plugins/talk-plugin-viewing-options/client/containers/ViewingOptions.js @@ -31,15 +31,13 @@ const withViewingOptionsFragments = withFragments({ const enhance = compose( connect(mapStateToProps, mapDispatchToProps), withViewingOptionsFragments, - mapProps(({ root, asset, data, open, openMenu, closeMenu }) => ({ + mapProps(({ root, asset, data, ...rest }) => ({ slotPassthrough: { data, root, asset, }, - open, - openMenu, - closeMenu, + ...rest, })) ); From e713b39846d6cb73c97acb89e090bc5982c5324f Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 09:17:30 -0300 Subject: [PATCH 06/56] clearer api - wip debug tools --- .../coral-embed-stream/src/tabs/stream/containers/Stream.js | 2 +- client/coral-framework/services/plugins.js | 6 ++++-- views/article.ejs | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 191490914..0f516f935 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -465,7 +465,7 @@ const mapStateToProps = state => ({ activeStreamTab: state.stream.activeTab, previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, - pluginConfig: state.config.plugin_config, + pluginConfig: state.config.plugins_config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, }); diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 222cc1b42..5afcd3f78 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -84,7 +84,8 @@ class PluginsService { * query datas are only passed to the component if it is defined in `component.fragments`. */ getSlotComponentProps(component, reduxState, props, queryData) { - const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; + const pluginConfig = + get(reduxState, 'config.plugins_config') || emptyConfig; return { ...props, config: pluginConfig, @@ -98,7 +99,8 @@ class PluginsService { * Returns React Elements for given slot. */ getSlotElements(slot, reduxState, props = {}, queryData = {}, options = {}) { - const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; + const pluginConfig = + get(reduxState, 'config.plugins_config') || emptyConfig; const { slotSize = 0 } = options; const isDisabled = component => { diff --git a/views/article.ejs b/views/article.ejs index 48d361d38..9bffc6451 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -42,7 +42,7 @@ * }); * }, */ - plugin_config: { + plugins_config: { /** * You can disable rendering slot components of a plugin by doing: * @@ -51,7 +51,7 @@ * }, */ test: 'data', - debug: false + debug: true } }) "> From 6200ba39c2985c1ae3f02a94376c54b3bfaa2270 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 10:21:42 -0300 Subject: [PATCH 07/56] Adding debug tools --- .../src/tabs/stream/containers/Stream.js | 2 +- client/coral-framework/components/Slot.css | 28 ++++++++++++++++++- client/coral-framework/components/Slot.js | 15 ++++++++-- client/coral-framework/services/plugins.js | 14 +++++----- plugin-api/beta/client/selectors/index.js | 2 +- 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 0f516f935..8515a2db4 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -465,7 +465,7 @@ const mapStateToProps = state => ({ activeStreamTab: state.stream.activeTab, previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, - pluginConfig: state.config.plugins_config, + pluginsConfig: state.config.plugins_config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, }); diff --git a/client/coral-framework/components/Slot.css b/client/coral-framework/components/Slot.css index 6a95d79ad..ea3a88fea 100644 --- a/client/coral-framework/components/Slot.css +++ b/client/coral-framework/components/Slot.css @@ -3,5 +3,31 @@ } .debug { - background-color: coral; + background-color: #e2e2e2; + border-style: dotted solid; + border-width: 2px; + border: dotted 2px coral; + padding: 2px; + margin: 1px; + position: relative; +} + +.debug::before { + content: "•slot: " attr(data-slot-name) "\A" "•classname: " attr(data-slot-classname); + display: inline-block; + position: absolute; + bottom: 50%; + background: #000; + color: #FFF; + padding: 5px; + border-radius: 5px; + opacity:0; transition:0.3s; overflow:hidden; + pointer-events: none; + z-index: 999!important; + white-space: pre-wrap; +} + +.debug:hover::before { + opacity:1; + bottom: 100%; } \ No newline at end of file diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 952844ed0..ca10d99c0 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -72,7 +72,7 @@ class Slot extends React.Component { } = this.props; const { plugins } = this.context; let children = this.getChildren(); - const pluginConfig = + const pluginsConfig = get(reduxState, 'config.plugins_config') || emptyConfig; if (children.length === 0 && DefaultComponent) { const props = plugins.getSlotComponentProps( @@ -88,13 +88,24 @@ class Slot extends React.Component { children = children.map(childFactory); } + const debugProps = pluginsConfig.debug + ? { + 'data-slot-name': fill, + 'data-slot-classname': `talk-slot-${kebabCase(fill)}`, + } + : {}; + return ( {children} diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 5afcd3f78..f0e6ff304 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -10,7 +10,7 @@ import get from 'lodash/get'; import { getDisplayName } from 'coral-framework/helpers/hoc'; import camelize from '../helpers/camelize'; -// This is returned for pluginConfig when it is empty. +// This is returned for pluginsConfig when it is empty. const emptyConfig = {}; // Memoize the warnings so we only show them once. @@ -84,11 +84,11 @@ class PluginsService { * query datas are only passed to the component if it is defined in `component.fragments`. */ getSlotComponentProps(component, reduxState, props, queryData) { - const pluginConfig = + const pluginsConfig = get(reduxState, 'config.plugins_config') || emptyConfig; return { ...props, - config: pluginConfig, + config: pluginsConfig, ...(component.fragments ? pick(queryData, Object.keys(component.fragments)) : withWarnings(component, queryData)), @@ -99,15 +99,15 @@ class PluginsService { * Returns React Elements for given slot. */ getSlotElements(slot, reduxState, props = {}, queryData = {}, options = {}) { - const pluginConfig = + const pluginsConfig = get(reduxState, 'config.plugins_config') || emptyConfig; const { slotSize = 0 } = options; const isDisabled = component => { if ( - pluginConfig && - pluginConfig[component.talkPluginName] && - pluginConfig[component.talkPluginName].disable_components + pluginsConfig && + pluginsConfig[component.talkPluginName] && + pluginsConfig[component.talkPluginName].disable_components ) { return true; } diff --git a/plugin-api/beta/client/selectors/index.js b/plugin-api/beta/client/selectors/index.js index 97e4623e4..c7951ca56 100644 --- a/plugin-api/beta/client/selectors/index.js +++ b/plugin-api/beta/client/selectors/index.js @@ -1 +1 @@ -export const pluginConfigSelector = state => state.config.pluginConfig; +export const pluginsConfigSelector = state => state.config.pluginsConfig; From 68fb51122350ec88fc312d66a38373449373997b Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 13:41:00 -0300 Subject: [PATCH 08/56] desc styling --- client/coral-framework/components/Slot.css | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/client/coral-framework/components/Slot.css b/client/coral-framework/components/Slot.css index ea3a88fea..69bfe937e 100644 --- a/client/coral-framework/components/Slot.css +++ b/client/coral-framework/components/Slot.css @@ -13,21 +13,26 @@ } .debug::before { - content: "•slot: " attr(data-slot-name) "\A" "•classname: " attr(data-slot-classname); + content: attr(data-slot-name); display: inline-block; position: absolute; - bottom: 50%; + background: #000; color: #FFF; padding: 5px; border-radius: 5px; - opacity:0; transition:0.3s; overflow:hidden; + opacity: 0; + transition: 0.3s; + overflow: hidden; pointer-events: none; z-index: 999!important; white-space: pre-wrap; + min-height: 16px; + top: 50%; + left: 0; } .debug:hover::before { - opacity:1; - bottom: 100%; + opacity: 1; + top: 100%; } \ No newline at end of file From 77be539710ebc71b60ee2dabd255f314eb9a4f20 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 13:49:35 -0300 Subject: [PATCH 09/56] Adding it to the docs, not sure if it should live there. --- client/coral-framework/components/Slot.js | 1 - docs/_docs/04-05-talk-slots-and-plugins-ES.md | 0 2 files changed, 1 deletion(-) create mode 100644 docs/_docs/04-05-talk-slots-and-plugins-ES.md diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index ca10d99c0..18d3163e0 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -91,7 +91,6 @@ class Slot extends React.Component { const debugProps = pluginsConfig.debug ? { 'data-slot-name': fill, - 'data-slot-classname': `talk-slot-${kebabCase(fill)}`, } : {}; diff --git a/docs/_docs/04-05-talk-slots-and-plugins-ES.md b/docs/_docs/04-05-talk-slots-and-plugins-ES.md new file mode 100644 index 000000000..e69de29bb From 5dbbf80068a6d9da3a60222c1b03f822beb3f601 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 14:27:41 -0300 Subject: [PATCH 10/56] Adding it to the docs, not sure if it should live there. --- docs/_docs/04-05-talk-slots-and-plugins-ES.md | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/docs/_docs/04-05-talk-slots-and-plugins-ES.md b/docs/_docs/04-05-talk-slots-and-plugins-ES.md index e69de29bb..846ccd033 100644 --- a/docs/_docs/04-05-talk-slots-and-plugins-ES.md +++ b/docs/_docs/04-05-talk-slots-and-plugins-ES.md @@ -0,0 +1,157 @@ +# Plugins y Slots +Los *Slots* son una parte muy importante para crear plugins. Estos nos permiten inyectar nuestros plugins en la interfaz visual de Talk. + +Talk por defecto muestra varios plugins - Observemos el archivo `plugins.default.json` : + +```json +{ + "server": [ + "talk-plugin-auth", + "talk-plugin-featured-comments", + "talk-plugin-offtopic", + "talk-plugin-respect" + ], + "client": [ + "talk-plugin-auth", + "talk-plugin-author-menu", + "talk-plugin-comment-content", + "talk-plugin-featured-comments", + "talk-plugin-flag-details", + "talk-plugin-ignore-user", + "talk-plugin-member-since", + "talk-plugin-moderation-actions", + "talk-plugin-offtopic", + "talk-plugin-permalink", + "talk-plugin-respect", + "talk-plugin-sort-most-replied", + "talk-plugin-sort-most-respected", + "talk-plugin-sort-newest", + "talk-plugin-sort-oldest", + "talk-plugin-viewing-options", + "talk-plugin-profile-settings" + ] +} +``` + +La parte que nos interesa es `client`, ya que estos son los plugins que van a utilizar los *slots* para meterse en distintas partes de Talk. + +Por ejemplo: Si observamos el plugin `talk-plugin-respect`, que está dentro de la carpeta `plugins` vamos a notar que el `client/index.js` luce asi: + +```js +import RespectButton from './RespectButton'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + commentReactions: [RespectButton], + }, +}; + +``` + +Dentro de la propiedad `slots` especificamos qué *slots* utilizará el plugin. Ahí estamos diciendo que el componente `RespectButton` se incrustará en el slot `commentReactions`. + +Los slots reciben un /Array/ de componentes. Es decir, puede ser uno o varios. + +### Anatomía del component Slot +En el core de Talk tenemos 32 slots declarados. El componente Slot tiene una propiedad `fill` donde establecemos el nombre del slot. Una declaración de un `` luce así: + +```js + +``` + +Esto probablemente no lo utilices para desarrollar plugins, pero sí para buscar donde se va a embeber tu plugin. + +### Lista de Slots + +* `adminCommentDetailArea` +* `adminCommentMoreDetails` +* `adminCommentLabels` +* `adminModerationSettings` +* `adminStreamSettings` +* `adminTechSettings` +* `adminCommentInfoBar` +* `adminCommentContent` +* `adminSideActions` +* `adminModeration` +* `adminModerationIndicator` + +* `commentInputDetailArea` +* `commentAvatar` +* `commentAuthorName` +* `commentAuthorTags` +* `commentTimestamp` +* `commentInfoBar` +* `commentContent` +* `commentReactions` +* `commentActions` +* `commentInputArea` + +* `draftArea` +* `streamSettings` +* `historyCommentTimestamp` +* `profileSections` +* `embed` +* `stream` +* `streamFilter` +* `streamQuestionArea` +* `login` +* `userProfile` +* `userDetailCommentContent` + +### Cómo sé donde colocar mi plugin? + +Lo primero que debemos pensar es a qué componente afectará la funcionalidad del plugin. Por ejemplo, si queremos agregar funcionalidad a todos los comentarios que se renderizan en la lista total de comentarios, esto tendrá que ver con el componente `Comment`. + +Los slots habilitados para agregar funcionalidad a los comentarios tienen un prefix `comment` como el `commentContent` o el `commentReactions`, que vimos previamente. + +### Deshabilitando plugins por configuración + +Los plugins se eliminan borrando su entrada en el `plugins.json`. Pero si por alguna razón necesitamos hacerlo por configuración, podemos hacerlo. + +Dentro de `views/article.ejs` embebemos Talk y notarás que podemos envialarle un objeto de configuración. Para deshabilitar visualmente a los plugins podemos utilizar la propiedad `disable_components` en `true`. y se utiliza de la siguiente forma: + +```js +plugins_config: { + 'talk-plugin-love': { + disable_components: true, + }, +} +``` + +### Enviando información a los slots / plugins + +Dentro de `plugins_config` podemos pasar todas las propiedades que queramos y los plugins las recibirán. + +Por ejemplo: Si enviamos esto +```js +plugins_config: { + test: 'data' +} +``` + +El plugin recibirá por props un objeto config con las propiedades que le pasamos. Haciendo un console.log de `this.props` verás: + +```js +config: {test: 'data'} +``` + +### Debug Slots + +Podés debuggear Slots / Plugins sólo con pasar la propiedad `debug` con valor `true`. + +```js +plugins_config: { + debug: true +} +``` + +Esto mostrará todos los slots disponibles en la UI y su nombre si pasas el mouse sobre ellos. + +### Slot ClassNames +Los slots autogeneran sus clases con el prefijo `talk-slot-` seguido del nombre del slot en kebab case. +Por ejemplo, la clase autogenerada del slot `commentContent` es `talk-slot-comment-content`. \ No newline at end of file From b80184d9ee05aa7ffa0f684f312c3f02b145cfb7 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 14:29:18 -0300 Subject: [PATCH 11/56] set debug to false --- views/article.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/article.ejs b/views/article.ejs index 9bffc6451..896be9730 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -51,7 +51,7 @@ * }, */ test: 'data', - debug: true + debug: false } }) "> From 0425c39bfe2be7ee73101305403bbb08c71d5831 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 12 Mar 2018 23:44:02 +0100 Subject: [PATCH 12/56] Remove requirement for data.variables in withFragment --- client/coral-framework/hocs/withFragments.js | 14 ++++---------- yarn.lock | 4 ++-- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/client/coral-framework/hocs/withFragments.js b/client/coral-framework/hocs/withFragments.js index f98411de1..98ab76922 100644 --- a/client/coral-framework/hocs/withFragments.js +++ b/client/coral-framework/hocs/withFragments.js @@ -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; } diff --git a/yarn.lock b/yarn.lock index 251ec050d..13f8f704f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -81,8 +81,8 @@ prettier "^1.10.2" "@coralproject/graphql-anywhere-optimized@^0.1.0": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@coralproject/graphql-anywhere-optimized/-/graphql-anywhere-optimized-0.1.5.tgz#67c862bf908ea717d9521ea76266b5bc9f109c65" + version "0.1.6" + resolved "https://registry.yarnpkg.com/@coralproject/graphql-anywhere-optimized/-/graphql-anywhere-optimized-0.1.6.tgz#073b33764c04788b0290788da9ebf0ed21af6437" dependencies: graphql-ast-tools "^0.2.2" From 5bb5f98b1795911bb7b0d579d02bcec694eb57a4 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Mar 2018 16:05:54 +0100 Subject: [PATCH 13/56] Refactor Slot Feature --- .../src/containers/ExtendableTabPanel.js | 93 ++++------ .../components/IfSlotIsEmpty.js | 104 ++--------- .../components/IfSlotIsNotEmpty.js | 104 ++--------- client/coral-framework/components/Slot.js | 150 +++------------ client/coral-framework/hocs/index.js | 2 + .../hocs/withCombatPassthrough.js | 53 ++++++ .../coral-framework/hocs/withSlotElements.js | 174 ++++++++++++++++++ 7 files changed, 316 insertions(+), 364 deletions(-) create mode 100644 client/coral-framework/hocs/withCombatPassthrough.js create mode 100644 client/coral-framework/hocs/withSlotElements.js diff --git a/client/coral-embed-stream/src/containers/ExtendableTabPanel.js b/client/coral-embed-stream/src/containers/ExtendableTabPanel.js index 66659d657..678981a8e 100644 --- a/client/coral-embed-stream/src/containers/ExtendableTabPanel.js +++ b/client/coral-embed-stream/src/containers/ExtendableTabPanel.js @@ -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,37 +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.slotPassthrough - ); - } - 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 ( - - {React.cloneElement(el, { - active: this.props.activeTab === el.type.talkPluginName, - })} - - ); - }); - } + createPluginTabFactory = (props = this.props) => el => { + return ( + + {React.cloneElement(el, { + active: props.activeTab === el.type.talkPluginName, + })} + + ); + }; getTabElements(props = this.props) { const elements = [...this.getPluginTabElementsPrepend(props)]; @@ -91,14 +57,16 @@ class ExtendableTabPanelContainer extends React.Component { return elements; } + createPluginTabPane(el) { + return ( + + {el} + + ); + } + getPluginTabPaneElements(props = this.props) { - return this.getSlotElements(props.tabPaneSlot).map(el => { - return ( - - {el} - - ); - }); + return props.slotElements[2].map(this.createPluginTabPane); } render() { @@ -137,8 +105,9 @@ ExtendableTabPanelContainer.propTypes = { 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); diff --git a/client/coral-framework/components/IfSlotIsEmpty.js b/client/coral-framework/components/IfSlotIsEmpty.js index fa8f2524d..599b21401 100644 --- a/client/coral-framework/components/IfSlotIsEmpty.js +++ b/client/coral-framework/components/IfSlotIsEmpty.js @@ -1,93 +1,14 @@ import React, { Children } from 'react'; -import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { getShallowChanges } from 'coral-framework/utils'; -import omit from 'lodash/omit'; +import { withSlotElements, withCombatPassthrough } from '../hocs'; +import { compose } from 'recompose'; class IfSlotIsEmpty extends React.Component { - static contextTypes = { - plugins: PropTypes.object, - }; - - shouldComponentUpdate(next) { - const changes = getShallowChanges(this.props, next); - - // Handle special `passthrough` props. - const passthroughIndex = changes.indexOf('passthrough'); - if (passthroughIndex !== -1) { - if (!this.props.passthrough || next.passthrough) { - return true; - } - if ( - getShallowChanges(this.props.passthrough, next.passthrough).lenght === 0 - ) { - changes.splice(passthroughIndex, 1); - } - } - - // Prevent Slot from rerendering when only reduxState has changed and - // it does not result in a change. - 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; - } - - getPassthrough(props = this.props) { - const slotProps = omit(props, [ - 'slot', - 'children', - 'reduxState', - 'passthrough', - 'dispatch', - ]); - - // @Deprecated - if (process.env.NODE_ENV !== 'production') { - if (Object.keys(slotProps).length) { - /* eslint-disable no-console */ - console.warn( - `IfSlotIsEmpty '${ - 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; - } - isSlotEmpty(props = this.props) { - const { slot, reduxState } = props; - const passthrough = this.getPassthrough(props); - const slots = Array.isArray(slot) ? slot : [slot]; - return slots.every(slot => - this.context.plugins.isSlotEmpty(slot, reduxState, passthrough) - ); + const { slotElements } = props; + return slotElements.length === 0 + ? false + : slotElements.every(elements => elements.length === 0); } render() { @@ -99,11 +20,14 @@ class IfSlotIsEmpty extends React.Component { IfSlotIsEmpty.propTypes = { slot: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), children: PropTypes.node.isRequired, - passthrough: PropTypes.object, + passthrough: PropTypes.object.isRequired, }; -const mapStateToProps = state => ({ - reduxState: state, -}); +const omitProps = ['slot', 'children']; -export default connect(mapStateToProps, null)(IfSlotIsEmpty); +export default compose( + withCombatPassthrough(omitProps), + withSlotElements({ + slot: props => props.slot, + }) +)(IfSlotIsEmpty); diff --git a/client/coral-framework/components/IfSlotIsNotEmpty.js b/client/coral-framework/components/IfSlotIsNotEmpty.js index 024877fbc..be67d4e4f 100644 --- a/client/coral-framework/components/IfSlotIsNotEmpty.js +++ b/client/coral-framework/components/IfSlotIsNotEmpty.js @@ -1,93 +1,14 @@ import React, { Children } from 'react'; -import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { getShallowChanges } from 'coral-framework/utils'; -import omit from 'lodash/omit'; +import { withSlotElements, withCombatPassthrough } from '../hocs'; +import { compose } from 'recompose'; class IfSlotIsNotEmpty extends React.Component { - static contextTypes = { - plugins: PropTypes.object, - }; - - shouldComponentUpdate(next) { - const changes = getShallowChanges(this.props, next); - - // Handle special `passthrough` props. - const passthroughIndex = changes.indexOf('passthrough'); - if (passthroughIndex !== -1) { - if (!this.props.passthrough || next.passthrough) { - return true; - } - if ( - getShallowChanges(this.props.passthrough, next.passthrough).lenght === 0 - ) { - changes.splice(passthroughIndex, 1); - } - } - - // Prevent Slot from rerendering when only reduxState has changed and - // it does not result in a change. - 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; - } - - getPassthrough(props = this.props) { - const slotProps = omit(props, [ - 'slot', - 'children', - 'reduxState', - 'passthrough', - 'dispatch', - ]); - - // @Deprecated - if (process.env.NODE_ENV !== 'production') { - if (Object.keys(slotProps).length) { - /* eslint-disable no-console */ - console.warn( - `IfSlotIsEmpty '${ - 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; - } - isSlotEmpty(props = this.props) { - const { slot, reduxState } = props; - const passthrough = this.getPassthrough(props); - const slots = Array.isArray(slot) ? slot : [slot]; - return slots.every(slot => - this.context.plugins.isSlotEmpty(slot, reduxState, passthrough) - ); + const { slotElements } = props; + return slotElements.length === 0 + ? false + : slotElements.every(elements => elements.length === 0); } render() { @@ -99,11 +20,14 @@ class IfSlotIsNotEmpty extends React.Component { IfSlotIsNotEmpty.propTypes = { slot: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), children: PropTypes.node.isRequired, - passthrough: PropTypes.object, + passthrough: PropTypes.object.isRequired, }; -const mapStateToProps = state => ({ - reduxState: state, -}); +const omitProps = ['slot', 'children']; -export default connect(mapStateToProps, null)(IfSlotIsNotEmpty); +export default compose( + withCombatPassthrough(omitProps), + withSlotElements({ + slot: props => props.slot, + }) +)(IfSlotIsNotEmpty); diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 02f182dc3..a70b9ccf9 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -4,135 +4,21 @@ 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'; - -const emptyConfig = {}; +import { withSlotElements, withCombatPassthrough } 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); - - // Handle special `passthrough` props. - const passthroughIndex = changes.indexOf('passthrough'); - if (passthroughIndex !== -1) { - if (!this.props.passthrough || next.passthrough) { - return true; - } - if ( - getShallowChanges(this.props.passthrough, next.passthrough).lenght === 0 - ) { - changes.splice(passthroughIndex, 1); - } - } - - 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; - } - - getPassthrough(props = this.props) { - const slotProps = omit(props, [ - 'fill', - 'inline', - 'className', - 'reduxState', - 'size', - 'defaultComponent', - 'queryData', - 'childFactory', - 'component', - 'passthrough', - 'dispatch', - ]); - - // @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; - } - - getChildren(props = this.props) { - const { size = 0 } = props; - const { plugins } = this.context; - - return plugins.getSlotElements( - props.fill, - props.reduxState, - this.getPassthrough(props), - { size } - ); - } - render() { const { inline = false, className, - reduxState, + debug, component: Component, childFactory, - defaultComponent: DefaultComponent, fill, } = this.props; - const { plugins } = this.context; - let children = this.getChildren(); - const pluginConfig = - get(reduxState, 'config.plugins_config') || emptyConfig; - if (children.length === 0 && DefaultComponent) { - const props = plugins.getSlotComponentProps( - DefaultComponent, - reduxState, - this.getPassthrough() - ); - children = ; - } - + let children = this.props.slotElements; if (childFactory) { children = children.map(childFactory); } @@ -140,7 +26,7 @@ class Slot extends React.Component { return ( ({ - reduxState: state, + debug: get(state, 'config.plugins_config.debug'), }); -export default connect(mapStateToProps, null)(Slot); +export default compose( + withCombatPassthrough(omitProps), + withSlotElements({ + slot: props => props.fill, + size: props => props.size, + defaultComponent: props => props.defaultComponent, + }), + connect(mapStateToProps, null) +)(Slot); diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js index 7ed114b73..a0f0a3e9c 100644 --- a/client/coral-framework/hocs/index.js +++ b/client/coral-framework/hocs/index.js @@ -12,6 +12,8 @@ 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 withCombatPassthrough } from './withCombatPassthrough'; +export { default as withSlotElements } from './withSlotElements'; export { default as withResendEmailConfirmation, } from './withResendEmailConfirmation'; diff --git a/client/coral-framework/hocs/withCombatPassthrough.js b/client/coral-framework/hocs/withCombatPassthrough.js new file mode 100644 index 000000000..84dbdb492 --- /dev/null +++ b/client/coral-framework/hocs/withCombatPassthrough.js @@ -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 + * withCombatPassthrough 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), + })); diff --git a/client/coral-framework/hocs/withSlotElements.js b/client/coral-framework/hocs/withSlotElements.js new file mode 100644 index 000000000..c7aa9fe29 --- /dev/null +++ b/client/coral-framework/hocs/withSlotElements.js @@ -0,0 +1,174 @@ +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, +}) => + 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.getSizes(props, slots.length); + + const elements = []; + slots.forEach((s, i) => { + const DefaultComponent = defaultComponents[i]; + const size = sizes[i]; + const slotElements = plugins.getSlotElements( + s, + props.reduxState, + this.getPassthrough(props), + { size } + ); + + if (slotElements.length === 0 && DefaultComponent) { + const p = plugins.getSlotComponentProps( + DefaultComponent, + props.reduxState, + this.getPassthrough(props) + ); + slotElements.push(); + } + + 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 = { + slotElements, + ...rest, + }; + return ; + } + }; + }); + +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 + * + * @return {func} Returns a HOC that 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)); +}; From 9ad92e38c041c9524a0939b3a3df78d7cb100680 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Mar 2018 17:48:54 +0100 Subject: [PATCH 14/56] Move profile settings to core --- .../src/tabs/profile/components/Profile.js | 42 ++---------- .../src/tabs/profile/components/Settings.js | 8 +-- .../src/tabs/profile/components/TabPanel.js | 63 ++++++++++++++++++ .../src/tabs/profile/containers/Profile.js | 23 ++----- .../src/tabs/profile/containers/Settings.js | 26 ++++++++ .../src/tabs/profile/containers/TabPanel.js | 66 +++++++++++++++++++ .../coral-framework/hocs/withSlotElements.js | 9 ++- locales/da.yml | 6 +- locales/de.yml | 4 +- locales/en.yml | 2 +- locales/es.yml | 2 +- locales/fr.yml | 2 +- locales/nl_NL.yml | 4 +- locales/pt_BR.yml | 2 +- locales/zh_CN.yml | 2 +- locales/zh_TW.yml | 2 +- plugins.default.json | 3 +- .../client/.eslintrc.json | 3 - .../client/components/Tab.js | 8 --- .../client/containers/TabPane.js | 26 -------- .../client/index.js | 11 ---- .../client/translations.yml | 27 -------- plugins/talk-plugin-profile-settings/index.js | 1 - 23 files changed, 190 insertions(+), 152 deletions(-) rename plugins/talk-plugin-profile-settings/client/components/TabPane.js => client/coral-embed-stream/src/tabs/profile/components/Settings.js (70%) create mode 100644 client/coral-embed-stream/src/tabs/profile/components/TabPanel.js create mode 100644 client/coral-embed-stream/src/tabs/profile/containers/Settings.js create mode 100644 client/coral-embed-stream/src/tabs/profile/containers/TabPanel.js delete mode 100644 plugins/talk-plugin-profile-settings/client/.eslintrc.json delete mode 100644 plugins/talk-plugin-profile-settings/client/components/Tab.js delete mode 100644 plugins/talk-plugin-profile-settings/client/containers/TabPane.js delete mode 100644 plugins/talk-plugin-profile-settings/client/index.js delete mode 100644 plugins/talk-plugin-profile-settings/client/translations.yml delete mode 100644 plugins/talk-plugin-profile-settings/index.js diff --git a/client/coral-embed-stream/src/tabs/profile/components/Profile.js b/client/coral-embed-stream/src/tabs/profile/components/Profile.js index bde624063..55c428c00 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Profile.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Profile.js @@ -1,24 +1,10 @@ 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, -}) => { - const slotPassthrough = { - data, - root, - }; +const Profile = ({ username, emailAddress, data, root, slotPassthrough }) => { return (
    @@ -26,26 +12,7 @@ const Profile = ({ {emailAddress ?

    {emailAddress}

    : null}
    - - {t('framework.my_comments')} - , - ]} - tabPanes={[ - - - , - ]} - sub - /> +
    ); }; @@ -55,8 +22,7 @@ Profile.propTypes = { emailAddress: PropTypes.string, data: PropTypes.object, root: PropTypes.object, - activeTab: PropTypes.string.isRequired, - setActiveTab: PropTypes.func.isRequired, + slotPassthrough: PropTypes.object, }; export default Profile; diff --git a/plugins/talk-plugin-profile-settings/client/components/TabPane.js b/client/coral-embed-stream/src/tabs/profile/components/Settings.js similarity index 70% rename from plugins/talk-plugin-profile-settings/client/components/TabPane.js rename to client/coral-embed-stream/src/tabs/profile/components/Settings.js index 83ca614f4..1d5a7cc51 100644 --- a/plugins/talk-plugin-profile-settings/client/components/TabPane.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Settings.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Slot } from 'plugin-api/beta/client/components'; +import { Slot } from 'coral-framework/components'; -class TabPane extends React.Component { +class Settings extends React.Component { render() { const { data, root } = this.props; const slotPassthrough = { data, root }; @@ -14,9 +14,9 @@ class TabPane extends React.Component { } } -TabPane.propTypes = { +Settings.propTypes = { data: PropTypes.object, root: PropTypes.object, }; -export default TabPane; +export default Settings; diff --git a/client/coral-embed-stream/src/tabs/profile/components/TabPanel.js b/client/coral-embed-stream/src/tabs/profile/components/TabPanel.js new file mode 100644 index 000000000..fd236dd3a --- /dev/null +++ b/client/coral-embed-stream/src/tabs/profile/components/TabPanel.js @@ -0,0 +1,63 @@ +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 = ({ + data, + root, + activeTab, + setActiveTab, + showSettingsTab, + slotPassthrough, +}) => { + const tabs = [ + + {t('framework.my_comments')} + , + ]; + + if (showSettingsTab) { + tabs.push( + + {t('profile_settings')} + + ); + } + + return ( + + + , + + + , + ]} + sub + /> + ); +}; + +TabPanel.propTypes = { + data: PropTypes.object, + root: PropTypes.object, + slotPassthrough: PropTypes.object, + activeTab: PropTypes.string.isRequired, + setActiveTab: PropTypes.func.isRequired, + showSettingsTab: PropTypes.bool.isRequired, +}; + +export default TabPanel; diff --git a/client/coral-embed-stream/src/tabs/profile/containers/Profile.js b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js index 82a0a49eb..11d514540 100644 --- a/client/coral-embed-stream/src/tabs/profile/containers/Profile.js +++ b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js @@ -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 { @@ -41,6 +40,7 @@ class ProfileContainer extends Component { const localProfile = currentUser.profiles.find(p => p.provider === 'local'); const emailAddress = localProfile && localProfile.id; + const slotPassthrough = { data, root }; return ( ); } @@ -60,16 +59,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 +70,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 +84,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), diff --git a/client/coral-embed-stream/src/tabs/profile/containers/Settings.js b/client/coral-embed-stream/src/tabs/profile/containers/Settings.js new file mode 100644 index 000000000..795a3c10a --- /dev/null +++ b/client/coral-embed-stream/src/tabs/profile/containers/Settings.js @@ -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 ; + } +} + +const enhance = compose( + withFragments({ + root: gql` + fragment TalkEmbedStream_ProfileSettings_root on RootQuery { + __typename + ${getSlotFragmentSpreads(slots, 'root')} + } + `, + }) +); + +export default enhance(SettingsContainer); diff --git a/client/coral-embed-stream/src/tabs/profile/containers/TabPanel.js b/client/coral-embed-stream/src/tabs/profile/containers/TabPanel.js new file mode 100644 index 000000000..0679ad31f --- /dev/null +++ b/client/coral-embed-stream/src/tabs/profile/containers/TabPanel.js @@ -0,0 +1,66 @@ +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 ( + 0} + /> + ); + } +} + +TabPanelContainer.propTypes = { + data: PropTypes.object, + 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); diff --git a/client/coral-framework/hocs/withSlotElements.js b/client/coral-framework/hocs/withSlotElements.js index c7aa9fe29..4580e7ce9 100644 --- a/client/coral-framework/hocs/withSlotElements.js +++ b/client/coral-framework/hocs/withSlotElements.js @@ -19,6 +19,7 @@ const createHOC = ({ defaultComponent = null, passthroughPropName = 'passthrough', size = null, + propName = 'slotElements', }) => hoistStatics(WrappedComponent => { return class withSlotElements extends React.Component { @@ -140,7 +141,7 @@ const createHOC = ({ const { reduxState: _a, ...rest } = this.props; const slotElements = this.getSlotElements(); const props = { - slotElements, + [propName]: slotElements, ...rest, }; return ; @@ -159,9 +160,11 @@ const mapStateToProps = state => ({ * @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 provides the property `slotElements` with an Array of - * Slot Elements or in case of multiple slots, an Array of Slot Element Arrays. + * @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({ diff --git a/locales/da.yml b/locales/da.yml index c68185406..8dccf755f 100644 --- a/locales/da.yml +++ b/locales/da.yml @@ -16,7 +16,7 @@ da: send: "Send" notify_ban_headline: "Notify the user of ban" notify_ban_description: "This will notify the user by email that they have been banned from the community" - email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team." + email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team." bio_offensive: "Denne biografi er stødende" cancel: "Afbryd" confirm_email: @@ -256,7 +256,7 @@ da: label: "Nyt brugernavn" msg: "Din konto er midlertidigt suspenderet, fordi dit brugernavn er blevet anset for upassende. For at gendanne din konto skal du indtaste et nyt brugernavn. Kontakt os venligst, hvis du har spørgsmål." changed_name: - msg: "Your username change is under review by our moderation team." + msg: "Your username change is under review by our moderation team." my_comments: "Mine kommentarer" my_profile: "Min profil" new_count: "Se {0} mere {1}" @@ -350,7 +350,7 @@ da: personal_info: "Denne kommentar afslører personligt identificerbare oplysninger" post: "Post" profile: "Profil" - profile_settings: "Profilindstillinger" + profile_settings: "Indstillinger" reply: "Svar" report: "Rapporter" report_notif: "Tak fordi du rapporterede denne kommentar. Vores modereringsteam har fået besked, og vil gennemgå det inden for kort tid." diff --git a/locales/de.yml b/locales/de.yml index b5ced1096..972c2735b 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -349,7 +349,7 @@ de: personal_info: "Dieser Kommentar enthält zu viel personenbezogene Daten" post: Post # Kontext? profile: Profil - profile_settings: "Profil-Einstellungen" + profile_settings: "Einstellungen" reply: Antworten report: Melden report_notif: "Vielen Dank für Ihre Meldung. Unsere Moderatoren wurden informiert und werden sich in Kürze darum kümmern." @@ -397,7 +397,7 @@ de: suspend_user: "Nutzer vorübergehend sperren" email_message_suspend: "Sehr geehrte/r {0}, entsprechend der Community-Richtlinien von {1} wurde Ihr Konto vorübergehend gesperrt. Während der Sperrung können Sie weder kommentieren noch andere Aktionen ausführen. Nehmen Sie {2} wieder an der Diskussion teil." title_notify: "Den Nutzer über die vorübergehende Kontosperrung informieren" - notify_suspend_until: "Nutzer {0} wurde vorübergehend gesperrt. Diese Sperrung endet automatisch {1}." + notify_suspend_until: "Nutzer {0} wurde vorübergehend gesperrt. Diese Sperrung endet automatisch {1}." description_notify: "Während der Sperrung hat der Nutzer keinen Zugriff auf das Konto." write_message: "Nachricht verfassen" send: Senden diff --git a/locales/en.yml b/locales/en.yml index d540e8c1e..59689d1b1 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -352,7 +352,7 @@ en: personal_info: "This comment reveals personally identifiable information" post: Post profile: Profile - profile_settings: "Profile Settings" + profile_settings: "Settings" reply: Reply report: Report report_notif: "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly." diff --git a/locales/es.yml b/locales/es.yml index 8edd1f8fc..ba6cb8ad3 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -351,7 +351,7 @@ es: personal_info: "Este comentario muestra información personal" post: Publicar profile: Perfil - profile_settings: "Configuración del Perfil" + profile_settings: "Configuración" reply: Responder report: Reportar report_notif: "Gracias por reportar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar." diff --git a/locales/fr.yml b/locales/fr.yml index fb2b3be99..dd14bea81 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -351,7 +351,7 @@ fr: personal_info: "Ce commentaire révèle des informations personnelles identifiables" post: Publier profile: Profil - profile_settings: "Paramètres du profil" + profile_settings: "Paramètres" reply: Répondre report: Signaler report_notif: "Merci de signaler ce commentaire. Notre équipe de modération a é té informée." diff --git a/locales/nl_NL.yml b/locales/nl_NL.yml index a60268e42..aa80f58be 100644 --- a/locales/nl_NL.yml +++ b/locales/nl_NL.yml @@ -65,7 +65,7 @@ nl_NL: notify_approved: '{0} heeft gebruikersnaam {1} goedgekeurd' notify_rejected: '{0} heeft gebruikersnaam {1} afgekeurd' notify_flagged: '{0} heeft gebruikersnaam {1} gerapporteerd' - notify_changed: 'gebruiker {0} heeft zijn/haar gebruikersnaam veranderd naar {1}' + notify_changed: 'gebruiker {0} heeft zijn/haar gebruikersnaam veranderd naar {1}' community: account_creation_date: "Aanmaakdatum account" active: Actief @@ -350,7 +350,7 @@ nl_NL: personal_info: "Deze reactie onthult persoonlijk identificeerbare gegevens." post: Plaats profile: Profiel - profile_settings: "Profiel instellingen" + profile_settings: "Instellingen" reply: Beantwoord report: Rapporteer report_notif: "Dank voor het rapporteren van deze reactie. Deze wordt zo snel mogelijk gemodereerd." diff --git a/locales/pt_BR.yml b/locales/pt_BR.yml index b19a3789b..86267d51d 100644 --- a/locales/pt_BR.yml +++ b/locales/pt_BR.yml @@ -349,7 +349,7 @@ pt_BR: personal_info: "Este comentário revela informações de identificação pessoal" post: Publicar profile: Perfil - profile_settings: "Configurações de perfil" + profile_settings: "Configurações" reply: Responder report: Denunciar report_notif: "Obrigado por denunciar este comentário. Nossa equipe de moderação foi notificada e irá revisá-la em breve." diff --git a/locales/zh_CN.yml b/locales/zh_CN.yml index 31d6623a3..59e028eb7 100644 --- a/locales/zh_CN.yml +++ b/locales/zh_CN.yml @@ -351,7 +351,7 @@ zh_CN: personal_info: "这个评论透露了可确定个人身份的信息" post: "发表" profile: "资料" - profile_settings: "资料设定" + profile_settings: "设置" reply: "回复" report: "举报" report_notif: "感谢您举报此评论。我们的审核小组已经收到通知,并会很快进行审查。" diff --git a/locales/zh_TW.yml b/locales/zh_TW.yml index 8f2f54dfd..1b131f636 100644 --- a/locales/zh_TW.yml +++ b/locales/zh_TW.yml @@ -351,7 +351,7 @@ zh_TW: personal_info: "該評論洩露了個人身份資訊" post: 帖子 profile: 概況 - profile_settings: "概況設置" + profile_settings: "設置" reply: 回覆 report: 舉報 report_notif: "感謝您舉報此評論。我們的審核小組已經收到通知,並會很快進行審查。" diff --git a/plugins.default.json b/plugins.default.json index b54c6a54c..066c94478 100644 --- a/plugins.default.json +++ b/plugins.default.json @@ -21,7 +21,6 @@ "talk-plugin-sort-most-respected", "talk-plugin-sort-newest", "talk-plugin-sort-oldest", - "talk-plugin-viewing-options", - "talk-plugin-profile-settings" + "talk-plugin-viewing-options" ] } diff --git a/plugins/talk-plugin-profile-settings/client/.eslintrc.json b/plugins/talk-plugin-profile-settings/client/.eslintrc.json deleted file mode 100644 index c8a6db18a..000000000 --- a/plugins/talk-plugin-profile-settings/client/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "@coralproject/eslint-config-talk/client" -} diff --git a/plugins/talk-plugin-profile-settings/client/components/Tab.js b/plugins/talk-plugin-profile-settings/client/components/Tab.js deleted file mode 100644 index 86df0e8d1..000000000 --- a/plugins/talk-plugin-profile-settings/client/components/Tab.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { t } from 'plugin-api/beta/client/services'; - -const Tab = () => { - return {t('talk-plugin-profile-settings.tab')}; -}; - -export default Tab; diff --git a/plugins/talk-plugin-profile-settings/client/containers/TabPane.js b/plugins/talk-plugin-profile-settings/client/containers/TabPane.js deleted file mode 100644 index 6384c7160..000000000 --- a/plugins/talk-plugin-profile-settings/client/containers/TabPane.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { compose, gql } from 'react-apollo'; -import TabPane from '../components/TabPane'; -import { withFragments } from 'plugin-api/beta/client/hocs'; -import { getSlotFragmentSpreads } from 'plugin-api/beta/client/utils'; - -const slots = ['profileSettings']; - -class TabPaneContainer extends React.Component { - render() { - return ; - } -} - -const enhance = compose( - withFragments({ - root: gql` - fragment TalkProfileSettings_TabPane_root on RootQuery { - __typename - ${getSlotFragmentSpreads(slots, 'root')} - } - `, - }) -); - -export default enhance(TabPaneContainer); diff --git a/plugins/talk-plugin-profile-settings/client/index.js b/plugins/talk-plugin-profile-settings/client/index.js deleted file mode 100644 index 00e37a0f1..000000000 --- a/plugins/talk-plugin-profile-settings/client/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import Tab from './components/Tab'; -import TabPane from './containers/TabPane'; -import translations from './translations.yml'; - -export default { - slots: { - profileTabs: [Tab], - profileTabPanes: [TabPane], - }, - translations, -}; diff --git a/plugins/talk-plugin-profile-settings/client/translations.yml b/plugins/talk-plugin-profile-settings/client/translations.yml deleted file mode 100644 index cd8edb415..000000000 --- a/plugins/talk-plugin-profile-settings/client/translations.yml +++ /dev/null @@ -1,27 +0,0 @@ -en: - talk-plugin-profile-settings: - tab: Settings -de: - talk-plugin-profile-settings: - tab: Einstellungen -es: - talk-plugin-profile-settings: - tab: Configuración -fr: - talk-plugin-profile-settings: - tab: Paramètres -nl_NL: - talk-plugin-profile-settings: - tab: Instellingen -da: - talk-plugin-profile-settings: - tab: Indstillinger -pt_PR: - talk-plugin-profile-settings: - tab: Configurações -zh_TW: - talk-plugin-profile-settings: - tab: 設置 -zh_CN: - talk-plugin-profile-settings: - tab: 设置 diff --git a/plugins/talk-plugin-profile-settings/index.js b/plugins/talk-plugin-profile-settings/index.js deleted file mode 100644 index f053ebf79..000000000 --- a/plugins/talk-plugin-profile-settings/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; From 84f1003b2b959ed213bcc4e9ba7315560b3bcd99 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Mar 2018 19:58:32 +0100 Subject: [PATCH 15/56] Get rid of `data` direct dependency in the plugins --- client/coral-framework/hocs/index.js | 4 +++ client/coral-framework/hocs/withFetchMore.js | 27 +++++++++++++++++++ client/coral-framework/hocs/withQuery.js | 22 ++++++++++++--- client/coral-framework/hocs/withRefetch.js | 23 ++++++++++++++++ .../hocs/withSubscribeToMore.js | 27 +++++++++++++++++++ client/coral-framework/hocs/withVariables.js | 26 ++++++++++++++++++ package.json | 1 + plugin-api/beta/client/hocs/index.js | 5 ++++ .../client/containers/AuthorName.js | 4 +-- .../client/components/Comment.js | 11 +++----- .../client/components/TabPane.js | 2 -- .../containers/ModIndicatorSubscription.js | 5 ++-- .../client/containers/ModSubscription.js | 17 +++++++++--- .../client/containers/TabPane.js | 17 ++++++++---- .../client/components/FlagDetails.js | 4 +-- .../client/components/ModerationActions.js | 3 --- .../client/containers/ModerationActions.js | 1 - .../client/containers/Settings.js | 2 -- .../client/containers/ViewingOptions.js | 3 +-- yarn.lock | 7 +++++ 20 files changed, 172 insertions(+), 39 deletions(-) create mode 100644 client/coral-framework/hocs/withFetchMore.js create mode 100644 client/coral-framework/hocs/withRefetch.js create mode 100644 client/coral-framework/hocs/withSubscribeToMore.js create mode 100644 client/coral-framework/hocs/withVariables.js diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js index a0f0a3e9c..97bf910d9 100644 --- a/client/coral-framework/hocs/index.js +++ b/client/coral-framework/hocs/index.js @@ -14,6 +14,10 @@ export { default as withPopupAuthHandler } from './withPopupAuthHandler'; export { default as withEnumValues } from './withEnumValues'; export { default as withCombatPassthrough } from './withCombatPassthrough'; 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 withResendEmailConfirmation, } from './withResendEmailConfirmation'; diff --git a/client/coral-framework/hocs/withFetchMore.js b/client/coral-framework/hocs/withFetchMore.js new file mode 100644 index 000000000..cc2106436 --- /dev/null +++ b/client/coral-framework/hocs/withFetchMore.js @@ -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 ( + + {data => ( + + )} + + ); + } + } + return WithFetchMore; +}); diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index 2c941dc57..807989af5 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -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 ( @@ -71,6 +73,15 @@ function networkStatusToString(networkStatus) { } } +// When wrapped broadcast all data changes to channel "queryData". +const withBroadcaster = WrappedComponent => props => ( + /* eslint-disable react/prop-types */ + + + + /* eslint-enable react/prop-types */ +); + const createHOC = (document, config, { notifyOnError = true }) => hoistStatics(WrappedComponent => { return class WithQuery extends React.Component { @@ -357,10 +368,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; }; diff --git a/client/coral-framework/hocs/withRefetch.js b/client/coral-framework/hocs/withRefetch.js new file mode 100644 index 000000000..04848fe11 --- /dev/null +++ b/client/coral-framework/hocs/withRefetch.js @@ -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 ( + + {data => ( + + )} + + ); + } + } + return WithRefetch; +}); diff --git a/client/coral-framework/hocs/withSubscribeToMore.js b/client/coral-framework/hocs/withSubscribeToMore.js new file mode 100644 index 000000000..67ffe9ae2 --- /dev/null +++ b/client/coral-framework/hocs/withSubscribeToMore.js @@ -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 ( + + {data => ( + + )} + + ); + } + } + return WithSubscribeToMore; +}); diff --git a/client/coral-framework/hocs/withVariables.js b/client/coral-framework/hocs/withVariables.js new file mode 100644 index 000000000..886742f36 --- /dev/null +++ b/client/coral-framework/hocs/withVariables.js @@ -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 ( + + {data => ( + + )} + + ); + } + } + return WithVariables; +}); diff --git a/package.json b/package.json index e54ad11d4..585eec6fb 100644 --- a/package.json +++ b/package.json @@ -159,6 +159,7 @@ "query-string": "^5.0.0", "react": "^15.4.2", "react-apollo": "^1.4.12", + "react-broadcast": "^0.6.2", "react-dom": "^15.4.2", "react-input-autosize": "^1.1.4", "react-mdl": "^1.11.0", diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js index 35899a418..ff4523084 100644 --- a/plugin-api/beta/client/hocs/index.js +++ b/plugin-api/beta/client/hocs/index.js @@ -13,6 +13,10 @@ export { withResendEmailConfirmation, withSetUsername, withEnumValues, + withVariables, + withFetchMore, + withSubscribeToMore, + withRefetch, } from 'coral-framework/hocs'; export { withIgnoreUser, @@ -21,3 +25,4 @@ export { withStopIgnoringUser, withSetCommentStatus, } from 'coral-framework/graphql/mutations'; +export { compose } from 'recompose'; diff --git a/plugins/talk-plugin-author-menu/client/containers/AuthorName.js b/plugins/talk-plugin-author-menu/client/containers/AuthorName.js index 958610093..e385058c4 100644 --- a/plugins/talk-plugin-author-menu/client/containers/AuthorName.js +++ b/plugins/talk-plugin-author-menu/client/containers/AuthorName.js @@ -49,7 +49,6 @@ class AuthorNameContainer extends React.Component { render() { const { - data, root, asset, comment, @@ -57,7 +56,7 @@ class AuthorNameContainer extends React.Component { showMenuForComment, } = this.props; - const slotPassthrough = { data, root, asset, comment }; + const slotPassthrough = { root, asset, comment }; return ( - +
    - this.props.data.subscribeToMore(config) + this.props.subscribeToMore(config) ); } @@ -60,4 +61,4 @@ const COMMENT_UNFEATURED_SUBSCRIPTION = gql` } `; -export default ModIndicatorSubscription; +export default compose(withSubscribeToMore)(ModIndicatorSubscription); diff --git a/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js b/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js index 6d0fdd811..d91d69304 100644 --- a/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js +++ b/plugins/talk-plugin-featured-comments/client/containers/ModSubscription.js @@ -6,6 +6,11 @@ import { getDefinitionName } from 'coral-framework/utils'; import truncate from 'lodash/truncate'; import t from 'coral-framework/services/i18n'; import { subscriptionFields } from 'coral-admin/src/routes/Moderation/graphql'; +import { + compose, + withSubscribeToMore, + withVariables, +} from 'plugin-api/beta/client/hocs'; function prepareNotificationText(text) { return truncate(text, { length: 50 }).replace('\n', ' '); @@ -19,7 +24,7 @@ class ModSubscription extends React.Component { { document: COMMENT_FEATURED_SUBSCRIPTION, variables: { - assetId: this.props.data.variables.asset_id, + assetId: this.props.variables.asset_id, }, updateQuery: ( prev, @@ -39,7 +44,7 @@ class ModSubscription extends React.Component { { document: COMMENT_UNFEATURED_SUBSCRIPTION, variables: { - assetId: this.props.data.variables.asset_id, + assetId: this.props.variables.asset_id, }, updateQuery: ( prev, @@ -62,7 +67,7 @@ class ModSubscription extends React.Component { }, ]; this.subscriptions = configs.map(config => - this.props.data.subscribeToMore(config) + this.props.subscribeToMore(config) ); } @@ -111,4 +116,8 @@ const mapStateToProps = state => ({ user: state.auth.user, }); -export default connect(mapStateToProps, null)(ModSubscription); +export default compose( + connect(mapStateToProps, null), + withVariables, + withSubscribeToMore +)(ModSubscription); diff --git a/plugins/talk-plugin-featured-comments/client/containers/TabPane.js b/plugins/talk-plugin-featured-comments/client/containers/TabPane.js index 297f5aedf..b3f44b429 100644 --- a/plugins/talk-plugin-featured-comments/client/containers/TabPane.js +++ b/plugins/talk-plugin-featured-comments/client/containers/TabPane.js @@ -2,7 +2,12 @@ import React from 'react'; import { bindActionCreators } from 'redux'; import { compose, gql } from 'react-apollo'; import TabPane from '../components/TabPane'; -import { withFragments, connect } from 'plugin-api/beta/client/hocs'; +import { + withFragments, + connect, + withFetchMore, + withVariables, +} from 'plugin-api/beta/client/hocs'; import Comment from '../containers/Comment'; import { viewComment } from 'coral-embed-stream/src/actions/stream'; import { @@ -13,15 +18,15 @@ import update from 'immutability-helper'; class TabPaneContainer extends React.Component { loadMore = () => { - return this.props.data.fetchMore({ + return this.props.fetchMore({ query: LOAD_MORE_QUERY, variables: { limit: 5, cursor: this.props.asset.featuredComments.endCursor, asset_id: this.props.asset.id, - sortOrder: this.props.data.variables.sortOrder, - sortBy: this.props.data.variables.sortBy, - excludeIgnored: this.props.data.variables.excludeIgnored, + sortOrder: this.props.variables.sortOrder, + sortBy: this.props.variables.sortBy, + excludeIgnored: this.props.variables.excludeIgnored, }, updateQuery: (previous, { fetchMoreResult: { comments } }) => { const updated = update(previous, { @@ -86,6 +91,8 @@ const mapDispatchToProps = dispatch => const enhance = compose( connect(null, mapDispatchToProps), + withFetchMore, + withVariables, withFragments({ root: gql` fragment TalkFeaturedComments_TabPane_root on RootQuery { diff --git a/plugins/talk-plugin-flag-details/client/components/FlagDetails.js b/plugins/talk-plugin-flag-details/client/components/FlagDetails.js index 7197c0360..7900f5b50 100644 --- a/plugins/talk-plugin-flag-details/client/components/FlagDetails.js +++ b/plugins/talk-plugin-flag-details/client/components/FlagDetails.js @@ -10,7 +10,7 @@ import { class FlagDetails extends Component { render() { - const { comment: { actions }, more, data, root, comment } = this.props; + const { comment: { actions }, more, root, comment } = this.props; const flagActions = actions && actions.filter(a => a.__typename === 'FlagAction'); @@ -27,7 +27,6 @@ class FlagDetails extends Component { const reasons = Object.keys(summaries); const slotPassthrough = { - data, root, comment, }; @@ -68,7 +67,6 @@ class FlagDetails extends Component { FlagDetails.propTypes = { more: PropTypes.bool, - data: PropTypes.object, root: PropTypes.object, comment: PropTypes.shape({ actions: PropTypes.arrayOf( diff --git a/plugins/talk-plugin-moderation-actions/client/components/ModerationActions.js b/plugins/talk-plugin-moderation-actions/client/components/ModerationActions.js index 6cf2558c1..272202d9c 100644 --- a/plugins/talk-plugin-moderation-actions/client/components/ModerationActions.js +++ b/plugins/talk-plugin-moderation-actions/client/components/ModerationActions.js @@ -16,7 +16,6 @@ export default class ModerationActions extends React.Component { comment, root, asset, - data, menuVisible, toogleMenu, hideMenu, @@ -25,7 +24,6 @@ export default class ModerationActions extends React.Component { const slotPassthrough = { comment, asset, - data, }; return ( @@ -72,7 +70,6 @@ ModerationActions.propTypes = { comment: PropTypes.object, root: PropTypes.object, asset: PropTypes.object, - data: PropTypes.object, menuVisible: PropTypes.bool, toogleMenu: PropTypes.func, hideMenu: PropTypes.func, diff --git a/plugins/talk-plugin-moderation-actions/client/containers/ModerationActions.js b/plugins/talk-plugin-moderation-actions/client/containers/ModerationActions.js index 9e0edb9b3..891d248ce 100644 --- a/plugins/talk-plugin-moderation-actions/client/containers/ModerationActions.js +++ b/plugins/talk-plugin-moderation-actions/client/containers/ModerationActions.js @@ -42,7 +42,6 @@ class ModerationActionsContainer extends React.Component { render() { return ( ({ + mapProps(({ root, asset, ...rest }) => ({ slotPassthrough: { - data, root, asset, }, diff --git a/yarn.lock b/yarn.lock index 13f8f704f..0813f7023 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8677,6 +8677,13 @@ react-apollo@^1.4.12: object-assign "^4.0.1" prop-types "^15.5.8" +react-broadcast@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/react-broadcast/-/react-broadcast-0.6.2.tgz#9555c73b80ca5b2673830872e54f6bb3092cf8a9" + dependencies: + invariant "^2.2.1" + prop-types "^15.6.0" + react-dom@>=0.14.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" From 5e4d010246c9606f62b6a42dc0f5bc0d51ffb26b Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Mar 2018 20:43:06 +0100 Subject: [PATCH 16/56] Deprecate passing `data` prop to Slot --- .../coral-framework/hocs/withSlotElements.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/hocs/withSlotElements.js b/client/coral-framework/hocs/withSlotElements.js index 4580e7ce9..e02a9d5a7 100644 --- a/client/coral-framework/hocs/withSlotElements.js +++ b/client/coral-framework/hocs/withSlotElements.js @@ -106,6 +106,21 @@ const createHOC = ({ const slots = this.getSlots(props); const sizes = this.getSizes(props, slots.length); const defaultComponents = this.getSizes(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) => { @@ -114,7 +129,7 @@ const createHOC = ({ const slotElements = plugins.getSlotElements( s, props.reduxState, - this.getPassthrough(props), + slotPassthrough, { size } ); @@ -122,7 +137,7 @@ const createHOC = ({ const p = plugins.getSlotComponentProps( DefaultComponent, props.reduxState, - this.getPassthrough(props) + slotPassthrough ); slotElements.push(); } From a81ae2b7f86f5dee63e652a10ee2ada799891656 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Mar 2018 21:06:32 +0100 Subject: [PATCH 17/56] Remove data prop from Slots --- client/coral-admin/src/components/CommentDetails.js | 4 +--- client/coral-admin/src/components/UserDetail.js | 4 ---- client/coral-admin/src/components/UserDetailComment.js | 5 +---- .../src/components/UserDetailCommentList.js | 3 --- .../src/routes/Configure/components/Configure.js | 2 -- .../src/routes/Configure/containers/Configure.js | 1 - .../routes/Configure/containers/ModerationSettings.js | 3 +-- .../src/routes/Configure/containers/StreamSettings.js | 3 +-- .../src/routes/Configure/containers/TechSettings.js | 3 +-- .../src/routes/Moderation/components/Comment.js | 4 ---- .../src/routes/Moderation/components/Moderation.js | 3 --- .../routes/Moderation/components/ModerationQueue.js | 3 --- .../src/routes/Moderation/containers/Indicator.js | 1 - client/coral-embed-stream/src/components/Embed.js | 8 ++------ .../src/tabs/configure/components/Configure.js | 7 +------ .../src/tabs/configure/containers/Configure.js | 8 +------- .../src/tabs/configure/containers/Settings.js | 3 --- .../src/tabs/profile/components/Comment.js | 5 ++--- .../src/tabs/profile/components/CommentHistory.js | 4 +--- .../src/tabs/profile/components/Profile.js | 5 ++--- .../src/tabs/profile/components/Settings.js | 5 ++--- .../src/tabs/profile/components/TabPanel.js | 6 ++---- .../src/tabs/profile/containers/CommentHistory.js | 10 +++++----- .../src/tabs/profile/containers/Profile.js | 5 ++--- .../src/tabs/profile/containers/TabPanel.js | 2 -- .../src/tabs/stream/components/AllCommentsPane.js | 3 --- .../src/tabs/stream/components/Comment.js | 4 ---- .../src/tabs/stream/components/Stream.js | 10 ++-------- .../client/components/Menu.js | 2 +- 29 files changed, 28 insertions(+), 98 deletions(-) diff --git a/client/coral-admin/src/components/CommentDetails.js b/client/coral-admin/src/components/CommentDetails.js index 7a45219af..a9c0884a4 100644 --- a/client/coral-admin/src/components/CommentDetails.js +++ b/client/coral-admin/src/components/CommentDetails.js @@ -25,11 +25,10 @@ class CommentDetails extends Component { }; render() { - const { data, root, comment, clearHeightCache } = this.props; + const { root, comment, clearHeightCache } = this.props; const { showDetail } = this.state; const slotPassthrough = { - data, clearHeightCache, root, comment, @@ -56,7 +55,6 @@ class CommentDetails extends Component { } CommentDetails.propTypes = { - data: PropTypes.object.isRequired, root: PropTypes.object.isRequired, comment: PropTypes.object.isRequired, clearHeightCache: PropTypes.func, diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index db71b3b78..31ad999dc 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -98,7 +98,6 @@ class UserDetail extends React.Component { renderLoaded() { const { - data, root, root: { me, user, totalComments, rejectedComments }, activeTab, @@ -124,7 +123,6 @@ class UserDetail extends React.Component { const suspended = isSuspended(user); const slotPassthrough = { - data, root, user, }; @@ -303,7 +301,6 @@ class UserDetail extends React.Component {
    - + ); } @@ -135,7 +133,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, diff --git a/client/coral-admin/src/components/UserDetailCommentList.js b/client/coral-admin/src/components/UserDetailCommentList.js index a0ff9982b..3a31ee3f9 100644 --- a/client/coral-admin/src/components/UserDetailCommentList.js +++ b/client/coral-admin/src/components/UserDetailCommentList.js @@ -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, diff --git a/client/coral-admin/src/routes/Configure/components/Configure.js b/client/coral-admin/src/routes/Configure/components/Configure.js index 140222f68..efd428378 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.js +++ b/client/coral-admin/src/routes/Configure/components/Configure.js @@ -75,7 +75,6 @@ export default class Configure extends Component {
    @@ -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, diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js index fe87766b6..ce6ea0627 100644 --- a/client/coral-admin/src/routes/Configure/containers/Configure.js +++ b/client/coral-admin/src/routes/Configure/containers/Configure.js @@ -31,7 +31,6 @@ class ConfigureContainer extends Component { return ( ({ + mapProps(({ root, settings, updatePending, errors, ...rest }) => ({ slotPassthrough: { - data, root, settings, updatePending, diff --git a/client/coral-admin/src/routes/Configure/containers/StreamSettings.js b/client/coral-admin/src/routes/Configure/containers/StreamSettings.js index 314dfa3ab..5bc8e69a9 100644 --- a/client/coral-admin/src/routes/Configure/containers/StreamSettings.js +++ b/client/coral-admin/src/routes/Configure/containers/StreamSettings.js @@ -44,9 +44,8 @@ export default compose( `, }), connect(mapStateToProps, mapDispatchToProps), - mapProps(({ root, settings, data, updatePending, errors, ...rest }) => ({ + mapProps(({ root, settings, updatePending, errors, ...rest }) => ({ slotPassthrough: { - data, root, settings, updatePending, diff --git a/client/coral-admin/src/routes/Configure/containers/TechSettings.js b/client/coral-admin/src/routes/Configure/containers/TechSettings.js index d8a957cbf..7fbe81cdb 100644 --- a/client/coral-admin/src/routes/Configure/containers/TechSettings.js +++ b/client/coral-admin/src/routes/Configure/containers/TechSettings.js @@ -40,9 +40,8 @@ export default compose( `, }), connect(mapStateToProps, mapDispatchToProps), - mapProps(({ root, settings, data, updatePending, errors, ...rest }) => ({ + mapProps(({ root, settings, updatePending, errors, ...rest }) => ({ slotPassthrough: { - data, root, settings, updatePending, diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 8a107ac24..62aaad645 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -53,7 +53,6 @@ class Comment extends React.Component { comment, selected, className, - data, root, root: { settings }, currentAsset, @@ -70,7 +69,6 @@ class Comment extends React.Component { }; const slotPassthrough = { - data, clearHeightCache, root, comment, @@ -177,7 +175,6 @@ class Comment extends React.Component {
    - +
    @@ -20,7 +16,6 @@ class Configure extends React.Component { } Configure.propTypes = { - data: PropTypes.object.isRequired, root: PropTypes.object.isRequired, asset: PropTypes.object.isRequired, }; diff --git a/client/coral-embed-stream/src/tabs/configure/containers/Configure.js b/client/coral-embed-stream/src/tabs/configure/containers/Configure.js index b6104b067..1ba86509e 100644 --- a/client/coral-embed-stream/src/tabs/configure/containers/Configure.js +++ b/client/coral-embed-stream/src/tabs/configure/containers/Configure.js @@ -13,13 +13,7 @@ class ConfigureContainer extends React.Component { return
    {this.props.data.error.message}
    ; } - return ( - - ); + return ; } } diff --git a/client/coral-embed-stream/src/tabs/configure/containers/Settings.js b/client/coral-embed-stream/src/tabs/configure/containers/Settings.js index 652550a5d..cfb72c0ae 100644 --- a/client/coral-embed-stream/src/tabs/configure/containers/Settings.js +++ b/client/coral-embed-stream/src/tabs/configure/containers/Settings.js @@ -57,7 +57,6 @@ class SettingsContainer extends React.Component { const { mergedSettings, canSave, - data, root, asset, errors, @@ -68,7 +67,6 @@ class SettingsContainer extends React.Component { root, asset, settings: mergedSettings, - data, updatePending, errors, }; @@ -91,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, diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.js b/client/coral-embed-stream/src/tabs/profile/components/Comment.js index 668e71702..be13c2c50 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.js @@ -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 slotPassthrough = { data, root, comment, asset: comment.asset }; + const slotPassthrough = { root, comment, asset: comment.asset }; return (
    @@ -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, }; diff --git a/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js b/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js index 64d9e97ae..c15314f29 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js +++ b/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js @@ -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 ; } @@ -33,7 +33,6 @@ class CommentHistory extends React.Component { return ( { +const Profile = ({ username, emailAddress, root, slotPassthrough }) => { return (
    @@ -12,7 +12,7 @@ const Profile = ({ username, emailAddress, data, root, slotPassthrough }) => { {emailAddress ?

    {emailAddress}

    : null}
    - +
    ); }; @@ -20,7 +20,6 @@ const Profile = ({ username, emailAddress, data, root, slotPassthrough }) => { Profile.propTypes = { username: PropTypes.string, emailAddress: PropTypes.string, - data: PropTypes.object, root: PropTypes.object, slotPassthrough: PropTypes.object, }; diff --git a/client/coral-embed-stream/src/tabs/profile/components/Settings.js b/client/coral-embed-stream/src/tabs/profile/components/Settings.js index 1d5a7cc51..c61a09a92 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Settings.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Settings.js @@ -4,8 +4,8 @@ import { Slot } from 'coral-framework/components'; class Settings extends React.Component { render() { - const { data, root } = this.props; - const slotPassthrough = { data, root }; + const { root } = this.props; + const slotPassthrough = { root }; return (
    @@ -15,7 +15,6 @@ class Settings extends React.Component { } Settings.propTypes = { - data: PropTypes.object, root: PropTypes.object, }; diff --git a/client/coral-embed-stream/src/tabs/profile/components/TabPanel.js b/client/coral-embed-stream/src/tabs/profile/components/TabPanel.js index fd236dd3a..08270bcd8 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/TabPanel.js +++ b/client/coral-embed-stream/src/tabs/profile/components/TabPanel.js @@ -7,7 +7,6 @@ import t from 'coral-framework/services/i18n'; import Settings from '../containers/Settings'; const TabPanel = ({ - data, root, activeTab, setActiveTab, @@ -40,10 +39,10 @@ const TabPanel = ({ tabs={tabs} tabPanes={[ - + , - + , ]} sub @@ -52,7 +51,6 @@ const TabPanel = ({ }; TabPanel.propTypes = { - data: PropTypes.object, root: PropTypes.object, slotPassthrough: PropTypes.object, activeTab: PropTypes.string.isRequired, diff --git a/client/coral-embed-stream/src/tabs/profile/containers/CommentHistory.js b/client/coral-embed-stream/src/tabs/profile/containers/CommentHistory.js index a02225939..27727216e 100644 --- a/client/coral-embed-stream/src/tabs/profile/containers/CommentHistory.js +++ b/client/coral-embed-stream/src/tabs/profile/containers/CommentHistory.js @@ -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 ( ({ export default compose( connect(mapStateToProps, null), - withCommentHistoryFragments + withCommentHistoryFragments, + withFetchMore )(CommentHistoryContainer); diff --git a/client/coral-embed-stream/src/tabs/profile/containers/Profile.js b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js index 11d514540..30d634d94 100644 --- a/client/coral-embed-stream/src/tabs/profile/containers/Profile.js +++ b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js @@ -22,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; @@ -40,13 +40,12 @@ class ProfileContainer extends Component { const localProfile = currentUser.profiles.find(p => p.provider === 'local'); const emailAddress = localProfile && localProfile.id; - const slotPassthrough = { data, root }; + const slotPassthrough = { root }; return ( diff --git a/client/coral-embed-stream/src/tabs/profile/containers/TabPanel.js b/client/coral-embed-stream/src/tabs/profile/containers/TabPanel.js index 0679ad31f..46c24227f 100644 --- a/client/coral-embed-stream/src/tabs/profile/containers/TabPanel.js +++ b/client/coral-embed-stream/src/tabs/profile/containers/TabPanel.js @@ -15,7 +15,6 @@ class TabPanelContainer extends Component { render() { return ( { return ( {t('stream.comment_not_found')}; } - const slotPassthrough = { data, root, asset }; + const slotPassthrough = { root, asset }; return (
    @@ -318,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, diff --git a/plugins/talk-plugin-viewing-options/client/components/Menu.js b/plugins/talk-plugin-viewing-options/client/components/Menu.js index bb44ce775..c3ea40fe1 100644 --- a/plugins/talk-plugin-viewing-options/client/components/Menu.js +++ b/plugins/talk-plugin-viewing-options/client/components/Menu.js @@ -26,7 +26,7 @@ class Menu extends React.Component { ))} From 79f885244a9125c844a26727a20856d18e9faa97 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Mar 2018 22:10:00 +0100 Subject: [PATCH 18/56] Add support for withGraphQLExtension --- client/coral-framework/hocs/index.js | 1 + .../hocs/withGraphQLExtension.js | 19 +++++++++ client/coral-framework/services/plugins.js | 14 +++++-- plugin-api/beta/client/hocs/index.js | 1 + .../client/containers/Toggle.js | 40 ++++++++++++++++++- .../client/index.js | 3 -- 6 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 client/coral-framework/hocs/withGraphQLExtension.js diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js index 97bf910d9..e16e09941 100644 --- a/client/coral-framework/hocs/index.js +++ b/client/coral-framework/hocs/index.js @@ -18,6 +18,7 @@ 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'; diff --git a/client/coral-framework/hocs/withGraphQLExtension.js b/client/coral-framework/hocs/withGraphQLExtension.js new file mode 100644 index 000000000..c44a94e47 --- /dev/null +++ b/client/coral-framework/hocs/withGraphQLExtension.js @@ -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.graphqlExtension = extension; + return WithGraphQLExtension; + }); diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 3879249ac..b390f1a1b 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -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'; @@ -205,9 +206,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() { diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js index ff4523084..215641f32 100644 --- a/plugin-api/beta/client/hocs/index.js +++ b/plugin-api/beta/client/hocs/index.js @@ -17,6 +17,7 @@ export { withFetchMore, withSubscribeToMore, withRefetch, + withGraphQLExtension, } from 'coral-framework/hocs'; export { withIgnoreUser, diff --git a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js index d261946d0..c55be5f90 100644 --- a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js +++ b/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js @@ -3,7 +3,10 @@ import PropTypes from 'prop-types'; import { compose, gql } from 'react-apollo'; import Toggle from 'talk-plugin-notifications/client/components/Toggle'; import { t } from 'plugin-api/beta/client/services'; -import { withFragments } from 'plugin-api/beta/client/hocs'; +import { + withFragments, + withGraphQLExtension, +} from 'plugin-api/beta/client/hocs'; class ToggleContainer extends React.Component { constructor(props) { @@ -57,6 +60,38 @@ ToggleContainer.propTypes = { disabled: PropTypes.bool.isRequired, }; +const extension = { + mutations: { + UpdateNotificationSettings: ({ + variables: { input }, + state: { auth: { user: { id } } }, + }) => ({ + update: proxy => { + if (input.onReply === undefined) { + return; + } + + const fragment = gql` + fragment TalkNotificationsCategoryReply_User_Fragment on User { + notificationSettings { + onReply + } + } + `; + const fragmentId = `User_${id}`; + const data = { + __typename: 'User', + notificationSettings: { + __typename: 'NotificationSettings', + onReply: input.onReply, + }, + }; + proxy.writeFragment({ fragment, id: fragmentId, data }); + }, + }), + }, +}; + const enhance = compose( withFragments({ root: gql` @@ -68,7 +103,8 @@ const enhance = compose( } } `, - }) + }), + withGraphQLExtension(extension) ); export default enhance(ToggleContainer); diff --git a/plugins/talk-plugin-notifications-category-reply/client/index.js b/plugins/talk-plugin-notifications-category-reply/client/index.js index 1e22c3a93..fa10c15e2 100644 --- a/plugins/talk-plugin-notifications-category-reply/client/index.js +++ b/plugins/talk-plugin-notifications-category-reply/client/index.js @@ -1,11 +1,8 @@ import Toggle from './containers/Toggle'; import translations from './translations.yml'; -import graphql from './graphql'; - export default { slots: { notificationSettings: [Toggle], }, translations, - ...graphql, }; From 414b62ff668b186f5b5c66e8f32ac2addc20c39f Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Mar 2018 23:11:27 +0100 Subject: [PATCH 19/56] Refactor talk-plugin-notifications using the new withGraphQLExtension --- .../client/containers/Toggle.js | 74 ----------- .../client/graphql.js | 33 ----- .../client/index.js | 11 +- .../client/containers/Toggle.js | 110 ---------------- .../client/graphql.js | 33 ----- .../client/index.js | 10 +- .../client/containers/Toggle.js | 74 ----------- .../client/graphql.js | 33 ----- .../client/index.js | 11 +- .../client/api/components/Toggle.css | 20 +++ .../client/api/components/Toggle.js | 41 ++++++ .../client/api/components/index.js | 1 + .../api/factories/createSettingsToggle.js | 22 ++++ .../client/api/factories/index.js | 1 + .../client/api/hocs/index.js | 1 + .../client/api/hocs/withSettingsToggle.js | 121 ++++++++++++++++++ 16 files changed, 229 insertions(+), 367 deletions(-) delete mode 100644 plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js delete mode 100644 plugins/talk-plugin-notifications-category-featured/client/graphql.js delete mode 100644 plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js delete mode 100644 plugins/talk-plugin-notifications-category-reply/client/graphql.js delete mode 100644 plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js delete mode 100644 plugins/talk-plugin-notifications-category-staff/client/graphql.js create mode 100644 plugins/talk-plugin-notifications/client/api/components/Toggle.css create mode 100644 plugins/talk-plugin-notifications/client/api/components/Toggle.js create mode 100644 plugins/talk-plugin-notifications/client/api/components/index.js create mode 100644 plugins/talk-plugin-notifications/client/api/factories/createSettingsToggle.js create mode 100644 plugins/talk-plugin-notifications/client/api/factories/index.js create mode 100644 plugins/talk-plugin-notifications/client/api/hocs/index.js create mode 100644 plugins/talk-plugin-notifications/client/api/hocs/withSettingsToggle.js diff --git a/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js deleted file mode 100644 index f888789f3..000000000 --- a/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { compose, gql } from 'react-apollo'; -import Toggle from 'talk-plugin-notifications/client/components/Toggle'; -import { t } from 'plugin-api/beta/client/services'; -import { withFragments } from 'plugin-api/beta/client/hocs'; - -class ToggleContainer extends React.Component { - constructor(props) { - super(props); - props.setTurnOffInputFragment({ onFeatured: false }); - - if (this.getOnFeaturedSetting()) { - props.indicateOn(); - } - } - - componentWillReceiveProps(nextProps) { - const prevSetting = this.getOnFeaturedSetting(this.props); - const nextSetting = this.getOnFeaturedSetting(nextProps); - if (prevSetting && !nextSetting) { - nextProps.indicateOff(); - } else if (!prevSetting && nextSetting) { - nextProps.indicateOn(); - } - } - - getOnFeaturedSetting = (props = this.props) => - props.root.me.notificationSettings.onFeatured; - - toggle = () => { - this.props.updateNotificationSettings({ - onFeatured: !this.getOnFeaturedSetting(), - }); - }; - - render() { - return ( - - {t('talk-plugin-notifications-category-featured.toggle_description')} - - ); - } -} - -ToggleContainer.propTypes = { - data: PropTypes.object, - root: PropTypes.object, - indicateOn: PropTypes.func.isRequired, - indicateOff: PropTypes.func.isRequired, - setTurnOffInputFragment: PropTypes.func.isRequired, - updateNotificationSettings: PropTypes.func.isRequired, - disabled: PropTypes.bool.isRequired, -}; - -const enhance = compose( - withFragments({ - root: gql` - fragment TalkNotificationsCategoryFeatured_Toggle_root on RootQuery { - me { - notificationSettings { - onFeatured - } - } - } - `, - }) -); - -export default enhance(ToggleContainer); diff --git a/plugins/talk-plugin-notifications-category-featured/client/graphql.js b/plugins/talk-plugin-notifications-category-featured/client/graphql.js deleted file mode 100644 index 440a6f7fa..000000000 --- a/plugins/talk-plugin-notifications-category-featured/client/graphql.js +++ /dev/null @@ -1,33 +0,0 @@ -import { gql } from 'react-apollo'; - -export default { - mutations: { - UpdateNotificationSettings: ({ - variables: { input }, - state: { auth: { user: { id } } }, - }) => ({ - update: proxy => { - if (input.onFeatured === undefined) { - return; - } - - const fragment = gql` - fragment TalkNotificationsCategoryFeatured_User_Fragment on User { - notificationSettings { - onFeatured - } - } - `; - const fragmentId = `User_${id}`; - const data = { - __typename: 'User', - notificationSettings: { - __typename: 'NotificationSettings', - onFeatured: input.onFeatured, - }, - }; - proxy.writeFragment({ fragment, id: fragmentId, data }); - }, - }), - }, -}; diff --git a/plugins/talk-plugin-notifications-category-featured/client/index.js b/plugins/talk-plugin-notifications-category-featured/client/index.js index 1e22c3a93..74c0c2aa5 100644 --- a/plugins/talk-plugin-notifications-category-featured/client/index.js +++ b/plugins/talk-plugin-notifications-category-featured/client/index.js @@ -1,11 +1,14 @@ -import Toggle from './containers/Toggle'; import translations from './translations.yml'; -import graphql from './graphql'; +import { t } from 'plugin-api/beta/client/services'; +import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories'; + +const SettingsToggle = createSettingsToggle('onFeatured', () => + t('talk-plugin-notifications-category-featured.toggle_description') +); export default { slots: { - notificationSettings: [Toggle], + notificationSettings: [SettingsToggle], }, translations, - ...graphql, }; diff --git a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js deleted file mode 100644 index c55be5f90..000000000 --- a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js +++ /dev/null @@ -1,110 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { compose, gql } from 'react-apollo'; -import Toggle from 'talk-plugin-notifications/client/components/Toggle'; -import { t } from 'plugin-api/beta/client/services'; -import { - withFragments, - withGraphQLExtension, -} from 'plugin-api/beta/client/hocs'; - -class ToggleContainer extends React.Component { - constructor(props) { - super(props); - props.setTurnOffInputFragment({ onReply: false }); - - if (this.getOnReplySetting()) { - props.indicateOn(); - } - } - - componentWillReceiveProps(nextProps) { - const prevSetting = this.getOnReplySetting(this.props); - const nextSetting = this.getOnReplySetting(nextProps); - if (prevSetting && !nextSetting) { - nextProps.indicateOff(); - } else if (!prevSetting && nextSetting) { - nextProps.indicateOn(); - } - } - - getOnReplySetting = (props = this.props) => - props.root.me.notificationSettings.onReply; - - toggle = () => { - this.props.updateNotificationSettings({ - onReply: !this.getOnReplySetting(), - }); - }; - - render() { - return ( - - {t('talk-plugin-notifications-category-reply.toggle_description')} - - ); - } -} - -ToggleContainer.propTypes = { - data: PropTypes.object, - root: PropTypes.object, - indicateOn: PropTypes.func.isRequired, - indicateOff: PropTypes.func.isRequired, - setTurnOffInputFragment: PropTypes.func.isRequired, - updateNotificationSettings: PropTypes.func.isRequired, - disabled: PropTypes.bool.isRequired, -}; - -const extension = { - mutations: { - UpdateNotificationSettings: ({ - variables: { input }, - state: { auth: { user: { id } } }, - }) => ({ - update: proxy => { - if (input.onReply === undefined) { - return; - } - - const fragment = gql` - fragment TalkNotificationsCategoryReply_User_Fragment on User { - notificationSettings { - onReply - } - } - `; - const fragmentId = `User_${id}`; - const data = { - __typename: 'User', - notificationSettings: { - __typename: 'NotificationSettings', - onReply: input.onReply, - }, - }; - proxy.writeFragment({ fragment, id: fragmentId, data }); - }, - }), - }, -}; - -const enhance = compose( - withFragments({ - root: gql` - fragment TalkNotificationsCategoryReply_Toggle_root on RootQuery { - me { - notificationSettings { - onReply - } - } - } - `, - }), - withGraphQLExtension(extension) -); - -export default enhance(ToggleContainer); diff --git a/plugins/talk-plugin-notifications-category-reply/client/graphql.js b/plugins/talk-plugin-notifications-category-reply/client/graphql.js deleted file mode 100644 index 623e72366..000000000 --- a/plugins/talk-plugin-notifications-category-reply/client/graphql.js +++ /dev/null @@ -1,33 +0,0 @@ -import { gql } from 'react-apollo'; - -export default { - mutations: { - UpdateNotificationSettings: ({ - variables: { input }, - state: { auth: { user: { id } } }, - }) => ({ - update: proxy => { - if (input.onReply === undefined) { - return; - } - - const fragment = gql` - fragment TalkNotificationsCategoryReply_User_Fragment on User { - notificationSettings { - onReply - } - } - `; - const fragmentId = `User_${id}`; - const data = { - __typename: 'User', - notificationSettings: { - __typename: 'NotificationSettings', - onReply: input.onReply, - }, - }; - proxy.writeFragment({ fragment, id: fragmentId, data }); - }, - }), - }, -}; diff --git a/plugins/talk-plugin-notifications-category-reply/client/index.js b/plugins/talk-plugin-notifications-category-reply/client/index.js index fa10c15e2..098a1397e 100644 --- a/plugins/talk-plugin-notifications-category-reply/client/index.js +++ b/plugins/talk-plugin-notifications-category-reply/client/index.js @@ -1,8 +1,14 @@ -import Toggle from './containers/Toggle'; import translations from './translations.yml'; +import { t } from 'plugin-api/beta/client/services'; +import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories'; + +const SettingsToggle = createSettingsToggle('onReply', () => + t('talk-plugin-notifications-category-reply.toggle_description') +); + export default { slots: { - notificationSettings: [Toggle], + notificationSettings: [SettingsToggle], }, translations, }; diff --git a/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js deleted file mode 100644 index 62a4cbbce..000000000 --- a/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { compose, gql } from 'react-apollo'; -import Toggle from 'talk-plugin-notifications/client/components/Toggle'; -import { t } from 'plugin-api/beta/client/services'; -import { withFragments } from 'plugin-api/beta/client/hocs'; - -class ToggleContainer extends React.Component { - constructor(props) { - super(props); - props.setTurnOffInputFragment({ onStaffReply: false }); - - if (this.getOnReplySetting()) { - props.indicateOn(); - } - } - - componentWillReceiveProps(nextProps) { - const prevSetting = this.getOnReplySetting(this.props); - const nextSetting = this.getOnReplySetting(nextProps); - if (prevSetting && !nextSetting) { - nextProps.indicateOff(); - } else if (!prevSetting && nextSetting) { - nextProps.indicateOn(); - } - } - - getOnReplySetting = (props = this.props) => - props.root.me.notificationSettings.onStaffReply; - - toggle = () => { - this.props.updateNotificationSettings({ - onStaffReply: !this.getOnReplySetting(), - }); - }; - - render() { - return ( - - {t('talk-plugin-notifications-category-staff.toggle_description')} - - ); - } -} - -ToggleContainer.propTypes = { - data: PropTypes.object, - root: PropTypes.object, - indicateOn: PropTypes.func.isRequired, - indicateOff: PropTypes.func.isRequired, - setTurnOffInputFragment: PropTypes.func.isRequired, - updateNotificationSettings: PropTypes.func.isRequired, - disabled: PropTypes.bool.isRequired, -}; - -const enhance = compose( - withFragments({ - root: gql` - fragment TalkNotificationsCategoryStaffReply_User_Fragment on RootQuery { - me { - notificationSettings { - onStaffReply - } - } - } - `, - }) -); - -export default enhance(ToggleContainer); diff --git a/plugins/talk-plugin-notifications-category-staff/client/graphql.js b/plugins/talk-plugin-notifications-category-staff/client/graphql.js deleted file mode 100644 index 2e8007d4f..000000000 --- a/plugins/talk-plugin-notifications-category-staff/client/graphql.js +++ /dev/null @@ -1,33 +0,0 @@ -import { gql } from 'react-apollo'; - -export default { - mutations: { - UpdateNotificationSettings: ({ - variables: { input }, - state: { auth: { user: { id } } }, - }) => ({ - update: proxy => { - if (input.onStaffReply === undefined) { - return; - } - - const fragment = gql` - fragment TalkNotificationsCategoryStaffReply_User_Fragment on User { - notificationSettings { - onStaffReply - } - } - `; - const fragmentId = `User_${id}`; - const data = { - __typename: 'User', - notificationSettings: { - __typename: 'NotificationSettings', - onStaffReply: input.onStaffReply, - }, - }; - proxy.writeFragment({ fragment, id: fragmentId, data }); - }, - }), - }, -}; diff --git a/plugins/talk-plugin-notifications-category-staff/client/index.js b/plugins/talk-plugin-notifications-category-staff/client/index.js index 1e22c3a93..e273fceaa 100644 --- a/plugins/talk-plugin-notifications-category-staff/client/index.js +++ b/plugins/talk-plugin-notifications-category-staff/client/index.js @@ -1,11 +1,14 @@ -import Toggle from './containers/Toggle'; import translations from './translations.yml'; -import graphql from './graphql'; +import { t } from 'plugin-api/beta/client/services'; +import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories'; + +const SettingsToggle = createSettingsToggle('onStaffReply', () => + t('talk-plugin-notifications-category-staff.toggle_description') +); export default { slots: { - notificationSettings: [Toggle], + notificationSettings: [SettingsToggle], }, translations, - ...graphql, }; diff --git a/plugins/talk-plugin-notifications/client/api/components/Toggle.css b/plugins/talk-plugin-notifications/client/api/components/Toggle.css new file mode 100644 index 000000000..3f5b02705 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/components/Toggle.css @@ -0,0 +1,20 @@ +.title { + display: inline-block; + width: 100%; + cursor: pointer; + user-select: none; + + &.disabled { + color: #e5e5e5; + cursor: default; + } +} + +.toggle { + display: flex; + align-items: center; +} + +.checkBox { + text-align: right; +} diff --git a/plugins/talk-plugin-notifications/client/api/components/Toggle.js b/plugins/talk-plugin-notifications/client/api/components/Toggle.js new file mode 100644 index 000000000..40e7da4e6 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/components/Toggle.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Checkbox } from 'plugin-api/beta/client/components/ui'; +import styles from './Toggle.css'; +import uuid from 'uuid/v4'; +import cn from 'classnames'; + +class Toggle extends React.Component { + id = uuid(); + + render() { + const { checked, onChange, children, disabled } = this.props; + return ( +
    + +
    + +
    +
    + ); + } +} + +Toggle.propTypes = { + disabled: PropTypes.bool, + checked: PropTypes.bool, + onChange: PropTypes.func, + children: PropTypes.node, +}; + +export default Toggle; diff --git a/plugins/talk-plugin-notifications/client/api/components/index.js b/plugins/talk-plugin-notifications/client/api/components/index.js new file mode 100644 index 000000000..81e73b58b --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/components/index.js @@ -0,0 +1 @@ +export { default as Toggle } from './Toggle'; diff --git a/plugins/talk-plugin-notifications/client/api/factories/createSettingsToggle.js b/plugins/talk-plugin-notifications/client/api/factories/createSettingsToggle.js new file mode 100644 index 000000000..95b7789cb --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/factories/createSettingsToggle.js @@ -0,0 +1,22 @@ +import React from 'react'; +import Toggle from '../components/Toggle'; +import withSettingsToggle from '../hocs/withSettingsToggle'; + +/** + * createSettingsToggle will add a boolean setting with the + * name `settingsName` to notification settings and return + * a full Toggle Component. + * + * You must provide a `label` either as a string or as a callback. + * E.g. to provide translations you could do: + * + * `const SettingsToggle = createSettingsToggle('onReply', () => t('translate'));` + */ +const createSettingsToggle = (settingsName, label) => { + const SettingsToggle = props => ( + {typeof label === 'function' ? label() : label} + ); + return withSettingsToggle(settingsName)(SettingsToggle); +}; + +export default createSettingsToggle; diff --git a/plugins/talk-plugin-notifications/client/api/factories/index.js b/plugins/talk-plugin-notifications/client/api/factories/index.js new file mode 100644 index 000000000..b18ad7679 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/factories/index.js @@ -0,0 +1 @@ +export { default as createSettingsToggle } from './createSettingsToggle'; diff --git a/plugins/talk-plugin-notifications/client/api/hocs/index.js b/plugins/talk-plugin-notifications/client/api/hocs/index.js new file mode 100644 index 000000000..c55d371bc --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/hocs/index.js @@ -0,0 +1 @@ +export { default as withSettingsToggle } from './withSettingsToggle'; diff --git a/plugins/talk-plugin-notifications/client/api/hocs/withSettingsToggle.js b/plugins/talk-plugin-notifications/client/api/hocs/withSettingsToggle.js new file mode 100644 index 000000000..fc7b1bac6 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/hocs/withSettingsToggle.js @@ -0,0 +1,121 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { compose, gql } from 'react-apollo'; +import { + withFragments, + withGraphQLExtension, +} from 'plugin-api/beta/client/hocs'; + +const createHOC = settingsName => WrappedComponent => { + class WithSettingsToggle extends React.Component { + constructor(props) { + super(props); + props.setTurnOffInputFragment({ [settingsName]: false }); + + if (this.isChecked()) { + props.indicateOn(); + } + } + + componentWillReceiveProps(nextProps) { + const prevSetting = this.isChecked(this.props); + const nextSetting = this.isChecked(nextProps); + if (prevSetting && !nextSetting) { + nextProps.indicateOff(); + } else if (!prevSetting && nextSetting) { + nextProps.indicateOn(); + } + } + + isChecked = (props = this.props) => + props.root.me.notificationSettings[settingsName]; + + toggle = () => { + this.props.updateNotificationSettings({ + [settingsName]: !this.isChecked(), + }); + }; + + render() { + return ( + + ); + } + } + + WithSettingsToggle.propTypes = { + root: PropTypes.object, + indicateOn: PropTypes.func.isRequired, + indicateOff: PropTypes.func.isRequired, + setTurnOffInputFragment: PropTypes.func.isRequired, + updateNotificationSettings: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired, + }; + + return WithSettingsToggle; +}; + +/** + * withSettingsToggle will add a boolean setting with the + * name `settingsName` to notification settings and provide + * the folliwng props: + * + * `checked: boolean` Whether setting is on or off + * `onChange: () => void` Calling this will toggle the setting + * `disabled: boolean` Whether setting is disabled + */ +const withSettingsToggle = settingsName => { + const extension = { + mutations: { + UpdateNotificationSettings: ({ + variables: { input }, + state: { auth: { user: { id } } }, + }) => ({ + update: proxy => { + if (input[settingsName] === undefined) { + return; + } + + const fragment = gql` + fragment TalkNotifications_Toggle_${settingsName}_Fragment on User { + notificationSettings { + ${settingsName} + } + } + `; + const fragmentId = `User_${id}`; + const data = { + __typename: 'User', + notificationSettings: { + __typename: 'NotificationSettings', + [settingsName]: input[settingsName], + }, + }; + proxy.writeFragment({ fragment, id: fragmentId, data }); + }, + }), + }, + }; + + return compose( + withFragments({ + root: gql` + fragment TalkNotifications_Toggle_${settingsName}_root on RootQuery { + me { + notificationSettings { + ${settingsName} + } + } + } + `, + }), + withGraphQLExtension(extension), + createHOC(settingsName) + ); +}; + +export default withSettingsToggle; From 73fce8624c66f157cee92f15f5d3b8241d3808b0 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Mar 2018 23:32:21 +0100 Subject: [PATCH 20/56] Fix query data detection --- client/coral-framework/hocs/withQuery.js | 35 +--------------------- client/coral-framework/services/plugins.js | 22 +++++++------- 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index 807989af5..77fb7380e 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -24,34 +24,6 @@ const withSkipOnErrors = reducer => (prev, action, ...rest) => { return reducer(prev, action, ...rest); }; -/** - * attachFromQueryMarkers will add a `__unsafeFromQuery` to objects - * inside data, to allow other parts of the framework to easily detect - * that the data came from a query. - */ -function attachFromQueryMarkers(data) { - if (typeof data === 'object') { - if (data === null) { - return data; - } - if (Array.isArray(data)) { - const result = [...data]; - result.forEach((v, k) => { - result[k] = attachFromQueryMarkers(v); - }); - return result; - } else { - const result = { ...data }; - result.__unsafeFromQuery = true; - Object.keys(result).forEach(key => { - result[key] = attachFromQueryMarkers(result[key]); - }); - return result; - } - } - return data; -} - function networkStatusToString(networkStatus) { switch (networkStatus) { case 1: @@ -323,11 +295,6 @@ const createHOC = (document, config, { notifyOnError = true }) => const nextData = this.nextData(args.data); const { root } = separateDataAndRoot(args.data); - // We attach query markes `__fromQuery` to each node in - // the returned `root` result, so the framework can - // easily detect props coming from the query. - const rootWithQueryMarkers = attachFromQueryMarkers(root); - if (config.props) { // Custom props, in this case we just pass the wrapped args to it. return config.props({ @@ -337,7 +304,7 @@ const createHOC = (document, config, { notifyOnError = true }) => } // Return our wrapped data with a separated root. - return { ...args, data: nextData, root: rootWithQueryMarkers }; + return { ...args, data: nextData, root }; }, }; diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index b390f1a1b..4c09c1047 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -84,20 +84,22 @@ function getSlotComponentProps(component, reduxState, props, queryData) { } /** - * splitProps detects props coming from the query and - * returns `queryData` and `rest`. + * 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 = {}; - if (props.passthrough) { - Object.keys(props).forEach(k => { - if (props[k].__unsafeFromQuery) { - queryData[k] = props[k]; - delete rest[k]; - } - }); - } + 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 }; } From 3d8fc116a7b6493316dde95148f0cd680d10ffc5 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 14 Mar 2018 11:32:13 +0100 Subject: [PATCH 21/56] Fix defaultComponent bug --- client/coral-framework/hocs/withSlotElements.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/coral-framework/hocs/withSlotElements.js b/client/coral-framework/hocs/withSlotElements.js index e02a9d5a7..a4b39c907 100644 --- a/client/coral-framework/hocs/withSlotElements.js +++ b/client/coral-framework/hocs/withSlotElements.js @@ -105,7 +105,10 @@ const createHOC = ({ const { plugins } = this.context; const slots = this.getSlots(props); const sizes = this.getSizes(props, slots.length); - const defaultComponents = this.getSizes(props, slots.length); + const defaultComponents = this.getDefaultComponents( + props, + slots.length + ); const slotPassthrough = this.getPassthrough(props); if (process.env.NODE_ENV !== 'production') { From 106b2faaf1957409d761df77afd7602b0ae6e3b4 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 14 Mar 2018 14:25:00 +0100 Subject: [PATCH 22/56] Extract css --- package.json | 1 + views/admin.ejs | 1 + views/embed/stream.ejs | 1 + views/login.ejs | 1 + webpack.config.js | 16 +++++++++++----- yarn.lock | 11 ++++++++++- 6 files changed, 25 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 924df52d6..29becde3e 100644 --- a/package.json +++ b/package.json @@ -213,6 +213,7 @@ "enzyme-adapter-react-15": "^1.0.0", "eslint": "^4.5.0", "eslint-plugin-mocha": "^4.11.0", + "extract-text-webpack-plugin": "^3.0.2", "husky": "^0.14.3", "identity-obj-proxy": "^3.0.0", "ip": "^1.1.5", diff --git a/views/admin.ejs b/views/admin.ejs index 939da0dcf..36356f797 100644 --- a/views/admin.ejs +++ b/views/admin.ejs @@ -5,6 +5,7 @@ Talk - Coral Admin + + + + + + <%- include partials/head %> diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs index 50faaf5c2..0ca249176 100644 --- a/views/embed/stream.ejs +++ b/views/embed/stream.ejs @@ -2,6 +2,8 @@ + + <%- include ../partials/head %> diff --git a/views/login.ejs b/views/login.ejs index 9ae282f65..7af820a9c 100644 --- a/views/login.ejs +++ b/views/login.ejs @@ -2,6 +2,8 @@ + + <%- include partials/head %> diff --git a/views/partials/head.ejs b/views/partials/head.ejs index 555f5689e..e6cdb97d2 100644 --- a/views/partials/head.ejs +++ b/views/partials/head.ejs @@ -12,11 +12,9 @@ - - <%_ if (locals.customCssUrl) { _%> <%_ } _%> <%- include data %> - \ No newline at end of file + From 4f844dfd58510abe7604360bcda8c5b9dba1f88c Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 14 Mar 2018 22:38:27 -0300 Subject: [PATCH 30/56] Adding deprecation notice --- .../src/tabs/stream/containers/Stream.js | 2 +- client/coral-framework/components/Slot.js | 12 +++++++++++- client/coral-framework/services/plugins.js | 10 +++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 8515a2db4..9b12349c5 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -465,7 +465,7 @@ const mapStateToProps = state => ({ activeStreamTab: state.stream.activeTab, previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, - pluginsConfig: state.config.plugins_config, + pluginsConfig: state.config.plugins_config || state.config.plugin_config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, }); diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 18d3163e0..25e6521eb 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -72,8 +72,18 @@ class Slot extends React.Component { } = this.props; const { plugins } = this.context; let children = this.getChildren(); + + if (!!get(reduxState, 'config.plugin_config')) { + console.warn( + `deprecation warning: config.plugin_config will be phased out soon, please replace calls from config.plugin_config to config.plugins_config` + ); + } + const pluginsConfig = - get(reduxState, 'config.plugins_config') || emptyConfig; + get(reduxState, 'config.plugins_config') || + get(reduxState, 'config.plugin_config') || + emptyConfig; + if (children.length === 0 && DefaultComponent) { const props = plugins.getSlotComponentProps( DefaultComponent, diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index f0e6ff304..5d2c88e55 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -84,8 +84,16 @@ class PluginsService { * query datas are only passed to the component if it is defined in `component.fragments`. */ getSlotComponentProps(component, reduxState, props, queryData) { + if (!!get(reduxState, 'config.plugin_config')) { + console.warn( + `deprecation warning: config.plugin_config will be phased out soon, please replace calls from config.plugin_config to config.plugins_config` + ); + } + const pluginsConfig = - get(reduxState, 'config.plugins_config') || emptyConfig; + get(reduxState, 'config.plugins_config') || + get(reduxState, 'config.plugin_config') || + emptyConfig; return { ...props, config: pluginsConfig, From 7d7e3f60d383ff27df4a878fd5b548c942c86ec7 Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 14 Mar 2018 23:33:06 -0300 Subject: [PATCH 31/56] show dep message only once --- client/coral-framework/components/Slot.js | 6 ------ client/coral-framework/services/plugins.js | 8 +++++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 25e6521eb..a2def60fa 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -73,12 +73,6 @@ class Slot extends React.Component { const { plugins } = this.context; let children = this.getChildren(); - if (!!get(reduxState, 'config.plugin_config')) { - console.warn( - `deprecation warning: config.plugin_config will be phased out soon, please replace calls from config.plugin_config to config.plugins_config` - ); - } - const pluginsConfig = get(reduxState, 'config.plugins_config') || get(reduxState, 'config.plugin_config') || diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 5d2c88e55..fb8ae045d 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -83,11 +83,17 @@ class PluginsService { * 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`. */ + showPluginsConfigWarning = true; + getSlotComponentProps(component, reduxState, props, queryData) { - if (!!get(reduxState, 'config.plugin_config')) { + if ( + !!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; } const pluginsConfig = From 9d9adc81951a072664523c0802d3bcbc014aeefc Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 15 Mar 2018 18:38:21 +0100 Subject: [PATCH 32/56] Rename typo --- client/coral-framework/components/IfSlotIsEmpty.js | 4 ++-- client/coral-framework/components/IfSlotIsNotEmpty.js | 4 ++-- client/coral-framework/components/Slot.js | 4 ++-- client/coral-framework/hocs/index.js | 2 +- .../{withCombatPassthrough.js => withCompatPassthrough.js} | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename client/coral-framework/hocs/{withCombatPassthrough.js => withCompatPassthrough.js} (95%) diff --git a/client/coral-framework/components/IfSlotIsEmpty.js b/client/coral-framework/components/IfSlotIsEmpty.js index 599b21401..dc7b7f41d 100644 --- a/client/coral-framework/components/IfSlotIsEmpty.js +++ b/client/coral-framework/components/IfSlotIsEmpty.js @@ -1,6 +1,6 @@ import React, { Children } from 'react'; import PropTypes from 'prop-types'; -import { withSlotElements, withCombatPassthrough } from '../hocs'; +import { withSlotElements, withCompatPassthrough } from '../hocs'; import { compose } from 'recompose'; class IfSlotIsEmpty extends React.Component { @@ -26,7 +26,7 @@ IfSlotIsEmpty.propTypes = { const omitProps = ['slot', 'children']; export default compose( - withCombatPassthrough(omitProps), + withCompatPassthrough(omitProps), withSlotElements({ slot: props => props.slot, }) diff --git a/client/coral-framework/components/IfSlotIsNotEmpty.js b/client/coral-framework/components/IfSlotIsNotEmpty.js index be67d4e4f..6318e6447 100644 --- a/client/coral-framework/components/IfSlotIsNotEmpty.js +++ b/client/coral-framework/components/IfSlotIsNotEmpty.js @@ -1,6 +1,6 @@ import React, { Children } from 'react'; import PropTypes from 'prop-types'; -import { withSlotElements, withCombatPassthrough } from '../hocs'; +import { withSlotElements, withCompatPassthrough } from '../hocs'; import { compose } from 'recompose'; class IfSlotIsNotEmpty extends React.Component { @@ -26,7 +26,7 @@ IfSlotIsNotEmpty.propTypes = { const omitProps = ['slot', 'children']; export default compose( - withCombatPassthrough(omitProps), + withCompatPassthrough(omitProps), withSlotElements({ slot: props => props.slot, }) diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index a70b9ccf9..a2f90161f 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -5,7 +5,7 @@ import { connect } from 'react-redux'; import kebabCase from 'lodash/kebabCase'; import PropTypes from 'prop-types'; import get from 'lodash/get'; -import { withSlotElements, withCombatPassthrough } from '../hocs'; +import { withSlotElements, withCompatPassthrough } from '../hocs'; import { compose } from 'recompose'; class Slot extends React.Component { @@ -96,7 +96,7 @@ const mapStateToProps = state => ({ }); export default compose( - withCombatPassthrough(omitProps), + withCompatPassthrough(omitProps), withSlotElements({ slot: props => props.fill, size: props => props.size, diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js index e16e09941..26d69f8e7 100644 --- a/client/coral-framework/hocs/index.js +++ b/client/coral-framework/hocs/index.js @@ -12,7 +12,7 @@ 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 withCombatPassthrough } from './withCombatPassthrough'; +export { default as withCompatPassthrough } from './withCompatPassthrough'; export { default as withSlotElements } from './withSlotElements'; export { default as withVariables } from './withVariables'; export { default as WithRefetch } from './withRefetch'; diff --git a/client/coral-framework/hocs/withCombatPassthrough.js b/client/coral-framework/hocs/withCompatPassthrough.js similarity index 95% rename from client/coral-framework/hocs/withCombatPassthrough.js rename to client/coral-framework/hocs/withCompatPassthrough.js index 84dbdb492..57589854d 100644 --- a/client/coral-framework/hocs/withCombatPassthrough.js +++ b/client/coral-framework/hocs/withCompatPassthrough.js @@ -43,7 +43,7 @@ function getPassthrough(props, omitProps) { /** * @Deprecated - * withCombatPassthrough is a compatibility HOC that supports our old + * withCompatPassthrough is a compatibility HOC that supports our old * API which puts unknown props and `queryData` to `passhtrough` to be * used with HOC `withSlotElements`. */ From 96dff8ed25c0ab7efe66c0c619c0041e83fbbcdf Mon Sep 17 00:00:00 2001 From: okbel Date: Fri, 16 Mar 2018 20:16:28 -0300 Subject: [PATCH 33/56] wip :0 --- .../coral-embed-stream/src/constants/debug.js | 2 ++ .../coral-embed-stream/src/reducers/debug.js | 20 +++++++++++++++++++ .../coral-embed-stream/src/reducers/index.js | 2 ++ client/coral-embed/src/Stream.js | 4 ++++ client/coral-embed/src/StreamInterface.js | 4 ++++ client/coral-framework/components/Slot.js | 4 +++- client/coral-framework/services/bootstrap.js | 6 ++++++ 7 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 client/coral-embed-stream/src/constants/debug.js create mode 100644 client/coral-embed-stream/src/reducers/debug.js diff --git a/client/coral-embed-stream/src/constants/debug.js b/client/coral-embed-stream/src/constants/debug.js new file mode 100644 index 000000000..d31784af9 --- /dev/null +++ b/client/coral-embed-stream/src/constants/debug.js @@ -0,0 +1,2 @@ +export const ENABLE_PLUGINS_DEBUG = 'ENABLE_PLUGINS_DEBUG'; +export const DISABLE_PLUGINS_DEBUG = 'DISABLE_PLUGINS_DEBUG'; diff --git a/client/coral-embed-stream/src/reducers/debug.js b/client/coral-embed-stream/src/reducers/debug.js new file mode 100644 index 000000000..bcd919ebf --- /dev/null +++ b/client/coral-embed-stream/src/reducers/debug.js @@ -0,0 +1,20 @@ +import * as actions from '../constants/debug'; + +const initialState = { + plugins: false, +}; + +export default function DEBUG(state = initialState, action) { + switch (action.type) { + case actions.ENABLE_PLUGINS_DEBUG: + return { + plugins: true, + }; + case actions.DISABLE_PLUGINS_DEBUG: + return { + plugins: false, + }; + default: + return state; + } +} diff --git a/client/coral-embed-stream/src/reducers/index.js b/client/coral-embed-stream/src/reducers/index.js index 15056d414..a896e64c0 100644 --- a/client/coral-embed-stream/src/reducers/index.js +++ b/client/coral-embed-stream/src/reducers/index.js @@ -3,6 +3,7 @@ import embed from './embed'; import configure from './configure'; import stream from './stream'; import profile from './profile'; +import debug from './debug'; export default { login, @@ -10,4 +11,5 @@ export default { configure, stream, profile, + debug, }; diff --git a/client/coral-embed/src/Stream.js b/client/coral-embed/src/Stream.js index fc8c0d5e7..fffa26aba 100644 --- a/client/coral-embed/src/Stream.js +++ b/client/coral-embed/src/Stream.js @@ -161,6 +161,10 @@ export default class Stream { ); } + dispatch(action) { + this.pym.sendMessage('dispatch', action); + } + login(token) { this.pym.sendMessage('login', token); } diff --git a/client/coral-embed/src/StreamInterface.js b/client/coral-embed/src/StreamInterface.js index 4c6e29970..67e2fff1b 100644 --- a/client/coral-embed/src/StreamInterface.js +++ b/client/coral-embed/src/StreamInterface.js @@ -3,6 +3,10 @@ export default class StreamInterface { this._stream = stream; } + dispatch(action) { + return this._stream.dispatch(action); + } + on(eventName, callback) { return this._stream.emitter.on(eventName, callback); } diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index a2def60fa..4f56f7994 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -69,6 +69,7 @@ class Slot extends React.Component { defaultComponent: DefaultComponent, queryData, fill, + debugPlugins, } = this.props; const { plugins } = this.context; let children = this.getChildren(); @@ -103,7 +104,7 @@ class Slot extends React.Component { className={cn( { [styles.inline]: inline, - [styles.debug]: pluginsConfig.debug, + [styles.debug]: debugPlugins || pluginsConfig.debug, }, className, `talk-slot-${kebabCase(fill)}` @@ -156,6 +157,7 @@ Slot.propTypes = { const mapStateToProps = state => ({ reduxState: state, + debugPlugins: state.debug.plugins, }); export default connect(mapStateToProps, null)(Slot); diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index 691404369..fc06ea441 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -215,6 +215,12 @@ export async function createContext({ pym.onMessage('logout', () => { store.dispatch(logout()); }); + + pym.onMessage('dispatch', action => { + store.dispatch({ + type: action, + }); + }); } const preInitList = []; From a700e91911517debb15f5bcbccd4e89fb4f73e27 Mon Sep 17 00:00:00 2001 From: okbel Date: Sat, 17 Mar 2018 16:26:46 -0300 Subject: [PATCH 34/56] adding debugPlugins --- client/coral-framework/components/Slot.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 4f56f7994..c69b10cf1 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -93,11 +93,12 @@ class Slot extends React.Component { children = children.map(childFactory); } - const debugProps = pluginsConfig.debug - ? { - 'data-slot-name': fill, - } - : {}; + const debugProps = + debugPlugins || pluginsConfig.debug + ? { + 'data-slot-name': fill, + } + : {}; return ( Date: Sat, 17 Mar 2018 16:44:01 -0300 Subject: [PATCH 35/56] refactor due to auth probs --- client/coral-framework/components/Slot.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index c69b10cf1..3719db8c2 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -69,7 +69,7 @@ class Slot extends React.Component { defaultComponent: DefaultComponent, queryData, fill, - debugPlugins, + debug = {}, } = this.props; const { plugins } = this.context; let children = this.getChildren(); @@ -94,7 +94,7 @@ class Slot extends React.Component { } const debugProps = - debugPlugins || pluginsConfig.debug + debug.plugins || pluginsConfig.debug ? { 'data-slot-name': fill, } @@ -105,7 +105,7 @@ class Slot extends React.Component { className={cn( { [styles.inline]: inline, - [styles.debug]: debugPlugins || pluginsConfig.debug, + [styles.debug]: debug.plugins || pluginsConfig.debug, }, className, `talk-slot-${kebabCase(fill)}` @@ -123,7 +123,7 @@ Slot.defaultProps = { }; Slot.propTypes = { - debugPlugins: PropTypes.bool, + debug: PropTypes.object, fill: PropTypes.string.isRequired, inline: PropTypes.bool, className: PropTypes.string, @@ -159,7 +159,7 @@ Slot.propTypes = { const mapStateToProps = state => ({ reduxState: state, - debugPlugins: state.debug.plugins, + debug: state.debug, }); export default connect(mapStateToProps, null)(Slot); From 40782a5d0b6aff92d29e7884ae0b6e08df9f47d2 Mon Sep 17 00:00:00 2001 From: okbel Date: Sat, 17 Mar 2018 18:55:39 -0300 Subject: [PATCH 36/56] Adding off method to the Stream Interface --- client/coral-embed/src/StreamInterface.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/coral-embed/src/StreamInterface.js b/client/coral-embed/src/StreamInterface.js index 67e2fff1b..57339cf90 100644 --- a/client/coral-embed/src/StreamInterface.js +++ b/client/coral-embed/src/StreamInterface.js @@ -11,6 +11,10 @@ export default class StreamInterface { return this._stream.emitter.on(eventName, callback); } + off(eventName, callback) { + return this._stream.emitter.off(eventName, callback); + } + login(token) { return this._stream.login(token); } From 346a099c1b9d05d2ecedaf6162977ed82617d225 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 19 Mar 2018 15:10:14 -0300 Subject: [PATCH 37/56] changes - ready to be tested --- client/coral-embed-stream/src/tabs/stream/containers/Stream.js | 2 ++ client/coral-framework/components/Slot.js | 2 ++ client/coral-framework/services/plugins.js | 1 + 3 files changed, 5 insertions(+) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 9b12349c5..1eae7d816 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -465,6 +465,8 @@ const mapStateToProps = state => ({ activeStreamTab: state.stream.activeTab, previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, + + // @Deprecated plugin_config pluginsConfig: state.config.plugins_config || state.config.plugin_config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 3719db8c2..b4b0a725a 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -74,6 +74,8 @@ class Slot extends React.Component { const { plugins } = this.context; let children = this.getChildren(); + // @Deprecated plugin_config + const pluginsConfig = get(reduxState, 'config.plugins_config') || get(reduxState, 'config.plugin_config') || diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index fb8ae045d..7f1323587 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -87,6 +87,7 @@ class PluginsService { getSlotComponentProps(component, reduxState, props, queryData) { if ( + process.env.NODE_ENV !== 'production' && !!get(reduxState, 'config.plugin_config') && this.showPluginsConfigWarning ) { From eb331551c5675968c90e56702c7fb70b470008b7 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 19 Mar 2018 15:14:48 -0300 Subject: [PATCH 38/56] support for both --- plugin-api/beta/client/selectors/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin-api/beta/client/selectors/index.js b/plugin-api/beta/client/selectors/index.js index c7951ca56..5ae41f6e0 100644 --- a/plugin-api/beta/client/selectors/index.js +++ b/plugin-api/beta/client/selectors/index.js @@ -1 +1,3 @@ -export const pluginsConfigSelector = state => state.config.pluginsConfig; +// @Deprecated plugin_config +export const pluginsConfigSelector = state => + state.config.plugins_config || state.config.plugin_config; From 8a2fa2432f611abed0a0ec0282094544d2d65326 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 19 Mar 2018 15:26:18 -0300 Subject: [PATCH 39/56] @Deprecated plugin_config --- client/coral-framework/services/plugins.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 7f1323587..fbd2ae21f 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -97,6 +97,7 @@ class PluginsService { this.showPluginsConfigWarning = false; } + // @Deprecated plugin_config const pluginsConfig = get(reduxState, 'config.plugins_config') || get(reduxState, 'config.plugin_config') || From 58c5026fb7359fcec660e5f3d68c99a78ed06bcd Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 19 Mar 2018 13:00:41 -0600 Subject: [PATCH 40/56] some minor fixes --- middleware/staticTemplate.js | 47 +++++++++++++++++++++--------------- webpack.config.js | 13 ++++++++-- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/middleware/staticTemplate.js b/middleware/staticTemplate.js index 1d79f0011..b7d01feb4 100644 --- a/middleware/staticTemplate.js +++ b/middleware/staticTemplate.js @@ -1,8 +1,7 @@ const SettingsService = require('../services/settings'); const fs = require('fs'); const path = require('path'); -const merge = require('lodash/merge'); -const memoize = require('lodash/memoize'); +const { merge } = require('lodash'); const { BASE_URL, @@ -38,36 +37,44 @@ const attachStaticLocals = locals => { } }; +// MANIFESTS are all the manifests accessible by Talk. +const MANIFESTS = ['../dist/manifest.json', '../dist/manifest.embed.json']; + +// getManifest will retrieve the manifest files and parse the JSON. function getManifest() { return merge( {}, - ...['../dist/manifest.json', '../dist/manifest.embed.json'] - .map(f => fs.readFileSync(path.resolve(__dirname, f), 'utf8')) - .map(JSON.parse) + ...MANIFESTS.map(f => + fs.readFileSync(path.resolve(__dirname, f), 'utf8') + ).map(JSON.parse) ); } -const getManifestMemoized = memoize(getManifest); - -if (process.env.NODE_ENV === 'production') { - // Crash early if file does not exists. - getManifestMemoized(); -} - -function resolve(key) { +/** + * resolve is a function that can be used in templates to resolve an asset from + * the manifest. In production, the manifest is cached. + */ +const resolve = (() => { if (process.env.NODE_ENV === 'production') { - return `${STATIC_URL}static/${getManifestMemoized()[key]}`; - } else { - // In dev mode, we are more forgiving and we always load the - // newest version of the manifest. + // In production, we should attempt to load the manifest early. + const manifest = getManifest(); + + return key => `${STATIC_URL}static/${manifest[key]}`; + } + + // In dev mode, we are more forgiving and we always load the + // newest version of the manifest. + return key => { try { - return `${STATIC_URL}static/${getManifest()[key]}`; + const manifest = getManifest(); + + return `${STATIC_URL}static/${manifest[key]}`; } catch (err) { console.warn(err); return ''; } - } -} + }; +})(); module.exports = async (req, res, next) => { try { diff --git a/webpack.config.js b/webpack.config.js index 0f2287fa9..184107491 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -93,7 +93,15 @@ const config = { use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ - 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', + { + loader: 'css-loader', + options: { + minimize: true, + modules: true, + importLoaders: 1, + localIdentName: '[name]__[local]___[hash:base64:5]', + }, + }, 'postcss-loader', ], }), @@ -104,7 +112,8 @@ const config = { test: /\.(jpg|png|gif|svg)$/, }, { - loader: 'url-loader?limit=100000', + loader: 'url-loader', + options: { limit: 100000 }, test: /\.woff$/, }, { From d6fba2d515c1a82a7d7cd4b2caff5040c09d632d Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 20 Mar 2018 11:52:44 -0300 Subject: [PATCH 41/56] refactor into using config reducer --- .../coral-embed-stream/src/constants/debug.js | 2 -- .../src/reducers/configure.js | 2 +- .../coral-embed-stream/src/reducers/debug.js | 20 ------------------- client/coral-framework/components/Slot.js | 16 ++++++--------- client/coral-framework/constants/config.js | 2 ++ client/coral-framework/reducers/config.js | 14 ++++++++++++- 6 files changed, 22 insertions(+), 34 deletions(-) delete mode 100644 client/coral-embed-stream/src/constants/debug.js delete mode 100644 client/coral-embed-stream/src/reducers/debug.js diff --git a/client/coral-embed-stream/src/constants/debug.js b/client/coral-embed-stream/src/constants/debug.js deleted file mode 100644 index d31784af9..000000000 --- a/client/coral-embed-stream/src/constants/debug.js +++ /dev/null @@ -1,2 +0,0 @@ -export const ENABLE_PLUGINS_DEBUG = 'ENABLE_PLUGINS_DEBUG'; -export const DISABLE_PLUGINS_DEBUG = 'DISABLE_PLUGINS_DEBUG'; diff --git a/client/coral-embed-stream/src/reducers/configure.js b/client/coral-embed-stream/src/reducers/configure.js index 41d87f8d8..48b28ec72 100644 --- a/client/coral-embed-stream/src/reducers/configure.js +++ b/client/coral-embed-stream/src/reducers/configure.js @@ -8,7 +8,7 @@ const initialState = { errors: {}, }; -export default function config(state = initialState, action) { +export default function configure(state = initialState, action) { switch (action.type) { case actions.UPDATE_PENDING: { let next = state; diff --git a/client/coral-embed-stream/src/reducers/debug.js b/client/coral-embed-stream/src/reducers/debug.js deleted file mode 100644 index bcd919ebf..000000000 --- a/client/coral-embed-stream/src/reducers/debug.js +++ /dev/null @@ -1,20 +0,0 @@ -import * as actions from '../constants/debug'; - -const initialState = { - plugins: false, -}; - -export default function DEBUG(state = initialState, action) { - switch (action.type) { - case actions.ENABLE_PLUGINS_DEBUG: - return { - plugins: true, - }; - case actions.DISABLE_PLUGINS_DEBUG: - return { - plugins: false, - }; - default: - return state; - } -} diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index b4b0a725a..d60d69185 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -69,7 +69,6 @@ class Slot extends React.Component { defaultComponent: DefaultComponent, queryData, fill, - debug = {}, } = this.props; const { plugins } = this.context; let children = this.getChildren(); @@ -95,19 +94,18 @@ class Slot extends React.Component { children = children.map(childFactory); } - const debugProps = - debug.plugins || pluginsConfig.debug - ? { - 'data-slot-name': fill, - } - : {}; + const debugProps = pluginsConfig.debug + ? { + 'data-slot-name': fill, + } + : {}; return ( ({ reduxState: state, - debug: state.debug, }); export default connect(mapStateToProps, null)(Slot); diff --git a/client/coral-framework/constants/config.js b/client/coral-framework/constants/config.js index bf846af1d..b18b67387 100644 --- a/client/coral-framework/constants/config.js +++ b/client/coral-framework/constants/config.js @@ -1,3 +1,5 @@ const prefix = `TALK_FRAMEWORK`; export const MERGE_CONFIG = `${prefix}_MERGE_CONFIG`; +export const ENABLE_PLUGINS_DEBUG = `${prefix}_ENABLE_PLUGINS_DEBUG`; +export const DISABLE_PLUGINS_DEBUG = `${prefix}_DISABLE_PLUGINS_DEBUG`; diff --git a/client/coral-framework/reducers/config.js b/client/coral-framework/reducers/config.js index f7aebd9e2..eccc0d4bf 100644 --- a/client/coral-framework/reducers/config.js +++ b/client/coral-framework/reducers/config.js @@ -1,10 +1,22 @@ -import { MERGE_CONFIG } from '../constants/config'; +import { + MERGE_CONFIG, + ENABLE_PLUGINS_DEBUG, + DISABLE_PLUGINS_DEBUG, +} from '../constants/config'; import { LOGOUT } from '../constants/auth'; const initialState = {}; export default function config(state = initialState, action) { switch (action.type) { + case ENABLE_PLUGINS_DEBUG: + return { + plugins: true, + }; + case DISABLE_PLUGINS_DEBUG: + return { + plugins: false, + }; case LOGOUT: return { ...state, From aa40e7033b628d7fc333a32cf5e01c05880a0532 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 20 Mar 2018 11:04:58 -0400 Subject: [PATCH 42/56] Update README --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31e285607..b7818dd0a 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,17 @@ From getting up and running, to advanced configuration, to how to scale Talk, ou Learn more about Talk, including a deep dive into features for commenters and moderators, and FAQs in our [Talk Product Guide](https:/docs.coralproject.net/talk/how-talk-works). -## Relevant Links +## Pre-Launch Guide +You’ve installed Talk on your server, and you’re preparing to launch it on your site. The real community work starts now, before you go live. You have a unique opportunity pre-launch to set your community up for success. Read our [Talk Community Guide](https://blog.coralproject.net/youve-installed-talk-now-what/). + +## More Resources + +- [Talk Product Roadmap](https://www.pivotaltracker.com/n/projects/1863625) - [Our Blog](https://blog.coralproject.net/) - [Community Forums](https://community.coralproject.net/) - [Community Guides for Journalism](https://guides.coralproject.net/) - [More About Us](https://coralproject.net/) -- [Talk Roadmap](https://www.pivotaltracker.com/n/projects/1863625) ## End-to-End Testing From 40ec1a42dcd7ff6051a7e0618a1612723020945e Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 20 Mar 2018 11:06:18 -0400 Subject: [PATCH 43/56] Fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b7818dd0a..6bdb7fda0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ From getting up and running, to advanced configuration, to how to scale Talk, ou ## Product Guide -Learn more about Talk, including a deep dive into features for commenters and moderators, and FAQs in our [Talk Product Guide](https:/docs.coralproject.net/talk/how-talk-works). +Learn more about Talk, including a deep dive into features for commenters and moderators, and FAQs in our [Talk Product Guide](https://docs.coralproject.net/talk/how-talk-works). ## Pre-Launch Guide From 030deb1b81e8a191a5850e514235e34722aeebd9 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 20 Mar 2018 12:09:32 -0300 Subject: [PATCH 44/56] refactor into using config reducer --- client/coral-embed-stream/src/reducers/index.js | 2 -- client/coral-embed/src/Stream.js | 8 ++++++-- client/coral-embed/src/StreamInterface.js | 12 ++++++++---- client/coral-framework/actions/config.js | 14 +++++++++++++- client/coral-framework/components/Slot.js | 1 - client/coral-framework/reducers/config.js | 12 ++++++++++-- client/coral-framework/services/bootstrap.js | 12 +++++++----- 7 files changed, 44 insertions(+), 17 deletions(-) diff --git a/client/coral-embed-stream/src/reducers/index.js b/client/coral-embed-stream/src/reducers/index.js index a896e64c0..15056d414 100644 --- a/client/coral-embed-stream/src/reducers/index.js +++ b/client/coral-embed-stream/src/reducers/index.js @@ -3,7 +3,6 @@ import embed from './embed'; import configure from './configure'; import stream from './stream'; import profile from './profile'; -import debug from './debug'; export default { login, @@ -11,5 +10,4 @@ export default { configure, stream, profile, - debug, }; diff --git a/client/coral-embed/src/Stream.js b/client/coral-embed/src/Stream.js index fffa26aba..600e19741 100644 --- a/client/coral-embed/src/Stream.js +++ b/client/coral-embed/src/Stream.js @@ -161,8 +161,12 @@ export default class Stream { ); } - dispatch(action) { - this.pym.sendMessage('dispatch', action); + enableDebug() { + this.pym.sendMessage('enableDebug'); + } + + disableDebug() { + this.pym.sendMessage('disableDebug'); } login(token) { diff --git a/client/coral-embed/src/StreamInterface.js b/client/coral-embed/src/StreamInterface.js index 57339cf90..50bce0800 100644 --- a/client/coral-embed/src/StreamInterface.js +++ b/client/coral-embed/src/StreamInterface.js @@ -3,10 +3,6 @@ export default class StreamInterface { this._stream = stream; } - dispatch(action) { - return this._stream.dispatch(action); - } - on(eventName, callback) { return this._stream.emitter.on(eventName, callback); } @@ -26,4 +22,12 @@ export default class StreamInterface { remove() { return this._stream.remove(); } + + enableDebug() { + return this._stream.enableDebug(); + } + + disableDebug() { + return this._stream.disableDebug(); + } } diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js index dd1522333..3a04d3fa4 100644 --- a/client/coral-framework/actions/config.js +++ b/client/coral-framework/actions/config.js @@ -1,6 +1,18 @@ -import { MERGE_CONFIG } from '../constants/config'; +import { + MERGE_CONFIG, + ENABLE_PLUGINS_DEBUG, + DISABLE_PLUGINS_DEBUG, +} from '../constants/config'; export const mergeConfig = config => ({ type: MERGE_CONFIG, config, }); + +export const enablePlugins = () => ({ + type: ENABLE_PLUGINS_DEBUG, +}); + +export const disablePlugins = () => ({ + type: DISABLE_PLUGINS_DEBUG, +}); diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index d60d69185..0dea70e57 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -74,7 +74,6 @@ class Slot extends React.Component { let children = this.getChildren(); // @Deprecated plugin_config - const pluginsConfig = get(reduxState, 'config.plugins_config') || get(reduxState, 'config.plugin_config') || diff --git a/client/coral-framework/reducers/config.js b/client/coral-framework/reducers/config.js index eccc0d4bf..b063f9b97 100644 --- a/client/coral-framework/reducers/config.js +++ b/client/coral-framework/reducers/config.js @@ -11,11 +11,19 @@ export default function config(state = initialState, action) { switch (action.type) { case ENABLE_PLUGINS_DEBUG: return { - plugins: true, + ...state, + plugins_config: { + ...state.plugins_config, + debug: true, + }, }; case DISABLE_PLUGINS_DEBUG: return { - plugins: false, + ...state, + plugins_config: { + ...state.plugins_config, + debug: false, + }, }; case LOGOUT: return { diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index fc06ea441..f2263ecac 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -25,7 +25,7 @@ import { createIntrospection } from 'coral-framework/services/introspection'; import introspectionData from 'coral-framework/graphql/introspection.json'; import coreReducers from '../reducers'; import { checkLogin as checkLoginAction } from '../actions/auth'; -import { mergeConfig } from '../actions/config'; +import { mergeConfig, enableDebug, disableDebug } from '../actions/config'; import { setAuthToken, logout } from '../actions/auth'; /** @@ -216,10 +216,12 @@ export async function createContext({ store.dispatch(logout()); }); - pym.onMessage('dispatch', action => { - store.dispatch({ - type: action, - }); + pym.onMessage('enableDebug', () => { + store.dispatch(enableDebug()); + }); + + pym.onMessage('disableDebug', () => { + store.dispatch(disableDebug()); }); } From cd9c26099531d120ed06bd3839eb5711b550ed60 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 20 Mar 2018 13:10:42 -0300 Subject: [PATCH 45/56] refactor into using config reducer --- client/coral-embed/src/Stream.js | 8 ++++---- client/coral-embed/src/StreamInterface.js | 8 ++++---- client/coral-framework/actions/config.js | 4 ++-- client/coral-framework/services/bootstrap.js | 14 +++++++++----- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/client/coral-embed/src/Stream.js b/client/coral-embed/src/Stream.js index 600e19741..d0ada6080 100644 --- a/client/coral-embed/src/Stream.js +++ b/client/coral-embed/src/Stream.js @@ -161,12 +161,12 @@ export default class Stream { ); } - enableDebug() { - this.pym.sendMessage('enableDebug'); + enablePluginsDebug() { + this.pym.sendMessage('enablePluginsDebug'); } - disableDebug() { - this.pym.sendMessage('disableDebug'); + disablePluginsDebug() { + this.pym.sendMessage('disablePluginsDebug'); } login(token) { diff --git a/client/coral-embed/src/StreamInterface.js b/client/coral-embed/src/StreamInterface.js index 50bce0800..1e54dd155 100644 --- a/client/coral-embed/src/StreamInterface.js +++ b/client/coral-embed/src/StreamInterface.js @@ -23,11 +23,11 @@ export default class StreamInterface { return this._stream.remove(); } - enableDebug() { - return this._stream.enableDebug(); + enablePluginsDebug() { + return this._stream.enablePluginsDebug(); } - disableDebug() { - return this._stream.disableDebug(); + disablePluginsDebug() { + return this._stream.disablePluginsDebug(); } } diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js index 3a04d3fa4..859fac4d5 100644 --- a/client/coral-framework/actions/config.js +++ b/client/coral-framework/actions/config.js @@ -9,10 +9,10 @@ export const mergeConfig = config => ({ config, }); -export const enablePlugins = () => ({ +export const enablePluginsDebug = () => ({ type: ENABLE_PLUGINS_DEBUG, }); -export const disablePlugins = () => ({ +export const disablePluginsDebug = () => ({ type: DISABLE_PLUGINS_DEBUG, }); diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index f2263ecac..878c2e303 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -25,7 +25,11 @@ import { createIntrospection } from 'coral-framework/services/introspection'; import introspectionData from 'coral-framework/graphql/introspection.json'; import coreReducers from '../reducers'; import { checkLogin as checkLoginAction } from '../actions/auth'; -import { mergeConfig, enableDebug, disableDebug } from '../actions/config'; +import { + mergeConfig, + enablePluginsDebug, + disablePluginsDebug, +} from '../actions/config'; import { setAuthToken, logout } from '../actions/auth'; /** @@ -216,12 +220,12 @@ export async function createContext({ store.dispatch(logout()); }); - pym.onMessage('enableDebug', () => { - store.dispatch(enableDebug()); + pym.onMessage('enablePluginsDebug', () => { + store.dispatch(enablePluginsDebug()); }); - pym.onMessage('disableDebug', () => { - store.dispatch(disableDebug()); + pym.onMessage('disablePluginsDebug', () => { + store.dispatch(disablePluginsDebug()); }); } From b6ba59ac513351b96470c1b023e7ee5deb53bb59 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 20 Mar 2018 13:19:20 -0300 Subject: [PATCH 46/56] using the plugins service --- .../src/tabs/stream/containers/Stream.js | 3 --- client/coral-framework/components/Slot.js | 7 ------- client/coral-framework/services/plugins.js | 8 ++++++++ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 1eae7d816..9687a9370 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -465,9 +465,6 @@ const mapStateToProps = state => ({ activeStreamTab: state.stream.activeTab, previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, - - // @Deprecated plugin_config - pluginsConfig: state.config.plugins_config || state.config.plugin_config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, }); diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 0dea70e57..049d075ab 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -93,12 +93,6 @@ class Slot extends React.Component { children = children.map(childFactory); } - const debugProps = pluginsConfig.debug - ? { - 'data-slot-name': fill, - } - : {}; - return ( {children} diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index fbd2ae21f..8f9ad5b41 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -102,8 +102,16 @@ class PluginsService { 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)) From 6c35f02e6b722506fc89c902119d63a543977f15 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 20 Mar 2018 13:50:28 -0300 Subject: [PATCH 47/56] changes --- client/coral-framework/components/Slot.js | 10 +++++++--- client/coral-framework/services/plugins.js | 12 +++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 049d075ab..e6a3eda2d 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -8,6 +8,7 @@ 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 = {}; @@ -75,9 +76,10 @@ class Slot extends React.Component { // @Deprecated plugin_config const pluginsConfig = - get(reduxState, 'config.plugins_config') || - get(reduxState, 'config.plugin_config') || - emptyConfig; + merge( + get(reduxState, 'config.plugins_config'), + get(reduxState, 'config.plugin_config') + ) || emptyConfig; if (children.length === 0 && DefaultComponent) { const props = plugins.getSlotComponentProps( @@ -93,6 +95,8 @@ class Slot extends React.Component { children = children.map(childFactory); } + // console.log('pluginsConfig', pluginsConfig); + return ( Date: Tue, 20 Mar 2018 13:04:13 -0400 Subject: [PATCH 48/56] English version of docs --- docs/source/04-06-slots-and-plugins.md | 169 +++++++++++++++++++ docs/source/plugins/slots-and-plugins-ES.md | 157 ++++++++++++++++++ docs/source/plugins/slots-and-plugins.md | 170 ++++++++++++++++++++ 3 files changed, 496 insertions(+) create mode 100644 docs/source/04-06-slots-and-plugins.md create mode 100644 docs/source/plugins/slots-and-plugins-ES.md create mode 100644 docs/source/plugins/slots-and-plugins.md diff --git a/docs/source/04-06-slots-and-plugins.md b/docs/source/04-06-slots-and-plugins.md new file mode 100644 index 000000000..86e3addeb --- /dev/null +++ b/docs/source/04-06-slots-and-plugins.md @@ -0,0 +1,169 @@ +--- +title: Slots and Plugins +permalink: /slots-and-plugins/ +--- + +Plugins make use of **"slots"** in order to change Talk's interface. + +By default, Talk has various plugins provided by default. We can see this in `plugins.default.json`: + +```json +{ + "server": [ + "talk-plugin-auth", + "talk-plugin-featured-comments", + "talk-plugin-offtopic", + "talk-plugin-respect" + ], + "client": [ + "talk-plugin-auth", + "talk-plugin-author-menu", + "talk-plugin-comment-content", + "talk-plugin-featured-comments", + "talk-plugin-flag-details", + "talk-plugin-ignore-user", + "talk-plugin-member-since", + "talk-plugin-moderation-actions", + "talk-plugin-offtopic", + "talk-plugin-permalink", + "talk-plugin-respect", + "talk-plugin-sort-most-replied", + "talk-plugin-sort-most-respected", + "talk-plugin-sort-newest", + "talk-plugin-sort-oldest", + "talk-plugin-viewing-options", + "talk-plugin-profile-settings" + ] +} +``` + +Let's only focus on the plugins which are listed under `client` - these are the plugins that use *slots* to inject certain functionality into the Talk UI. + +For example, if we look at the Respect plugin (`talk-plugin-respect`), we can see its `client/index.js` looks like this: + + +```js +import RespectButton from './RespectButton'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + commentReactions: [RespectButton], + }, +}; + +``` + +Inside the `slots` property, we specify which **slots** the plugin will use. Above we are saying that the `RespectButton` component is being injected into the slot `commentReactions`. + +Slots can receive an Array of components, so we can use one plugin or many for one slot. + +### Anatomy of the Slot Component + +In Talk core, we have 32 slots available for us to use. The component `Slot` has a `fill` property where we establish the name of the slot. It looks like this: + + +```js + +``` + +You won't have to use this to build plugins, but it's helpful to find where to embed your plugin. + +### Slot List + +* `adminCommentDetailArea` +* `adminCommentMoreDetails` +* `adminCommentLabels` +* `adminModerationSettings` +* `adminStreamSettings` +* `adminTechSettings` +* `adminCommentInfoBar` +* `adminCommentContent` +* `adminSideActions` +* `adminModeration` +* `adminModerationIndicator` + +* `commentInputDetailArea` +* `commentAvatar` +* `commentAuthorName` +* `commentAuthorTags` +* `commentTimestamp` +* `commentInfoBar` +* `commentContent` +* `commentReactions` +* `commentActions` +* `commentInputArea` + +* `draftArea` +* `streamSettings` +* `historyCommentTimestamp` +* `profileSections` +* `embed` +* `stream` +* `streamFilter` +* `streamQuestionArea` +* `login` +* `userProfile` +* `userDetailCommentContent` + +### Where should I insert my plugin? + +The first thing we should consider is what components will be affected by our plugin's functionality. For example, if we want to add functionality to all the comments that are rendered in a total list of comments, we would use the component `Comment`. + +The slots that are able to add functionality to comments start with `comment`, like `commentContent`, or `commentReactions`, as you can see above. + +### Disabling plugins via `plugins_config` + +Typically, you will manage plugins via your `plugins.json` file. If you want to remove a plugin, you would simply delete it there. However, we can also do this directly with the `plugins_config`. + +Let's look at our example article, `views/article.ejs`. Here we see can we have the Talk embed, and within the embed, we can also send a configuration object. To disable a plugin visually, we can pass `true` to the property `disable_components`. Like so: + + +```js +plugins_config: { + 'talk-plugin-love': { + disable_components: true, + }, +} +``` + +### Sending information to slots and plugins + + +Inside our `plugins_config`, we can also send properities and our plugins will receive them. For example, if we send this: + +```js +plugins_config: { + test: 'data' +} +``` + +The plugin will receive a config object with the properties we've passed. If we do a `console.log` with `this.props`, we would see: + +```js +config: {test: 'data'} +``` + +### Debugging slots and plugins + + +You can debug slots and plugins simply by passing the `debug` property with value `true`: + + +```js +plugins_config: { + debug: true +} +``` + +This will turn on a visual aid to show you all of Talk's available slots and their names. Just move your mouse around! + +### Slot ClassNames + +Slots autogenerate their classes with the prefix `talk-slot`, followed by the name of the slot in kebab case. + +For example, the class autogenerated for the slot `commentContent` is `talk-slot-comment-content`. diff --git a/docs/source/plugins/slots-and-plugins-ES.md b/docs/source/plugins/slots-and-plugins-ES.md new file mode 100644 index 000000000..846ccd033 --- /dev/null +++ b/docs/source/plugins/slots-and-plugins-ES.md @@ -0,0 +1,157 @@ +# Plugins y Slots +Los *Slots* son una parte muy importante para crear plugins. Estos nos permiten inyectar nuestros plugins en la interfaz visual de Talk. + +Talk por defecto muestra varios plugins - Observemos el archivo `plugins.default.json` : + +```json +{ + "server": [ + "talk-plugin-auth", + "talk-plugin-featured-comments", + "talk-plugin-offtopic", + "talk-plugin-respect" + ], + "client": [ + "talk-plugin-auth", + "talk-plugin-author-menu", + "talk-plugin-comment-content", + "talk-plugin-featured-comments", + "talk-plugin-flag-details", + "talk-plugin-ignore-user", + "talk-plugin-member-since", + "talk-plugin-moderation-actions", + "talk-plugin-offtopic", + "talk-plugin-permalink", + "talk-plugin-respect", + "talk-plugin-sort-most-replied", + "talk-plugin-sort-most-respected", + "talk-plugin-sort-newest", + "talk-plugin-sort-oldest", + "talk-plugin-viewing-options", + "talk-plugin-profile-settings" + ] +} +``` + +La parte que nos interesa es `client`, ya que estos son los plugins que van a utilizar los *slots* para meterse en distintas partes de Talk. + +Por ejemplo: Si observamos el plugin `talk-plugin-respect`, que está dentro de la carpeta `plugins` vamos a notar que el `client/index.js` luce asi: + +```js +import RespectButton from './RespectButton'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + commentReactions: [RespectButton], + }, +}; + +``` + +Dentro de la propiedad `slots` especificamos qué *slots* utilizará el plugin. Ahí estamos diciendo que el componente `RespectButton` se incrustará en el slot `commentReactions`. + +Los slots reciben un /Array/ de componentes. Es decir, puede ser uno o varios. + +### Anatomía del component Slot +En el core de Talk tenemos 32 slots declarados. El componente Slot tiene una propiedad `fill` donde establecemos el nombre del slot. Una declaración de un `` luce así: + +```js + +``` + +Esto probablemente no lo utilices para desarrollar plugins, pero sí para buscar donde se va a embeber tu plugin. + +### Lista de Slots + +* `adminCommentDetailArea` +* `adminCommentMoreDetails` +* `adminCommentLabels` +* `adminModerationSettings` +* `adminStreamSettings` +* `adminTechSettings` +* `adminCommentInfoBar` +* `adminCommentContent` +* `adminSideActions` +* `adminModeration` +* `adminModerationIndicator` + +* `commentInputDetailArea` +* `commentAvatar` +* `commentAuthorName` +* `commentAuthorTags` +* `commentTimestamp` +* `commentInfoBar` +* `commentContent` +* `commentReactions` +* `commentActions` +* `commentInputArea` + +* `draftArea` +* `streamSettings` +* `historyCommentTimestamp` +* `profileSections` +* `embed` +* `stream` +* `streamFilter` +* `streamQuestionArea` +* `login` +* `userProfile` +* `userDetailCommentContent` + +### Cómo sé donde colocar mi plugin? + +Lo primero que debemos pensar es a qué componente afectará la funcionalidad del plugin. Por ejemplo, si queremos agregar funcionalidad a todos los comentarios que se renderizan en la lista total de comentarios, esto tendrá que ver con el componente `Comment`. + +Los slots habilitados para agregar funcionalidad a los comentarios tienen un prefix `comment` como el `commentContent` o el `commentReactions`, que vimos previamente. + +### Deshabilitando plugins por configuración + +Los plugins se eliminan borrando su entrada en el `plugins.json`. Pero si por alguna razón necesitamos hacerlo por configuración, podemos hacerlo. + +Dentro de `views/article.ejs` embebemos Talk y notarás que podemos envialarle un objeto de configuración. Para deshabilitar visualmente a los plugins podemos utilizar la propiedad `disable_components` en `true`. y se utiliza de la siguiente forma: + +```js +plugins_config: { + 'talk-plugin-love': { + disable_components: true, + }, +} +``` + +### Enviando información a los slots / plugins + +Dentro de `plugins_config` podemos pasar todas las propiedades que queramos y los plugins las recibirán. + +Por ejemplo: Si enviamos esto +```js +plugins_config: { + test: 'data' +} +``` + +El plugin recibirá por props un objeto config con las propiedades que le pasamos. Haciendo un console.log de `this.props` verás: + +```js +config: {test: 'data'} +``` + +### Debug Slots + +Podés debuggear Slots / Plugins sólo con pasar la propiedad `debug` con valor `true`. + +```js +plugins_config: { + debug: true +} +``` + +Esto mostrará todos los slots disponibles en la UI y su nombre si pasas el mouse sobre ellos. + +### Slot ClassNames +Los slots autogeneran sus clases con el prefijo `talk-slot-` seguido del nombre del slot en kebab case. +Por ejemplo, la clase autogenerada del slot `commentContent` es `talk-slot-comment-content`. \ No newline at end of file diff --git a/docs/source/plugins/slots-and-plugins.md b/docs/source/plugins/slots-and-plugins.md new file mode 100644 index 000000000..d230ac0ba --- /dev/null +++ b/docs/source/plugins/slots-and-plugins.md @@ -0,0 +1,170 @@ +--- +title: Plugins Overview +permalink: /plugin-slots/ +toc: true +--- + +Plugins make use of "slots" in order to change Talk's interface. + +By default, Talk has various plugins provided by default. We can see this in `plugins.default.json`: + +```json +{ + "server": [ + "talk-plugin-auth", + "talk-plugin-featured-comments", + "talk-plugin-offtopic", + "talk-plugin-respect" + ], + "client": [ + "talk-plugin-auth", + "talk-plugin-author-menu", + "talk-plugin-comment-content", + "talk-plugin-featured-comments", + "talk-plugin-flag-details", + "talk-plugin-ignore-user", + "talk-plugin-member-since", + "talk-plugin-moderation-actions", + "talk-plugin-offtopic", + "talk-plugin-permalink", + "talk-plugin-respect", + "talk-plugin-sort-most-replied", + "talk-plugin-sort-most-respected", + "talk-plugin-sort-newest", + "talk-plugin-sort-oldest", + "talk-plugin-viewing-options", + "talk-plugin-profile-settings" + ] +} +``` + +Let's only focus on the plugins which are listed under `client` - these are the plugins that use *slots* to inject certain functionality into the Talk UI. + +For example, if we look at the Respect plugin (`talk-plugin-respect`), we can see its `client/index.js` looks like this: + + +```js +import RespectButton from './RespectButton'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + commentReactions: [RespectButton], + }, +}; + +``` + +Inside the `slots` property, we specify what *slots* the plugin will use. Above we are saying that the `RespectButton` component is being injected into the slot `commentReactions`. + +Slots can receive an Array of components, so we can use one plugin or many for one slot. + +### Anatomy of the Slot Component + +In Talk core, we have 32 slots available for us to use. The component `Slot` has a `fill` property where we establish the name of the slot. It looks like this: + + +```js + +``` + +You won't have to use this to build plugins, but it's helpful to find where to embed your plugin. + +### Slot List + +* `adminCommentDetailArea` +* `adminCommentMoreDetails` +* `adminCommentLabels` +* `adminModerationSettings` +* `adminStreamSettings` +* `adminTechSettings` +* `adminCommentInfoBar` +* `adminCommentContent` +* `adminSideActions` +* `adminModeration` +* `adminModerationIndicator` + +* `commentInputDetailArea` +* `commentAvatar` +* `commentAuthorName` +* `commentAuthorTags` +* `commentTimestamp` +* `commentInfoBar` +* `commentContent` +* `commentReactions` +* `commentActions` +* `commentInputArea` + +* `draftArea` +* `streamSettings` +* `historyCommentTimestamp` +* `profileSections` +* `embed` +* `stream` +* `streamFilter` +* `streamQuestionArea` +* `login` +* `userProfile` +* `userDetailCommentContent` + +### Where should I insert my plugin? + +The first thing we should consider is what components will be affected by our plugin's functionality. For example, if we want to add functionality to all the comments that are rendered in a total list of comments, we would use the component `Comment`. + +The slots that are able to add functionality to comments start with `comment`, like `commentContent`, or `commentReactions`, as you can see above. + +### Disabling plugins via `plugins_config` + +Typically, you will manage plugins via your `plugins.json` file. If you want to remove a plugin, you would simply delete it there. However, we can also do this directly with the `plugins_config`. + +Let's look at our example article, `views/article.ejs`. Here we see can we have the Talk embed, and within the embed, we can also send a configuration object. To disable a plugin visually, we can pass `true` to the property `disable_components`. Like so: + + +```js +plugins_config: { + 'talk-plugin-love': { + disable_components: true, + }, +} +``` + +### Sending information to slots and plugins + + +Inside our `plugins_config`, we can also send properities and our plugins will receive them. For example, if we send this: + +```js +plugins_config: { + test: 'data' +} +``` + +The plugin will receive a config object with the properties we've passed. If we do a `console.log` with `this.props`, we would see: + +```js +config: {test: 'data'} +``` + +### Debugging slots and plugins + + +You can debug slots and plugins simply by passing the `debug` property with value `true`: + + +```js +plugins_config: { + debug: true +} +``` + +This will turn on a visual aid to show you all of Talk's available slots and their names. Just move your mouse around! + +### Slot ClassNames + +Slots autogenerate their classes with the prefix `talk-slot`, followed by the name of the slot in kebab case. + +For example, the class autogenerated for the slot `commentContent` is `talk-slot-comment-content`. From 8fc69b05516884877f043a62b2d403a22c164f5f Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 20 Mar 2018 13:06:37 -0400 Subject: [PATCH 49/56] Translate Slot docs into English --- docs/_config.yml | 2 + docs/_docs/04-05-talk-slots-and-plugins-ES.md | 157 ------------------ 2 files changed, 2 insertions(+), 157 deletions(-) delete mode 100644 docs/_docs/04-05-talk-slots-and-plugins-ES.md diff --git a/docs/_config.yml b/docs/_config.yml index 2a6fa2725..f524fed0a 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -136,6 +136,8 @@ sidebar: url: /plugins-directory/ - title: Plugin Recipes url: /plugin-recipes/ + - title: Slots and Plugins + url: /slots-and-plugins/ - title: Tutorials children: - title: Creating a Basic Plugin diff --git a/docs/_docs/04-05-talk-slots-and-plugins-ES.md b/docs/_docs/04-05-talk-slots-and-plugins-ES.md deleted file mode 100644 index 846ccd033..000000000 --- a/docs/_docs/04-05-talk-slots-and-plugins-ES.md +++ /dev/null @@ -1,157 +0,0 @@ -# Plugins y Slots -Los *Slots* son una parte muy importante para crear plugins. Estos nos permiten inyectar nuestros plugins en la interfaz visual de Talk. - -Talk por defecto muestra varios plugins - Observemos el archivo `plugins.default.json` : - -```json -{ - "server": [ - "talk-plugin-auth", - "talk-plugin-featured-comments", - "talk-plugin-offtopic", - "talk-plugin-respect" - ], - "client": [ - "talk-plugin-auth", - "talk-plugin-author-menu", - "talk-plugin-comment-content", - "talk-plugin-featured-comments", - "talk-plugin-flag-details", - "talk-plugin-ignore-user", - "talk-plugin-member-since", - "talk-plugin-moderation-actions", - "talk-plugin-offtopic", - "talk-plugin-permalink", - "talk-plugin-respect", - "talk-plugin-sort-most-replied", - "talk-plugin-sort-most-respected", - "talk-plugin-sort-newest", - "talk-plugin-sort-oldest", - "talk-plugin-viewing-options", - "talk-plugin-profile-settings" - ] -} -``` - -La parte que nos interesa es `client`, ya que estos son los plugins que van a utilizar los *slots* para meterse en distintas partes de Talk. - -Por ejemplo: Si observamos el plugin `talk-plugin-respect`, que está dentro de la carpeta `plugins` vamos a notar que el `client/index.js` luce asi: - -```js -import RespectButton from './RespectButton'; -import translations from './translations.yml'; - -export default { - translations, - slots: { - commentReactions: [RespectButton], - }, -}; - -``` - -Dentro de la propiedad `slots` especificamos qué *slots* utilizará el plugin. Ahí estamos diciendo que el componente `RespectButton` se incrustará en el slot `commentReactions`. - -Los slots reciben un /Array/ de componentes. Es decir, puede ser uno o varios. - -### Anatomía del component Slot -En el core de Talk tenemos 32 slots declarados. El componente Slot tiene una propiedad `fill` donde establecemos el nombre del slot. Una declaración de un `` luce así: - -```js - -``` - -Esto probablemente no lo utilices para desarrollar plugins, pero sí para buscar donde se va a embeber tu plugin. - -### Lista de Slots - -* `adminCommentDetailArea` -* `adminCommentMoreDetails` -* `adminCommentLabels` -* `adminModerationSettings` -* `adminStreamSettings` -* `adminTechSettings` -* `adminCommentInfoBar` -* `adminCommentContent` -* `adminSideActions` -* `adminModeration` -* `adminModerationIndicator` - -* `commentInputDetailArea` -* `commentAvatar` -* `commentAuthorName` -* `commentAuthorTags` -* `commentTimestamp` -* `commentInfoBar` -* `commentContent` -* `commentReactions` -* `commentActions` -* `commentInputArea` - -* `draftArea` -* `streamSettings` -* `historyCommentTimestamp` -* `profileSections` -* `embed` -* `stream` -* `streamFilter` -* `streamQuestionArea` -* `login` -* `userProfile` -* `userDetailCommentContent` - -### Cómo sé donde colocar mi plugin? - -Lo primero que debemos pensar es a qué componente afectará la funcionalidad del plugin. Por ejemplo, si queremos agregar funcionalidad a todos los comentarios que se renderizan en la lista total de comentarios, esto tendrá que ver con el componente `Comment`. - -Los slots habilitados para agregar funcionalidad a los comentarios tienen un prefix `comment` como el `commentContent` o el `commentReactions`, que vimos previamente. - -### Deshabilitando plugins por configuración - -Los plugins se eliminan borrando su entrada en el `plugins.json`. Pero si por alguna razón necesitamos hacerlo por configuración, podemos hacerlo. - -Dentro de `views/article.ejs` embebemos Talk y notarás que podemos envialarle un objeto de configuración. Para deshabilitar visualmente a los plugins podemos utilizar la propiedad `disable_components` en `true`. y se utiliza de la siguiente forma: - -```js -plugins_config: { - 'talk-plugin-love': { - disable_components: true, - }, -} -``` - -### Enviando información a los slots / plugins - -Dentro de `plugins_config` podemos pasar todas las propiedades que queramos y los plugins las recibirán. - -Por ejemplo: Si enviamos esto -```js -plugins_config: { - test: 'data' -} -``` - -El plugin recibirá por props un objeto config con las propiedades que le pasamos. Haciendo un console.log de `this.props` verás: - -```js -config: {test: 'data'} -``` - -### Debug Slots - -Podés debuggear Slots / Plugins sólo con pasar la propiedad `debug` con valor `true`. - -```js -plugins_config: { - debug: true -} -``` - -Esto mostrará todos los slots disponibles en la UI y su nombre si pasas el mouse sobre ellos. - -### Slot ClassNames -Los slots autogeneran sus clases con el prefijo `talk-slot-` seguido del nombre del slot en kebab case. -Por ejemplo, la clase autogenerada del slot `commentContent` es `talk-slot-comment-content`. \ No newline at end of file From dbe80c40d7a370d257289983bec54968645a71d6 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 20 Mar 2018 18:25:04 +0100 Subject: [PATCH 50/56] Render when pluginsConfig changes --- client/coral-framework/hocs/withSlotElements.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/coral-framework/hocs/withSlotElements.js b/client/coral-framework/hocs/withSlotElements.js index a4b39c907..dc44f97db 100644 --- a/client/coral-framework/hocs/withSlotElements.js +++ b/client/coral-framework/hocs/withSlotElements.js @@ -83,6 +83,11 @@ const createHOC = ({ } if (changes.length === 1 && changes[0] === 'reduxState') { + // If pluginsConfig changed, we'll have to rerender everything. + if (this.props.reduxState.pluginsConfig !== next.reduxState.pluginsConfig) { + return true; + } + const prevChildrenKeys = this.getSlotElements(this.props).map( child => child.key ); From 84a79be9c1ed4edbb8413ce6dcff6765e8fcdcee Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 20 Mar 2018 18:31:09 +0100 Subject: [PATCH 51/56] Only use plugins_config in redux --- client/coral-framework/components/Slot.js | 1 + client/coral-framework/services/bootstrap.js | 12 +++++- client/coral-framework/services/plugins.js | 39 +------------------- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index a2f90161f..048c4da80 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -30,6 +30,7 @@ class Slot extends React.Component { className, `talk-slot-${kebabCase(fill)}` )} + data-slot-name={fill} > {children} diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index 878c2e303..82a55cdec 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -66,8 +66,16 @@ function initExternalConfig({ store, pym, inIframe }) { } return new Promise(resolve => { pym.sendMessage('getConfig'); - pym.onMessage('config', config => { - store.dispatch(mergeConfig(JSON.parse(config))); + pym.onMessage('config', rawConfig => { + const config = JSON.parse(rawConfig); + if (config.plugin_config) { + console.warn( + 'Deprecation Warning: `config.plugin_config` will be phased out soon, please replace `config.plugin_config with `config.plugins_config`' + ); + config.plugins_config = config.plugin_config; + delete config.plugin_config; + } + store.dispatch(mergeConfig(config)); resolve(); }); }); diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 3bc9bc5bc..bb16fc5e7 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -68,47 +68,14 @@ 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, - } - : {}; + const pluginsConfig = get(reduxState, 'config.plugins_config') || emptyConfig; return { ...props, - ...debugProps, config: pluginsConfig, ...(component.fragments ? pick(queryData, Object.keys(component.fragments)) @@ -159,9 +126,7 @@ class PluginsService { */ getSlotElements(slot, reduxState, props = {}, options = {}) { const pluginsConfig = - get(reduxState, 'config.plugins_config') || - get(reduxState, 'config.plugin_config') || - emptyConfig; + get(reduxState, 'config.plugins_config') || emptyConfig; const { size = 0 } = options; const { queryData, rest } = splitProps(props); From d98e64d6d7e15702a0c5073caf39a33e39361761 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 20 Mar 2018 18:45:56 +0100 Subject: [PATCH 52/56] Deprecation stuff --- client/coral-framework/services/bootstrap.js | 9 ++++++--- plugin-api/beta/client/selectors/index.js | 4 +--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index 82a55cdec..e0bfbb950 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -69,9 +69,12 @@ function initExternalConfig({ store, pym, inIframe }) { pym.onMessage('config', rawConfig => { const config = JSON.parse(rawConfig); if (config.plugin_config) { - console.warn( - 'Deprecation Warning: `config.plugin_config` will be phased out soon, please replace `config.plugin_config with `config.plugins_config`' - ); + // @Deprecated + if (process.env.NODE_ENV !== 'production') { + console.warn( + 'Deprecation Warning: `config.plugin_config` will be phased out soon, please replace `config.plugin_config with `config.plugins_config`' + ); + } config.plugins_config = config.plugin_config; delete config.plugin_config; } diff --git a/plugin-api/beta/client/selectors/index.js b/plugin-api/beta/client/selectors/index.js index 5ae41f6e0..c3a0b6821 100644 --- a/plugin-api/beta/client/selectors/index.js +++ b/plugin-api/beta/client/selectors/index.js @@ -1,3 +1 @@ -// @Deprecated plugin_config -export const pluginsConfigSelector = state => - state.config.plugins_config || state.config.plugin_config; +export const pluginsConfigSelector = state => state.config.plugins_config; From d08aaecd127ed790890970d107661e1421b6e7b0 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 20 Mar 2018 19:00:19 +0100 Subject: [PATCH 53/56] Rerender all Slot Components when config changes --- client/coral-framework/hocs/withSlotElements.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/hocs/withSlotElements.js b/client/coral-framework/hocs/withSlotElements.js index dc44f97db..9b432ade8 100644 --- a/client/coral-framework/hocs/withSlotElements.js +++ b/client/coral-framework/hocs/withSlotElements.js @@ -83,8 +83,10 @@ const createHOC = ({ } if (changes.length === 1 && changes[0] === 'reduxState') { - // If pluginsConfig changed, we'll have to rerender everything. - if (this.props.reduxState.pluginsConfig !== next.reduxState.pluginsConfig) { + // If config changed, we'll have to rerender everything. + // Should only happen during development as this is + // usually static. + if (this.props.reduxState.config !== next.reduxState.config) { return true; } From cd2426511c0c0c05b58ca4fe81a2bb64e84777b0 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 20 Mar 2018 14:08:23 -0400 Subject: [PATCH 54/56] Remove dupe files --- docs/source/plugins/slots-and-plugins-ES.md | 157 ------------------ docs/source/plugins/slots-and-plugins.md | 170 -------------------- 2 files changed, 327 deletions(-) delete mode 100644 docs/source/plugins/slots-and-plugins-ES.md delete mode 100644 docs/source/plugins/slots-and-plugins.md diff --git a/docs/source/plugins/slots-and-plugins-ES.md b/docs/source/plugins/slots-and-plugins-ES.md deleted file mode 100644 index 846ccd033..000000000 --- a/docs/source/plugins/slots-and-plugins-ES.md +++ /dev/null @@ -1,157 +0,0 @@ -# Plugins y Slots -Los *Slots* son una parte muy importante para crear plugins. Estos nos permiten inyectar nuestros plugins en la interfaz visual de Talk. - -Talk por defecto muestra varios plugins - Observemos el archivo `plugins.default.json` : - -```json -{ - "server": [ - "talk-plugin-auth", - "talk-plugin-featured-comments", - "talk-plugin-offtopic", - "talk-plugin-respect" - ], - "client": [ - "talk-plugin-auth", - "talk-plugin-author-menu", - "talk-plugin-comment-content", - "talk-plugin-featured-comments", - "talk-plugin-flag-details", - "talk-plugin-ignore-user", - "talk-plugin-member-since", - "talk-plugin-moderation-actions", - "talk-plugin-offtopic", - "talk-plugin-permalink", - "talk-plugin-respect", - "talk-plugin-sort-most-replied", - "talk-plugin-sort-most-respected", - "talk-plugin-sort-newest", - "talk-plugin-sort-oldest", - "talk-plugin-viewing-options", - "talk-plugin-profile-settings" - ] -} -``` - -La parte que nos interesa es `client`, ya que estos son los plugins que van a utilizar los *slots* para meterse en distintas partes de Talk. - -Por ejemplo: Si observamos el plugin `talk-plugin-respect`, que está dentro de la carpeta `plugins` vamos a notar que el `client/index.js` luce asi: - -```js -import RespectButton from './RespectButton'; -import translations from './translations.yml'; - -export default { - translations, - slots: { - commentReactions: [RespectButton], - }, -}; - -``` - -Dentro de la propiedad `slots` especificamos qué *slots* utilizará el plugin. Ahí estamos diciendo que el componente `RespectButton` se incrustará en el slot `commentReactions`. - -Los slots reciben un /Array/ de componentes. Es decir, puede ser uno o varios. - -### Anatomía del component Slot -En el core de Talk tenemos 32 slots declarados. El componente Slot tiene una propiedad `fill` donde establecemos el nombre del slot. Una declaración de un `` luce así: - -```js - -``` - -Esto probablemente no lo utilices para desarrollar plugins, pero sí para buscar donde se va a embeber tu plugin. - -### Lista de Slots - -* `adminCommentDetailArea` -* `adminCommentMoreDetails` -* `adminCommentLabels` -* `adminModerationSettings` -* `adminStreamSettings` -* `adminTechSettings` -* `adminCommentInfoBar` -* `adminCommentContent` -* `adminSideActions` -* `adminModeration` -* `adminModerationIndicator` - -* `commentInputDetailArea` -* `commentAvatar` -* `commentAuthorName` -* `commentAuthorTags` -* `commentTimestamp` -* `commentInfoBar` -* `commentContent` -* `commentReactions` -* `commentActions` -* `commentInputArea` - -* `draftArea` -* `streamSettings` -* `historyCommentTimestamp` -* `profileSections` -* `embed` -* `stream` -* `streamFilter` -* `streamQuestionArea` -* `login` -* `userProfile` -* `userDetailCommentContent` - -### Cómo sé donde colocar mi plugin? - -Lo primero que debemos pensar es a qué componente afectará la funcionalidad del plugin. Por ejemplo, si queremos agregar funcionalidad a todos los comentarios que se renderizan en la lista total de comentarios, esto tendrá que ver con el componente `Comment`. - -Los slots habilitados para agregar funcionalidad a los comentarios tienen un prefix `comment` como el `commentContent` o el `commentReactions`, que vimos previamente. - -### Deshabilitando plugins por configuración - -Los plugins se eliminan borrando su entrada en el `plugins.json`. Pero si por alguna razón necesitamos hacerlo por configuración, podemos hacerlo. - -Dentro de `views/article.ejs` embebemos Talk y notarás que podemos envialarle un objeto de configuración. Para deshabilitar visualmente a los plugins podemos utilizar la propiedad `disable_components` en `true`. y se utiliza de la siguiente forma: - -```js -plugins_config: { - 'talk-plugin-love': { - disable_components: true, - }, -} -``` - -### Enviando información a los slots / plugins - -Dentro de `plugins_config` podemos pasar todas las propiedades que queramos y los plugins las recibirán. - -Por ejemplo: Si enviamos esto -```js -plugins_config: { - test: 'data' -} -``` - -El plugin recibirá por props un objeto config con las propiedades que le pasamos. Haciendo un console.log de `this.props` verás: - -```js -config: {test: 'data'} -``` - -### Debug Slots - -Podés debuggear Slots / Plugins sólo con pasar la propiedad `debug` con valor `true`. - -```js -plugins_config: { - debug: true -} -``` - -Esto mostrará todos los slots disponibles en la UI y su nombre si pasas el mouse sobre ellos. - -### Slot ClassNames -Los slots autogeneran sus clases con el prefijo `talk-slot-` seguido del nombre del slot en kebab case. -Por ejemplo, la clase autogenerada del slot `commentContent` es `talk-slot-comment-content`. \ No newline at end of file diff --git a/docs/source/plugins/slots-and-plugins.md b/docs/source/plugins/slots-and-plugins.md deleted file mode 100644 index d230ac0ba..000000000 --- a/docs/source/plugins/slots-and-plugins.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: Plugins Overview -permalink: /plugin-slots/ -toc: true ---- - -Plugins make use of "slots" in order to change Talk's interface. - -By default, Talk has various plugins provided by default. We can see this in `plugins.default.json`: - -```json -{ - "server": [ - "talk-plugin-auth", - "talk-plugin-featured-comments", - "talk-plugin-offtopic", - "talk-plugin-respect" - ], - "client": [ - "talk-plugin-auth", - "talk-plugin-author-menu", - "talk-plugin-comment-content", - "talk-plugin-featured-comments", - "talk-plugin-flag-details", - "talk-plugin-ignore-user", - "talk-plugin-member-since", - "talk-plugin-moderation-actions", - "talk-plugin-offtopic", - "talk-plugin-permalink", - "talk-plugin-respect", - "talk-plugin-sort-most-replied", - "talk-plugin-sort-most-respected", - "talk-plugin-sort-newest", - "talk-plugin-sort-oldest", - "talk-plugin-viewing-options", - "talk-plugin-profile-settings" - ] -} -``` - -Let's only focus on the plugins which are listed under `client` - these are the plugins that use *slots* to inject certain functionality into the Talk UI. - -For example, if we look at the Respect plugin (`talk-plugin-respect`), we can see its `client/index.js` looks like this: - - -```js -import RespectButton from './RespectButton'; -import translations from './translations.yml'; - -export default { - translations, - slots: { - commentReactions: [RespectButton], - }, -}; - -``` - -Inside the `slots` property, we specify what *slots* the plugin will use. Above we are saying that the `RespectButton` component is being injected into the slot `commentReactions`. - -Slots can receive an Array of components, so we can use one plugin or many for one slot. - -### Anatomy of the Slot Component - -In Talk core, we have 32 slots available for us to use. The component `Slot` has a `fill` property where we establish the name of the slot. It looks like this: - - -```js - -``` - -You won't have to use this to build plugins, but it's helpful to find where to embed your plugin. - -### Slot List - -* `adminCommentDetailArea` -* `adminCommentMoreDetails` -* `adminCommentLabels` -* `adminModerationSettings` -* `adminStreamSettings` -* `adminTechSettings` -* `adminCommentInfoBar` -* `adminCommentContent` -* `adminSideActions` -* `adminModeration` -* `adminModerationIndicator` - -* `commentInputDetailArea` -* `commentAvatar` -* `commentAuthorName` -* `commentAuthorTags` -* `commentTimestamp` -* `commentInfoBar` -* `commentContent` -* `commentReactions` -* `commentActions` -* `commentInputArea` - -* `draftArea` -* `streamSettings` -* `historyCommentTimestamp` -* `profileSections` -* `embed` -* `stream` -* `streamFilter` -* `streamQuestionArea` -* `login` -* `userProfile` -* `userDetailCommentContent` - -### Where should I insert my plugin? - -The first thing we should consider is what components will be affected by our plugin's functionality. For example, if we want to add functionality to all the comments that are rendered in a total list of comments, we would use the component `Comment`. - -The slots that are able to add functionality to comments start with `comment`, like `commentContent`, or `commentReactions`, as you can see above. - -### Disabling plugins via `plugins_config` - -Typically, you will manage plugins via your `plugins.json` file. If you want to remove a plugin, you would simply delete it there. However, we can also do this directly with the `plugins_config`. - -Let's look at our example article, `views/article.ejs`. Here we see can we have the Talk embed, and within the embed, we can also send a configuration object. To disable a plugin visually, we can pass `true` to the property `disable_components`. Like so: - - -```js -plugins_config: { - 'talk-plugin-love': { - disable_components: true, - }, -} -``` - -### Sending information to slots and plugins - - -Inside our `plugins_config`, we can also send properities and our plugins will receive them. For example, if we send this: - -```js -plugins_config: { - test: 'data' -} -``` - -The plugin will receive a config object with the properties we've passed. If we do a `console.log` with `this.props`, we would see: - -```js -config: {test: 'data'} -``` - -### Debugging slots and plugins - - -You can debug slots and plugins simply by passing the `debug` property with value `true`: - - -```js -plugins_config: { - debug: true -} -``` - -This will turn on a visual aid to show you all of Talk's available slots and their names. Just move your mouse around! - -### Slot ClassNames - -Slots autogenerate their classes with the prefix `talk-slot`, followed by the name of the slot in kebab case. - -For example, the class autogenerated for the slot `commentContent` is `talk-slot-comment-content`. From 5cd2cce7d84a3c04b9002842ad77c772385c217e Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 20 Mar 2018 15:59:48 -0300 Subject: [PATCH 55/56] removing pell --- .gitignore | 1 - plugins/talk-plugin-rich-text-pell/README.md | 49 -------- .../client/.eslintrc.json | 3 - .../client/components/CommentContent.js | 23 ---- .../client/components/Editor.css | 41 ------- .../client/components/Editor.js | 108 ------------------ .../client/containers/CommentContent.js | 12 -- .../client/containers/Editor.js | 12 -- .../client/index.js | 70 ------------ .../client/utils.js | 20 ---- plugins/talk-plugin-rich-text-pell/index.js | 1 - .../talk-plugin-rich-text-pell/package.json | 12 -- 12 files changed, 352 deletions(-) delete mode 100644 plugins/talk-plugin-rich-text-pell/README.md delete mode 100644 plugins/talk-plugin-rich-text-pell/client/.eslintrc.json delete mode 100644 plugins/talk-plugin-rich-text-pell/client/components/CommentContent.js delete mode 100644 plugins/talk-plugin-rich-text-pell/client/components/Editor.css delete mode 100644 plugins/talk-plugin-rich-text-pell/client/components/Editor.js delete mode 100644 plugins/talk-plugin-rich-text-pell/client/containers/CommentContent.js delete mode 100644 plugins/talk-plugin-rich-text-pell/client/containers/Editor.js delete mode 100644 plugins/talk-plugin-rich-text-pell/client/index.js delete mode 100644 plugins/talk-plugin-rich-text-pell/client/utils.js delete mode 100644 plugins/talk-plugin-rich-text-pell/index.js delete mode 100644 plugins/talk-plugin-rich-text-pell/package.json diff --git a/.gitignore b/.gitignore index 7c0007dc9..6f3ac70b8 100644 --- a/.gitignore +++ b/.gitignore @@ -62,7 +62,6 @@ plugins/* !plugins/talk-plugin-toxic-comments !plugins/talk-plugin-viewing-options !plugins/talk-plugin-rich-text -!plugins/talk-plugin-rich-text-pell **/node_modules/* yarn-error.log diff --git a/plugins/talk-plugin-rich-text-pell/README.md b/plugins/talk-plugin-rich-text-pell/README.md deleted file mode 100644 index da70d48d0..000000000 --- a/plugins/talk-plugin-rich-text-pell/README.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: talk-plugin-rich-text-pell -permalink: /plugin/talk-plugin-rich-text-pell/ -layout: plugin -plugin: - name: talk-plugin-rich-text-pell - depends: - - name: talk-plugin-rich-text - provides: - - Client ---- - -Enables rich text support client-side by using [Pell](https://github.com/jaredreich/pell). - -## Installation - -Add `"talk-plugin-rich-text-pell"` to the `plugins.json` in your Talk -installation. Remember to add this in the `client` property since this plugin -only covers the client side. To add server support, please use -[talk-plugin-rich-text](/talk/plugin/talk-plugin-rich-text). - -_Note: Ensure that you don't have any other plugins utilizing the -`commentContent` slot, as it would result in duplicate comments._ - -## How does this work? - -This plugin contains 2 important components: - -- The Editor (`./components/Editor.js`) -- The Comment Content Renderer (`./components/CommentContent.js`) - -The editor component contains the rich text editor. For this particular plugin -we chose [Pell](https://github.com/jaredreich/pell). Pell is the simplest and -smallest WYSIWYG text editor with no dependencies that we could find. - -If you check our `index.js` you will notice that we inject this editor in the -`commentBox` slot. We do this to replace the core comment box with this one. - -Now, in order to render the new styled comments we need a comment renderer. For -this task we will have to replace our core comment renderer by using the -`commentContent` slot. - -If you are not familiar with GraphQL `client/index.js` will look complicated, -but fear not! With those functions we specify what to expect from the server -schema, how to perform optimistic updates and how keep the client store updated -with the latest changes. - -We encourage you to see the files and check how easy is to build plugins! If you -have any feedback, please let us know. diff --git a/plugins/talk-plugin-rich-text-pell/client/.eslintrc.json b/plugins/talk-plugin-rich-text-pell/client/.eslintrc.json deleted file mode 100644 index c8a6db18a..000000000 --- a/plugins/talk-plugin-rich-text-pell/client/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "@coralproject/eslint-config-talk/client" -} diff --git a/plugins/talk-plugin-rich-text-pell/client/components/CommentContent.js b/plugins/talk-plugin-rich-text-pell/client/components/CommentContent.js deleted file mode 100644 index 7e4a202bc..000000000 --- a/plugins/talk-plugin-rich-text-pell/client/components/CommentContent.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { pluginName } from '../../package.json'; - -class CommentContent extends React.Component { - render() { - const { comment } = this.props; - return comment.richTextBody ? ( -
    - ) : ( -
    {comment.body}
    - ); - } -} - -CommentContent.propTypes = { - comment: PropTypes.object.isRequired, -}; - -export default CommentContent; diff --git a/plugins/talk-plugin-rich-text-pell/client/components/Editor.css b/plugins/talk-plugin-rich-text-pell/client/components/Editor.css deleted file mode 100644 index 4561e7c6b..000000000 --- a/plugins/talk-plugin-rich-text-pell/client/components/Editor.css +++ /dev/null @@ -1,41 +0,0 @@ -.content { - background: #fff; - border: solid 1px #bbb; - min-height: 120px; - box-sizing: border-box; - outline: 0; - overflow-y: auto; - width: 100%; - padding: 10px; - font-style: unset; -} - -.button > i { - vertical-align: middle; -} - -.button { - background-color: transparent; - padding: 3px; - border: none; - color: #4e4e4e; - margin-right: 3px; -} - -.button:hover{ - cursor: pointer; - border-radius: 3px; - background-color: #eae8e8; -} - -.actionBar { - user-select: none; - padding: 5px 10px; - border-top: 1px solid #bbb; - border-left: 1px solid #bbb; - border-right: 1px solid #bbb; -} - -.container { - box-sizing: border-box; -} \ No newline at end of file diff --git a/plugins/talk-plugin-rich-text-pell/client/components/Editor.js b/plugins/talk-plugin-rich-text-pell/client/components/Editor.js deleted file mode 100644 index c129e146d..000000000 --- a/plugins/talk-plugin-rich-text-pell/client/components/Editor.js +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { init } from 'pell'; -import styles from './Editor.css'; -import cn from 'classnames'; -import { pluginName } from '../../package.json'; -import { htmlNormalizer } from '../utils'; - -class Editor extends React.Component { - ref = null; - - handleRef = ref => (this.ref = ref); - - componentDidMount() { - const { onChange, actions, classNames, isReply } = this.props; - - init({ - element: this.ref, - onChange: richTextBody => { - // We want to save the original comment body - const originalBody = this.ref.childNodes[1].innerText; - onChange(originalBody, { richTextBody: htmlNormalizer(richTextBody) }); - }, - actions, - classes: { - actionbar: cn( - styles.actionBar, - classNames.actionbar, - `${pluginName}-action-bar` - ), - content: cn( - styles.content, - classNames.content, - `${pluginName}-content` - ), - button: cn(styles.button, classNames.button, `${pluginName}-button`), - }, - }); - - // To edit comments and have the previous html comment - if (this.props.comment && this.props.comment.richTextBody && !isReply) { - this.ref.content.innerHTML = this.props.comment.richTextBody; - } - - if (this.props.registerHook) { - this.clearInputHook = this.props.registerHook( - 'postSubmit', - (res, handleBodyChange) => { - this.ref.content.innerHTML = ''; - handleBodyChange('', { richTextBody: '' }); - } - ); - } - } - - componentWillUnmount() { - this.props.unregisterHook(this.clearInputHook); - } - - render() { - const { id, classNames } = this.props; - - return ( -
    - ); - } -} - -Editor.defaultProps = { - defaultContent: '', - styleWithCSS: false, - actions: [ - { name: 'bold', icon: 'format_bold' }, - { name: 'italic', icon: 'format_italic' }, - { name: 'quote', icon: 'format_quote' }, - ], - classNames: { - button: '', - content: '', - actionbar: '', - container: '', - }, -}; - -Editor.propTypes = { - id: PropTypes.string, - value: PropTypes.string, - placeholder: PropTypes.string, - onChange: PropTypes.func, - disabled: PropTypes.bool, - rows: PropTypes.number, - comment: PropTypes.object, - classNames: PropTypes.object, - actions: PropTypes.array, - registerHook: PropTypes.func, - unregisterHook: PropTypes.func, - isReply: PropTypes.bool, -}; - -export default Editor; diff --git a/plugins/talk-plugin-rich-text-pell/client/containers/CommentContent.js b/plugins/talk-plugin-rich-text-pell/client/containers/CommentContent.js deleted file mode 100644 index 8bd36da23..000000000 --- a/plugins/talk-plugin-rich-text-pell/client/containers/CommentContent.js +++ /dev/null @@ -1,12 +0,0 @@ -import { gql } from 'react-apollo'; -import { withFragments } from 'plugin-api/beta/client/hocs'; -import CommentContent from '../components/CommentContent'; - -export default withFragments({ - comment: gql` - fragment TalkPluginRTE_CommentContent_comment on Comment { - body - richTextBody - } - `, -})(CommentContent); diff --git a/plugins/talk-plugin-rich-text-pell/client/containers/Editor.js b/plugins/talk-plugin-rich-text-pell/client/containers/Editor.js deleted file mode 100644 index 2bdc3490f..000000000 --- a/plugins/talk-plugin-rich-text-pell/client/containers/Editor.js +++ /dev/null @@ -1,12 +0,0 @@ -import { gql } from 'react-apollo'; -import { withFragments } from 'plugin-api/beta/client/hocs'; -import Editor from '../components/Editor'; - -export default withFragments({ - comment: gql` - fragment TalkPluginRTE_Editor_comment on Comment { - body - richTextBody - } - `, -})(Editor); diff --git a/plugins/talk-plugin-rich-text-pell/client/index.js b/plugins/talk-plugin-rich-text-pell/client/index.js deleted file mode 100644 index cf9eb11cd..000000000 --- a/plugins/talk-plugin-rich-text-pell/client/index.js +++ /dev/null @@ -1,70 +0,0 @@ -import Editor from './containers/Editor'; -import CommentContent from './containers/CommentContent'; -import { gql } from 'react-apollo'; - -export default { - slots: { - draftArea: [Editor], - commentContent: [CommentContent], - adminCommentContent: [CommentContent], - userDetailCommentContent: [CommentContent], - }, - fragments: { - CreateCommentResponse: gql` - fragment TalkRTE_CreateCommentResponse on CreateCommentResponse { - comment { - richTextBody - } - } - `, - EditCommentResponse: gql` - fragment TalkRTE_EditCommentResponse on EditCommentResponse { - comment { - richTextBody - } - } - `, - }, - mutations: { - PostComment: ({ variables: { input } }) => { - return { - optimisticResponse: { - createComment: { - comment: { - richTextBody: input.richTextBody, - }, - }, - }, - }; - }, - EditComment: ({ variables: { id, edit } }) => { - return { - optimisticResponse: { - editComment: { - comment: { - richTextBody: edit.richTextBody, - }, - }, - }, - update: proxy => { - const editCommentFragment = gql` - fragment Talk_EditComment on Comment { - richTextBody - } - `; - - const fragmentId = `Comment_${id}`; - - proxy.writeFragment({ - fragment: editCommentFragment, - id: fragmentId, - data: { - __typename: 'Comment', - richTextBody: edit.richTextBody, - }, - }); - }, - }; - }, - }, -}; diff --git a/plugins/talk-plugin-rich-text-pell/client/utils.js b/plugins/talk-plugin-rich-text-pell/client/utils.js deleted file mode 100644 index 6155fbfb2..000000000 --- a/plugins/talk-plugin-rich-text-pell/client/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -export function htmlNormalizer(htmlInput) { - let str = htmlInput; - // We are normalizing the input from contenteditable of each browser, also removing unnecesary html tags - // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content#Differences_in_markup_generation - - // Old browsers uses `p` normalize to `div` instead. - str = str - .replace(/

    /g, '

    ') // IE and old browsers outputs

    instead of

    s - .replace(/<\/p>/g, '
    '); // IE and old browsers outputs

    instead of

    s - - // Remove first opening tag, otherwise - // with the following transformation below - // we might add an unintended first empty line. - if (str.startsWith('
    ')) { - str = str.replace('
    ', ''); - } - - // Normalize
    s to
    . - return str.replace(/
    /g, '
    ').replace(/<\/div>/g, ''); -} diff --git a/plugins/talk-plugin-rich-text-pell/index.js b/plugins/talk-plugin-rich-text-pell/index.js deleted file mode 100644 index f053ebf79..000000000 --- a/plugins/talk-plugin-rich-text-pell/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/plugins/talk-plugin-rich-text-pell/package.json b/plugins/talk-plugin-rich-text-pell/package.json deleted file mode 100644 index 65543a89a..000000000 --- a/plugins/talk-plugin-rich-text-pell/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@coralproject/talk-plugin-rich-text-pell", - "pluginName": "talk-plugin-rich-text-pell", - "version": "0.0.1", - "description": "Pell's Rich Text Editor for Talk", - "main": "index.js", - "author": "The Coral Project Team ", - "license": "Apache-2.0", - "dependencies": { - "pell": "^1.0.1" - } -} From db64f21beb7489f2e21b3a32c4f83eb2529c234b Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 20 Mar 2018 16:09:33 -0400 Subject: [PATCH 56/56] Typo --- docs/source/04-06-slots-and-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/04-06-slots-and-plugins.md b/docs/source/04-06-slots-and-plugins.md index 86e3addeb..907c85862 100644 --- a/docs/source/04-06-slots-and-plugins.md +++ b/docs/source/04-06-slots-and-plugins.md @@ -120,7 +120,7 @@ The slots that are able to add functionality to comments start with `comment`, l Typically, you will manage plugins via your `plugins.json` file. If you want to remove a plugin, you would simply delete it there. However, we can also do this directly with the `plugins_config`. -Let's look at our example article, `views/article.ejs`. Here we see can we have the Talk embed, and within the embed, we can also send a configuration object. To disable a plugin visually, we can pass `true` to the property `disable_components`. Like so: +Let's look at our example article, `views/article.ejs`. Here we see we have the Talk embed, and within the embed, we can also send a configuration object. To disable a plugin visually, we can pass `true` to the property `disable_components`. Like so: ```js