diff --git a/.eslintignore b/.eslintignore index 2702e0a4d..3811f290c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,5 +13,6 @@ plugins/* !plugins/talk-plugin-comment-content !plugins/talk-plugin-permalink !plugins/talk-plugin-featured-comments +!plugins/talk-plugin-sort-oldest node_modules diff --git a/.gitignore b/.gitignore index 882c24883..3f5cbc085 100644 --- a/.gitignore +++ b/.gitignore @@ -29,5 +29,6 @@ plugins/* !plugins/talk-plugin-comment-content !plugins/talk-plugin-permalink !plugins/talk-plugin-featured-comments +!plugins/talk-plugin-sort-oldest **/node_modules/* diff --git a/client/coral-embed-stream/src/actions/stream.js b/client/coral-embed-stream/src/actions/stream.js index c20760259..d7301f8a1 100644 --- a/client/coral-embed-stream/src/actions/stream.js +++ b/client/coral-embed-stream/src/actions/stream.js @@ -5,6 +5,8 @@ import queryString from 'query-string'; export const setActiveReplyBox = (id) => ({type: actions.SET_ACTIVE_REPLY_BOX, id}); +export const setSort = ({order, by}) => ({type: actions.SET_SORT, order, by}); + export const viewAllComments = () => { const search = queryString.stringify({ diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index b37d232f7..544515da4 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -13,7 +13,7 @@ import t, {timeago} from 'coral-framework/services/i18n'; import CommentBox from 'talk-plugin-commentbox/CommentBox'; import QuestionBox from 'talk-plugin-questionbox/QuestionBox'; import {isCommentActive} from 'coral-framework/utils'; -import {Button, Tab, TabCount, TabPane} from 'coral-ui'; +import {Spinner, Button, Tab, TabCount, TabPane} from 'coral-ui'; import cn from 'classnames'; import {getTopLevelParent, attachCommentToParent} from '../graphql/utils'; @@ -23,6 +23,8 @@ import StreamTabPanel from '../containers/StreamTabPanel'; import styles from './Stream.css'; +const SpinnerWhenLoading = ({loading, children}) => loading ? :
{children}
; + class Stream extends React.Component { constructor(props) { @@ -49,15 +51,14 @@ class Stream extends React.Component { ); }; - render() { + renderHighlightedComment() { const { data, root, activeReplyBox, setActiveReplyBox, - appendItemArray, commentClassNames, - root: {asset, asset: {comment, comments, totalCommentCount}}, + root: {asset, asset: {comment}}, postComment, addNotification, editComment, @@ -65,23 +66,15 @@ class Stream extends React.Component { postDontAgree, deleteAction, showSignInDialog, - updateItem, ignoreUser, - activeStreamTab, - setActiveStreamTab, loadNewReplies, - loadMoreComments, - viewAllComments, - auth: {loggedIn, user}, - editName, + auth: {user}, emit, } = this.props; - const {keepCommentBox} = this.state; - const open = !asset.isClosed; // even though the permalinked comment is the highlighted one, we're displaying its parent + replies - let highlightedComment = comment && getTopLevelParent(comment); - if (highlightedComment) { + let topLevelComment = getTopLevelParent(comment); + if (topLevelComment) { // Inactive comments can be viewed by moderators and admins (e.g. using permalinks). const isInactive = !isCommentActive(comment.status); @@ -89,10 +82,152 @@ class Stream extends React.Component { // the highlighted comment is not active and as such not in the replies, so we // attach it to the right parent. - highlightedComment = attachCommentToParent(highlightedComment, comment); + topLevelComment = attachCommentToParent(topLevelComment, comment); } } + return ( +
+ +
+ ); + } + + renderTabPanel() { + const { + data, + root, + activeReplyBox, + setActiveReplyBox, + commentClassNames, + root: {asset, asset: {comments, totalCommentCount}}, + postComment, + addNotification, + editComment, + postFlag, + postDontAgree, + deleteAction, + showSignInDialog, + ignoreUser, + activeStreamTab, + setActiveStreamTab, + loadNewReplies, + loadMoreComments, + auth: {user}, + emit, + sortOrder, + sortBy, + } = this.props; + + const slotProps = {data}; + const slotQueryData = {root, asset}; + + // `key` of `StreamTabPanel` depends on sorting so that we always reset + // the state when changing sorting. + return ( +
+
+ +
+ + All Comments {totalCommentCount} + + } + appendTabPanes={ + + + + } + sub + /> +
+ ); + } + + render() { + const { + data, + root, + appendItemArray, + root: {asset, asset: {comment: highlightedComment, comments}}, + postComment, + addNotification, + updateItem, + viewAllComments, + auth: {loggedIn, user}, + editName, + loading, + } = this.props; + const {keepCommentBox} = this.state; + const open = !asset.isClosed; + const banned = user && user.status === 'BANNED'; const temporarilySuspended = user && @@ -103,7 +238,7 @@ class Stream extends React.Component { const slotProps = {data}; const slotQueryData = {root, asset}; - if (!comment && !comments) { + if (!highlightedComment && !comments) { console.error('Talk: No comments came back from the graph given that query. Please, check the query params.'); return ; } @@ -111,7 +246,7 @@ class Stream extends React.Component { return (
- {comment && + {highlightedComment &&
); } diff --git a/client/coral-embed-stream/src/constants/stream.js b/client/coral-embed-stream/src/constants/stream.js index 7ac9427cf..b4deb24e1 100644 --- a/client/coral-embed-stream/src/constants/stream.js +++ b/client/coral-embed-stream/src/constants/stream.js @@ -6,3 +6,4 @@ export const ADD_COMMENT_CLASSNAME = 'ADD_COMMENT_CLASSNAME'; export const REMOVE_COMMENT_CLASSNAME = 'REMOVE_COMMENT_CLASSNAME'; export const THREADING_LEVEL = process.env.TALK_THREADING_LEVEL; export const SET_ACTIVE_TAB = 'CORAL_STREAM_SET_ACTIVE_TAB'; +export const SET_SORT = 'CORAL_STREAM_SET_SORT'; diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js index 5a0ea534d..289d370a1 100644 --- a/client/coral-embed-stream/src/containers/Embed.js +++ b/client/coral-embed-stream/src/containers/Embed.js @@ -158,7 +158,7 @@ const EMBED_QUERY = gql` $hasComment: Boolean!, $excludeIgnored: Boolean, $sortBy: SORT_COMMENTS_BY!, - $sort: SORT_ORDER!, + $sortOrder: SORT_ORDER!, ) { me { id @@ -171,7 +171,7 @@ const EMBED_QUERY = gql` `; export const withEmbedQuery = withQuery(EMBED_QUERY, { - options: ({auth, commentId, assetId, assetUrl, sortBy, sort}) => ({ + options: ({auth, commentId, assetId, assetUrl, sortBy, sortOrder}) => ({ variables: { assetId, assetUrl, @@ -179,7 +179,7 @@ export const withEmbedQuery = withQuery(EMBED_QUERY, { hasComment: commentId !== '', excludeIgnored: Boolean(auth && auth.user && auth.user.id), sortBy, - sort, + sortOrder, }, }), }); @@ -191,7 +191,7 @@ const mapStateToProps = (state) => ({ assetUrl: state.stream.assetUrl, activeTab: state.embed.activeTab, config: state.config, - sort: state.stream.sort, + sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, }); diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index 4720a6889..b98d7e9cf 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -102,7 +102,7 @@ class StreamContainer extends React.Component { cursor: comment.replies.endCursor, parent_id, asset_id: this.props.root.asset.id, - sort: 'ASC', + sortOrder: 'ASC', excludeIgnored: this.props.data.variables.excludeIgnored, }, updateQuery: (prev, {fetchMoreResult:{comments}}) => { @@ -119,7 +119,7 @@ class StreamContainer extends React.Component { cursor: this.props.root.asset.comments.endCursor, parent_id: null, asset_id: this.props.root.asset.id, - sort: this.props.data.variables.sort, + sortOrder: this.props.data.variables.sortOrder, sortBy: this.props.data.variables.sortBy, excludeIgnored: this.props.data.variables.excludeIgnored, }, @@ -141,24 +141,43 @@ class StreamContainer extends React.Component { clearInterval(this.countPoll); } + componentWillReceiveProps(nextProps) { + if (nextProps.sortBy === 'CREATED_AT' && nextProps.sortOrder === 'DESC') { + + // When switching to 'Newest first' we refetch and subscribe so that we always have the newest comments. + if (this.props.sortOrder !== nextProps.sortOrder || this.props.sortBy !== nextProps.sortBy) { + nextProps.data.refetch(); + this.subscribeToUpdates(); + } + } else { + + // TODO: only unsubscribe from posting comments. + // We only subscribe to new comments during 'Newest first'. + this.unsubscribe(); + } + } + userIsDegraged({auth: {user}} = this.props) { return !can(user, 'INTERACT_WITH_COMMUNITY'); } render() { - if (this.props.refetching - || !this.props.root.asset + if (!this.props.root.asset || !this.props.root.asset.comment && !this.props.root.asset.comments ) { return ; } + + const streamLoading = this.props.refetching || this.props.data.loading; + return ; } } @@ -209,7 +228,7 @@ const LOAD_MORE_QUERY = gql` $cursor: Cursor $parent_id: ID $asset_id: ID - $sort: SORT_ORDER + $sortOrder: SORT_ORDER $sortBy: SORT_COMMENTS_BY = CREATED_AT $excludeIgnored: Boolean ) { @@ -219,7 +238,7 @@ const LOAD_MORE_QUERY = gql` cursor: $cursor parent_id: $parent_id asset_id: $asset_id - sort: $sort + sort: $sortOrder sortBy: $sortBy excludeIgnored: $excludeIgnored } @@ -274,7 +293,7 @@ const fragments = { } commentCount @skip(if: $hasComment) totalCommentCount @skip(if: $hasComment) - comments(query: {limit: 10, excludeIgnored: $excludeIgnored, sort: $sort, sortBy: $sortBy}) @skip(if: $hasComment) { + comments(query: {limit: 10, excludeIgnored: $excludeIgnored, sort: $sortOrder, sortBy: $sortBy}) @skip(if: $hasComment) { nodes { ...CoralEmbedStream_Stream_comment } @@ -317,6 +336,8 @@ const mapStateToProps = (state) => ({ previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, pluginConfig: state.config.plugin_config, + sortOrder: state.stream.sortOrder, + sortBy: state.stream.sortBy, }); const mapDispatchToProps = (dispatch) => diff --git a/client/coral-embed-stream/src/reducers/stream.js b/client/coral-embed-stream/src/reducers/stream.js index 02d6933ba..dd1817769 100644 --- a/client/coral-embed-stream/src/reducers/stream.js +++ b/client/coral-embed-stream/src/reducers/stream.js @@ -24,7 +24,7 @@ const initialState = { activeTab: process.env.TALK_DEFAULT_STREAM_TAB, previousTab: '', sortBy: 'CREATED_AT', - sort: 'DESC', + sortOrder: 'DESC', }; export default function stream(state = initialState, action) { @@ -68,6 +68,12 @@ export default function stream(state = initialState, action) { ...state.commentClassNames.slice(action.idx + 1) ] }; + case actions.SET_SORT : + return { + ...state, + sortOrder: action.order ? action.order : state.order, + sortBy: action.by ? action.by : state.by, + }; default: return state; } diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index c0cca143c..c4d680afb 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -6,6 +6,7 @@ import {getSlotElements, getSlotComponentProps} from 'coral-framework/helpers/pl import omit from 'lodash/omit'; import isEqual from 'lodash/isEqual'; import {getShallowChanges} from 'coral-framework/utils'; +import PropTypes from 'prop-types'; const emptyConfig = {}; @@ -25,7 +26,18 @@ class Slot extends React.Component { return changes.length !== 0; } - getSlotProps({fill: _a, inline: _b, className: _c, reduxState: _d, defaultComponent_: _e, queryData: _f, ...rest} = this.props) { + getSlotProps(props = this.props) { + const { + fill: _a, + inline: _b, + className: _c, + reduxState: _d, + defaultComponent_: _e, + queryData: _f, + childFactory: _g, + component: _h, + ...rest + } = props; return rest; } @@ -34,26 +46,60 @@ class Slot extends React.Component { } render() { - const {inline = false, className, reduxState, defaultComponent: DefaultComponent, queryData} = this.props; + const { + inline = false, + className, + reduxState, + component: Component, + childFactory, + defaultComponent: DefaultComponent, + queryData, + } = this.props; let children = this.getChildren(); const pluginConfig = reduxState.config.pluginConfig || emptyConfig; if (children.length === 0 && DefaultComponent) { children = ; } + if (childFactory) { + children = children.map(childFactory); + } + return ( -
+ {children} -
+ ); } } +Slot.defaultProps = { + component: 'div', +}; + Slot.propTypes = { - fill: React.PropTypes.string.isRequired, + fill: PropTypes.string.isRequired, + + /** + * You may specify the component to use as the root wrapper. + * Defaults to 'div'. + */ + component: PropTypes.any, // props coming from graphql must be passed through this property. - queryData: React.PropTypes.object, + queryData: 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 + * child the element has already been removed and not accessible to the consumer. + * + * If you do need to update a child as it leaves you can provide a `childFactory` + * to wrap every child, even the ones that are leaving. + * + * @type Function(child: ReactElement) -> ReactElement + */ + childFactory: PropTypes.func, }; const mapStateToProps = (state) => ({ diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index 634b6661f..26320df97 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -2,6 +2,7 @@ import {gql} from 'react-apollo'; import t from 'coral-framework/services/i18n'; import union from 'lodash/union'; import {capitalize} from 'coral-framework/helpers/strings'; +export * from 'coral-framework/helpers/strings'; export const getTotalActionCount = (type, comment) => { return comment.action_summaries diff --git a/plugin-api/beta/client/actions/stream.js b/plugin-api/beta/client/actions/stream.js new file mode 100644 index 000000000..8171357ba --- /dev/null +++ b/plugin-api/beta/client/actions/stream.js @@ -0,0 +1 @@ +export {setSort} from 'coral-embed-stream/src/actions/stream'; diff --git a/plugin-api/beta/client/components/SortOption.css b/plugin-api/beta/client/components/SortOption.css new file mode 100644 index 000000000..f2df56082 --- /dev/null +++ b/plugin-api/beta/client/components/SortOption.css @@ -0,0 +1,8 @@ +.input { + cursor: pointer; +} + +.label { + cursor: pointer; + padding-left: 4px; +} diff --git a/plugin-api/beta/client/components/SortOption.js b/plugin-api/beta/client/components/SortOption.js new file mode 100644 index 000000000..42e1c7ba5 --- /dev/null +++ b/plugin-api/beta/client/components/SortOption.js @@ -0,0 +1,15 @@ +import React from 'react'; +import styles from './SortOption.css'; + +export default class SortOption extends React.Component { + render() { + return ( +
+ + +
+ ); + } +} diff --git a/plugin-api/beta/client/components/index.js b/plugin-api/beta/client/components/index.js index 8b0448f4e..4367d3420 100644 --- a/plugin-api/beta/client/components/index.js +++ b/plugin-api/beta/client/components/index.js @@ -1,3 +1,5 @@ export {Slot} from 'coral-framework/components'; export {default as ClickOutside} from 'coral-framework/components/ClickOutside'; +export {default as IfSlotIsEmpty} from 'coral-framework/components/IfSlotIsEmpty'; +export {default as IfSlotIsNotEmpty} from 'coral-framework/components/IfSlotIsNotEmpty'; export {default as CommentAuthorName} from 'coral-framework/components/CommentAuthorName'; diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js index 60b118522..1bc47868e 100644 --- a/plugin-api/beta/client/hocs/index.js +++ b/plugin-api/beta/client/hocs/index.js @@ -1,5 +1,6 @@ export {default as withReaction} from './withReaction'; export {default as withTags} from './withTags'; +export {default as withSortOption} from './withSortOption'; export {default as withFragments} from 'coral-framework/hocs/withFragments'; export {default as excludeIf} from 'coral-framework/hocs/excludeIf'; export {default as connect} from 'coral-framework/hocs/connect'; diff --git a/plugin-api/beta/client/hocs/withSortOption.js b/plugin-api/beta/client/hocs/withSortOption.js new file mode 100644 index 000000000..16162710d --- /dev/null +++ b/plugin-api/beta/client/hocs/withSortOption.js @@ -0,0 +1,40 @@ +import React from 'react'; +import {connect} from 'plugin-api/beta/client/hocs'; +import {bindActionCreators} from 'redux'; +import {sortOrderSelector, sortBySelector} from 'plugin-api/beta/client/selectors/stream'; +import {setSort} from 'plugin-api/beta/client/actions/stream'; +import hoistStatics from 'recompose/hoistStatics'; + +const mapStateToProps = (state) => ({ + sortOrder: sortOrderSelector(state), + sortBy: sortBySelector(state), +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + setSort + }, + dispatch + ); + +export default ({by = 'created_at', order = 'DESC', label}) => hoistStatics((WrappedComponent) => { + class WithSortOption extends React.Component { + setSort = () => { + this.props.setSort({by, order}); + } + + render() { + const active = this.props.sortOrder === order && this.props.sortBy === by; + const resolvedLabel = typeof label === 'function' ? label() : label; + return ( + + ); + } + } + return connect(mapStateToProps, mapDispatchToProps)(WithSortOption); +}); diff --git a/plugin-api/beta/client/selectors/stream.js b/plugin-api/beta/client/selectors/stream.js new file mode 100644 index 000000000..6d8934bd3 --- /dev/null +++ b/plugin-api/beta/client/selectors/stream.js @@ -0,0 +1,2 @@ +export const sortOrderSelector = (state) => state.stream.sortOrder; +export const sortBySelector = (state) => state.stream.sortBy; diff --git a/plugin-api/beta/client/utils/index.js b/plugin-api/beta/client/utils/index.js index e0ff635e0..f652b2b66 100644 --- a/plugin-api/beta/client/utils/index.js +++ b/plugin-api/beta/client/utils/index.js @@ -3,5 +3,6 @@ export { insertCommentsSorted, getSlotFragmentSpreads, forEachError, + capitalize, getDefinitionName, } from 'coral-framework/utils'; diff --git a/plugins/talk-plugin-featured-comments/client/containers/TabPane.js b/plugins/talk-plugin-featured-comments/client/containers/TabPane.js index 9a2280233..6d9781ec4 100644 --- a/plugins/talk-plugin-featured-comments/client/containers/TabPane.js +++ b/plugins/talk-plugin-featured-comments/client/containers/TabPane.js @@ -18,7 +18,7 @@ class TabPaneContainer extends React.Component { limit: 5, cursor: this.props.asset.featuredComments.endCursor, asset_id: this.props.asset.id, - sort: this.props.data.variables.sort, + sortOrder: this.props.data.variables.sortOrder, sortBy: this.props.data.variables.sortBy, excludeIgnored: this.props.data.variables.excludeIgnored, }, @@ -52,7 +52,7 @@ const LOAD_MORE_QUERY = gql` $limit: Int = 5 $cursor: Cursor $asset_id: ID - $sort: SORT_ORDER + $sortOrder: SORT_ORDER $sortBy: SORT_COMMENTS_BY $excludeIgnored: Boolean ) { @@ -62,7 +62,7 @@ const LOAD_MORE_QUERY = gql` cursor: $cursor tags: ["FEATURED"] asset_id: $asset_id, - sort: $sort + sort: $sortOrder sortBy: $sortBy excludeIgnored: $excludeIgnored } @@ -100,7 +100,7 @@ const enhance = compose( featuredComments: comments( query: { tags: ["FEATURED"] - sort: $sort + sort: $sortOrder sortBy: $sortBy } deep: true diff --git a/plugins/talk-plugin-offtopic/client/components/styles.css b/plugins/talk-plugin-offtopic/client/components/styles.css index c2f3e4e36..22d1f787a 100644 --- a/plugins/talk-plugin-offtopic/client/components/styles.css +++ b/plugins/talk-plugin-offtopic/client/components/styles.css @@ -19,7 +19,6 @@ } .viewingOption { - padding: 5px 0; } :global(.talk-plugin-off-topic-comment) { diff --git a/plugins/talk-plugin-offtopic/client/index.js b/plugins/talk-plugin-offtopic/client/index.js index 24bfc095f..f7156a0ec 100644 --- a/plugins/talk-plugin-offtopic/client/index.js +++ b/plugins/talk-plugin-offtopic/client/index.js @@ -6,7 +6,7 @@ import reducer from './reducer'; /** * talk-plugin-offtopic depends on talk-plugin-viewing-options - * in other to display filter and use the streamViewingOptions slot + * in other to display filter. */ export default { @@ -15,6 +15,6 @@ export default { slots: { commentInputDetailArea: [OffTopicCheckbox], commentInfoBar: [OffTopicTag], - viewingOptions: [OffTopicFilter] + viewingOptionsFilter: [OffTopicFilter] } }; diff --git a/plugins/talk-plugin-sort-oldest/client/.babelrc b/plugins/talk-plugin-sort-oldest/client/.babelrc new file mode 100644 index 000000000..60be246eb --- /dev/null +++ b/plugins/talk-plugin-sort-oldest/client/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} \ No newline at end of file diff --git a/plugins/talk-plugin-sort-oldest/client/.eslintrc.json b/plugins/talk-plugin-sort-oldest/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/talk-plugin-sort-oldest/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/plugins/talk-plugin-sort-oldest/client/components/SortOption.css b/plugins/talk-plugin-sort-oldest/client/components/SortOption.css new file mode 100644 index 000000000..b8d6cacc5 --- /dev/null +++ b/plugins/talk-plugin-sort-oldest/client/components/SortOption.css @@ -0,0 +1,9 @@ + +.input { + cursor: pointer; +} + +.label { + cursor: pointer; + padding-left: 4px; +} diff --git a/plugins/talk-plugin-sort-oldest/client/components/SortOption.js b/plugins/talk-plugin-sort-oldest/client/components/SortOption.js new file mode 100644 index 000000000..42e1c7ba5 --- /dev/null +++ b/plugins/talk-plugin-sort-oldest/client/components/SortOption.js @@ -0,0 +1,15 @@ +import React from 'react'; +import styles from './SortOption.css'; + +export default class SortOption extends React.Component { + render() { + return ( +
+ + +
+ ); + } +} diff --git a/plugins/talk-plugin-sort-oldest/client/containers/SortOption.js b/plugins/talk-plugin-sort-oldest/client/containers/SortOption.js new file mode 100644 index 000000000..9c4567f18 --- /dev/null +++ b/plugins/talk-plugin-sort-oldest/client/containers/SortOption.js @@ -0,0 +1,4 @@ +import {withSortOption} from 'plugin-api/beta/client/hocs'; +import SortOption from '../components/SortOption'; + +export default withSortOption({by: 'CREATED_AT', order: 'ASC', label: 'Oldest first'})(SortOption); diff --git a/plugins/talk-plugin-sort-oldest/client/containers/SortOptionNewest.js b/plugins/talk-plugin-sort-oldest/client/containers/SortOptionNewest.js new file mode 100644 index 000000000..7ab5dcad7 --- /dev/null +++ b/plugins/talk-plugin-sort-oldest/client/containers/SortOptionNewest.js @@ -0,0 +1,4 @@ +import {withSortOption} from 'plugin-api/beta/client/hocs'; +import SortOption from '../components/SortOption'; + +export default withSortOption({by: 'CREATED_AT', order: 'DESC', label: 'Newest first'})(SortOption); diff --git a/plugins/talk-plugin-sort-oldest/client/index.js b/plugins/talk-plugin-sort-oldest/client/index.js new file mode 100644 index 000000000..5d3f08ee8 --- /dev/null +++ b/plugins/talk-plugin-sort-oldest/client/index.js @@ -0,0 +1,15 @@ +import translations from './translations.yml'; +import SortOption from './containers/SortOption'; +import SortOptionNewest from './containers/SortOptionNewest'; + +/** + * talk-plugin-sort-oldest depends on talk-plugin-viewing-options + * in other to display sort option. + */ + +export default { + translations, + slots: { + viewingOptionsSort: [SortOptionNewest, SortOption] + } +}; diff --git a/plugins/talk-plugin-sort-oldest/client/translations.yml b/plugins/talk-plugin-sort-oldest/client/translations.yml new file mode 100644 index 000000000..50645be77 --- /dev/null +++ b/plugins/talk-plugin-sort-oldest/client/translations.yml @@ -0,0 +1,4 @@ +en: + talk-plugin-sort-oldest: +es: + talk-plugin-sort-oldest: diff --git a/plugins/talk-plugin-sort-oldest/index.js b/plugins/talk-plugin-sort-oldest/index.js new file mode 100644 index 000000000..7be35b6b6 --- /dev/null +++ b/plugins/talk-plugin-sort-oldest/index.js @@ -0,0 +1,2 @@ +module.exports = { +}; diff --git a/plugins/talk-plugin-viewing-options/client/components/Category.css b/plugins/talk-plugin-viewing-options/client/components/Category.css new file mode 100644 index 000000000..758975fce --- /dev/null +++ b/plugins/talk-plugin-viewing-options/client/components/Category.css @@ -0,0 +1,15 @@ +.title { + font-weight: bold; + padding: 12px 8px 0px 8px; +} + +.list { + padding: 0; + margin: 0; +} + +.listItem { + padding: 10px; + list-style: none; + white-space: nowrap; +} diff --git a/plugins/talk-plugin-viewing-options/client/components/Category.js b/plugins/talk-plugin-viewing-options/client/components/Category.js new file mode 100644 index 000000000..0ef08b6d0 --- /dev/null +++ b/plugins/talk-plugin-viewing-options/client/components/Category.js @@ -0,0 +1,16 @@ +import React from 'react'; +import styles from './Category.css'; +import {Slot, IfSlotIsNotEmpty} from 'plugin-api/beta/client/components'; + +const childFactory = (child) =>
  • {child}
  • ; + +const ViewingOptions = ({slot, title}) => { + return ( + +
    {title}
    + +
    + ); +}; + +export default ViewingOptions; diff --git a/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.css b/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.css index c51d87f11..c71094e33 100644 --- a/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.css +++ b/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.css @@ -9,13 +9,15 @@ cursor: pointer; } -.list { +.menu { background: white; position: absolute; box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.15); right: 0px; top: 20px; z-index: 10; + min-height: 32px; + min-width: 64px; } .list > ul, .list > ul > li { diff --git a/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.js b/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.js index 8988dd1ec..a7d6040e1 100644 --- a/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.js +++ b/plugins/talk-plugin-viewing-options/client/components/ViewingOptions.js @@ -3,51 +3,60 @@ import cn from 'classnames'; import styles from './ViewingOptions.css'; import {t} from 'plugin-api/beta/client/services'; import {Icon} from 'plugin-api/beta/client/components/ui'; -import {Slot, ClickOutside} from 'plugin-api/beta/client/components'; +import {ClickOutside} from 'plugin-api/beta/client/components'; +import {capitalize} from 'plugin-api/beta/client/utils'; +import Category from './Category'; -const ViewingOptions = (props) => { - const toggleOpen = () => { - if (!props.open) { - props.openMenu(); +class ViewingOptions extends React.Component { + + categories = { + sort: t('talk-plugin-viewing-options.sort'), + filter: t('talk-plugin-viewing-options.filter'), + }; + + toggleOpen = () => { + const {open, openMenu, closeMenu} = this.props; + if (!open) { + openMenu(); } else { - props.closeMenu(); + closeMenu(); } }; - const handleClickOutside = () => { - if (props.open) { - props.closeMenu(); + handleClickOutside = () => { + const {open, closeMenu} = this.props; + if (open) { + closeMenu(); } }; - return ( - -
    -
    - -
    - { - props.open ? ( -
    -
      + render() { + const {open} = this.props; + return ( + +
      +
      + +
      + { + open ? ( +
      { - React.Children.map(, (component) => { - return React.createElement('li', { - className: 'talk-plugin-viewing-options-item' - }, component); - }) + Object.keys(this.categories).map((category) => + + ) } -
    -
    - ) : null - } -
    -
    - ); -}; + + ) : null + } + + + ); + } +} export default ViewingOptions; diff --git a/plugins/talk-plugin-viewing-options/client/translations.yml b/plugins/talk-plugin-viewing-options/client/translations.yml index d1b1c1896..8560b79cc 100644 --- a/plugins/talk-plugin-viewing-options/client/translations.yml +++ b/plugins/talk-plugin-viewing-options/client/translations.yml @@ -1,6 +1,8 @@ en: talk-plugin-viewing-options: viewing_options: "Viewing Options" + sort: Sort + filter: Filter es: talk-plugin-viewing-options: viewing_options: "Opciones de visualización"