From 1e4e1cb41399547d1368e5df5af0edf03241c1f0 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 5 Jul 2017 22:23:19 +0700 Subject: [PATCH 01/11] Implement Stream TabBar --- .../coral-embed-stream/src/actions/stream.js | 4 + .../src/components/AllCommentsPane.js | 202 +++++++++++++ .../src/components/Comment.css | 20 +- .../src/components/Comment.js | 18 +- .../src/components/EditableCommentContent.js | 8 +- .../src/components/Embed.js | 75 ++--- .../src/components/Stream.css | 12 + .../src/components/Stream.js | 270 ++++++------------ .../src/constants/stream.js | 1 + .../src/containers/Embed.js | 2 - .../src/containers/Stream.js | 6 +- .../coral-embed-stream/src/reducers/stream.js | 10 +- client/coral-embed-stream/style/default.css | 10 +- client/coral-framework/styles/reset.css | 29 ++ client/coral-plugin-best/BestButton.css | 4 + client/coral-plugin-best/BestButton.js | 5 +- .../CommentCount.js | 17 -- .../coral-plugin-flags/components/styles.css | 1 + client/coral-plugin-replies/ReplyButton.css | 4 + client/coral-plugin-replies/ReplyButton.js | 8 +- client/coral-ui/components/Tab.css | 67 ++++- client/coral-ui/components/Tab.js | 94 +++++- client/coral-ui/components/TabBar.css | 40 +-- client/coral-ui/components/TabBar.js | 75 +++-- client/coral-ui/components/TabContent.css | 3 + client/coral-ui/components/TabContent.js | 31 +- client/coral-ui/components/TabCount.css | 18 ++ client/coral-ui/components/TabCount.js | 29 ++ client/coral-ui/components/TabPane.js | 24 ++ client/coral-ui/index.js | 2 + locales/en.yml | 1 + locales/es.yml | 1 + .../coral-plugin-like/client/LikeButton.js | 2 +- plugins/coral-plugin-like/client/styles.css | 4 + .../coral-plugin-love/client/LoveButton.js | 2 +- plugins/coral-plugin-love/client/styles.css | 4 + .../coral-plugin-respect/client/styles.css | 2 +- .../client/components/ViewingOptions.css | 8 +- .../client/components/styles.css | 2 +- 39 files changed, 765 insertions(+), 350 deletions(-) create mode 100644 client/coral-embed-stream/src/components/AllCommentsPane.js create mode 100644 client/coral-embed-stream/src/components/Stream.css create mode 100644 client/coral-framework/styles/reset.css create mode 100644 client/coral-plugin-best/BestButton.css delete mode 100644 client/coral-plugin-comment-count/CommentCount.js create mode 100644 client/coral-plugin-replies/ReplyButton.css create mode 100644 client/coral-ui/components/TabContent.css create mode 100644 client/coral-ui/components/TabCount.css create mode 100644 client/coral-ui/components/TabCount.js create mode 100644 client/coral-ui/components/TabPane.js diff --git a/client/coral-embed-stream/src/actions/stream.js b/client/coral-embed-stream/src/actions/stream.js index 9b5d94a9b..af905a53f 100644 --- a/client/coral-embed-stream/src/actions/stream.js +++ b/client/coral-embed-stream/src/actions/stream.js @@ -47,3 +47,7 @@ export const removeCommentClassName = (idx) => ({ type: actions.REMOVE_COMMENT_CLASSNAME, idx }); + +export const setActiveTab = (tab) => (dispatch) => { + dispatch({type: actions.SET_ACTIVE_TAB, tab}); +}; diff --git a/client/coral-embed-stream/src/components/AllCommentsPane.js b/client/coral-embed-stream/src/components/AllCommentsPane.js new file mode 100644 index 000000000..6af72475a --- /dev/null +++ b/client/coral-embed-stream/src/components/AllCommentsPane.js @@ -0,0 +1,202 @@ +import React from 'react'; + +import LoadMore from './LoadMore'; +import IgnoredCommentTombstone from './IgnoredCommentTombstone'; +import NewCount from './NewCount'; +import {TransitionGroup} from 'react-transition-group'; +import {forEachError} from 'coral-framework/utils'; +import Comment from '../components/Comment'; + +const hasComment = (nodes, id) => nodes.some((node) => node.id === id); + +// resetCursors will return the id cursors of the first and second comment of +// the current comment list. The cursors are used to dertermine which +// comments to show. The spare cursor functions as a backup in case one +// of the comments gets deleted. +function resetCursors(state, props) { + const comments = props.root.asset.comments; + if (comments && comments.nodes.length) { + const idCursors = [comments.nodes[0].id]; + if (comments.nodes[1]) { + idCursors.push(comments.nodes[1].id); + } + return {idCursors}; + } + return {idCursors: []}; +} + +// invalidateCursor is called whenever a comment is removed which is referenced +// by one of the 2 id cursors. It returns a new set of id cursors calculated +// using the help of the backup cursor. +function invalidateCursor(invalidated, state, props) { + const alt = invalidated === 1 ? 0 : 1; + const comments = props.root.asset.comments; + const idCursors = []; + if (state.idCursors[alt]) { + idCursors.push(state.idCursors[alt]); + const index = comments.nodes.findIndex((node) => node.id === idCursors[0]); + const nextInLine = comments.nodes[index + 1]; + if (nextInLine) { + idCursors.push(nextInLine.id); + } + } + return {idCursors}; +} + +class AllCommentsPane extends React.Component { + + constructor(props) { + super(props); + this.state = { + ...resetCursors(this.state, props), + loadingState: '', + }; + } + + componentWillReceiveProps(next) { + const {comments: prevComments} = this.props; + const {comments: nextComments} = next; + + if (!prevComments && nextComments) { + this.setState(resetCursors); + return; + } + + if ( + prevComments && nextComments && + nextComments.nodes.length < prevComments.nodes.length + ) { + + // Invalidate first cursor if referenced comment was removed. + if (this.state.idCursors[0] && !hasComment(nextComments.nodes, this.state.idCursors[0])) { + this.setState(invalidateCursor(0, this.state, next)); + } + + // Invalidate second cursor if referenced comment was removed. + if (this.state.idCursors[1] && !hasComment(nextComments.nodes, this.state.idCursors[1])) { + this.setState(invalidateCursor(1, this.state, next)); + } + } + } + + loadMore = () => { + this.setState({loadingState: 'loading'}); + this.props.loadMore() + .then(() => { + this.setState({loadingState: 'success'}); + }) + .catch((error) => { + this.setState({loadingState: 'error'}); + forEachError(error, ({msg}) => {this.props.addNotification('error', msg);}); + }); + } + + viewNewComments = () => { + this.setState(resetCursors); + }; + + // getVisibileComments returns a list containing comments + // which were authored by current user or comes after the `idCursor`. + getVisibleComments() { + const {comments, currentUser: user} = this.props; + const idCursor = this.state.idCursors[0]; + const userId = user ? user.id : null; + + if (!comments) { + return []; + } + + const view = []; + let pastCursor = false; + comments.nodes.forEach((comment) => { + if (comment.id === idCursor) { + pastCursor = true; + } + if (pastCursor || comment.user.id === userId) { + view.push(comment); + } + }); + return view; + } + + render() { + const { + data, + root, + comments, + commentClassNames, + addTag, + removeTag, + ignoreUser, + setActiveReplyBox, + activeReplyBox, + addNotification, + disableReply, + postComment, + asset, + currentUser, + postFlag, + postDontAgree, + loadNewReplies, + deleteAction, + showSignInDialog, + commentIsIgnored, + charCountEnable, + maxCharCount, + editComment, + } = this.props; + + const {loadingState} = this.state; + const view = this.getVisibleComments(); + + return ( +
+ + + {view.map((comment) => { + return commentIsIgnored(comment) + ? + : ; + })} + + +
+ ); + } +} + +export default AllCommentsPane; diff --git a/client/coral-embed-stream/src/components/Comment.css b/client/coral-embed-stream/src/components/Comment.css index 4c8a9fc86..d397185dc 100644 --- a/client/coral-embed-stream/src/components/Comment.css +++ b/client/coral-embed-stream/src/components/Comment.css @@ -1,8 +1,17 @@ .root { - margin-top: 16px; margin-left: 20px; - margin-bottom: 15px; + margin-bottom: 16px; position: relative; + border-top: 1px solid rgba(0, 0, 0, 0.1); + padding-top: 12px; +} + +.rootLevel0:first-child { + padding-top: 0; +} + +.root:first-child { + border: 0; } .rootLevel0 { @@ -52,6 +61,13 @@ font-style: italic; } +.hr { + border: 0; + height: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(255, 255, 255, 0.3); +} + /* element in the top right of the Comment */ .topRight { float: right; diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js index 0e6201caf..bad2b6e4a 100644 --- a/client/coral-embed-stream/src/components/Comment.js +++ b/client/coral-embed-stream/src/components/Comment.js @@ -134,7 +134,6 @@ export default class Comment extends React.Component { } static propTypes = { - reactKey: PropTypes.string.isRequired, // id of currently opened ReplyBox. tracked in Stream.js activeReplyBox: PropTypes.string.isRequired, @@ -148,12 +147,8 @@ export default class Comment extends React.Component { addNotification: PropTypes.func.isRequired, postComment: PropTypes.func.isRequired, depth: PropTypes.number.isRequired, - liveUpdates: PropTypes.bool.isRequired, - asset: PropTypes.shape({ - id: PropTypes.string, - title: PropTypes.string, - url: PropTypes.string - }).isRequired, + liveUpdates: PropTypes.bool, + asset: PropTypes.object.isRequired, currentUser: PropTypes.shape({ id: PropTypes.string.isRequired }), @@ -335,7 +330,6 @@ export default class Comment extends React.Component { const view = this.getVisibileReplies(); const {loadingState} = this.state; - const isReply = !!parentId; const isPending = comment.id.indexOf('pending') >= 0; const isHighlighted = highlighted === comment.id; @@ -372,7 +366,7 @@ export default class Comment extends React.Component { addTag({ id: comment.id, name: BEST_TAG, - assetId: asset.id + assetId: asset.id, }), () => 'Failed to tag comment as best' ); @@ -382,7 +376,7 @@ export default class Comment extends React.Component { removeTag({ id: comment.id, name: BEST_TAG, - assetId: asset.id + assetId: asset.id, }), () => 'Failed to remove best comment tag' ); @@ -422,7 +416,6 @@ export default class Comment extends React.Component { className={cn(...rootClassNames)} id={`c_${comment.id}`} > - {!isReply &&
}
; diff --git a/client/coral-embed-stream/src/components/EditableCommentContent.js b/client/coral-embed-stream/src/components/EditableCommentContent.js index 7e6035d58..06d8e6b9d 100644 --- a/client/coral-embed-stream/src/components/EditableCommentContent.js +++ b/client/coral-embed-stream/src/components/EditableCommentContent.js @@ -18,11 +18,6 @@ export class EditableCommentContent extends React.Component { // show notification to the user (e.g. for errors) addNotification: PropTypes.func.isRequired, - asset: PropTypes.shape({ - settings: PropTypes.shape({ - charCountEnable: PropTypes.bool, - }), - }).isRequired, // comment that is being edited comment: PropTypes.shape({ @@ -39,6 +34,7 @@ export class EditableCommentContent extends React.Component { currentUser: PropTypes.shape({ id: PropTypes.string.isRequired }), + charCountEnable: PropTypes.bool, maxCharCount: PropTypes.number, // edit a comment, passed {{ body }} @@ -121,7 +117,7 @@ export class EditableCommentContent extends React.Component {
{ - switch (tab) { - case 0: - this.props.setActiveTab('stream'); - break; - case 1: - this.props.setActiveTab('profile'); - // TODO: move data fetching to profile container. + // TODO: move data fetching to appropiate containers. + switch (tab) { + case 'profile': this.props.data.refetch(); break; - case 2: - this.props.setActiveTab('config'); - - // TODO: move data fetching to config container. + case 'config': this.props.data.refetch(); break; } + this.props.setActiveTab(tab); }; - handleShowProfile = () => this.props.setActiveTab('profile'); - render() { - const {activeTab, viewAllComments, commentId} = this.props; - const {asset: {totalCommentCount}} = this.props.root; + const {activeTab} = this.props; const {user} = this.props.auth; return (
- - - {t('framework.my_profile')} - {t('framework.configure_stream')} + + + {t('embed_comments_tab')} + + + {t('framework.my_profile')} + + {can(user, 'UPDATE_CONFIG') && + + {t('framework.configure_stream')} + + } - {commentId && - } - - - - - - - - + + + + + + + + + + +
diff --git a/client/coral-embed-stream/src/components/Stream.css b/client/coral-embed-stream/src/components/Stream.css new file mode 100644 index 000000000..f2c4c48c5 --- /dev/null +++ b/client/coral-embed-stream/src/components/Stream.css @@ -0,0 +1,12 @@ +.root { + margin-top: 6px; +} + +.viewAllButton { + position: absolute; + right: 0px; +} + +.tabPanel { + margin-top: 8px; +} diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 4b04f5e7b..1c84c2d47 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -1,5 +1,4 @@ import React, {PropTypes} from 'react'; -import LoadMore from './LoadMore'; import {StreamError} from './StreamError'; import Comment from '../components/Comment'; import SuspendedAccount from './SuspendedAccount'; @@ -12,161 +11,71 @@ import RestrictedMessageBox import t, {timeago} from 'coral-framework/services/i18n'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; -import IgnoredCommentTombstone from './IgnoredCommentTombstone'; -import NewCount from './NewCount'; -import {TransitionGroup} from 'react-transition-group'; -import {forEachError} from 'coral-framework/utils'; +import {Button, TabBar, Tab, TabCount, TabContent, TabPane} from 'coral-ui'; + import {getTopLevelParent} from '../graphql/utils'; +import AllCommentsPane from './AllCommentsPane'; -const hasComment = (nodes, id) => nodes.some((node) => node.id === id); - -// resetCursors will return the id cursors of the first and second comment of -// the current comment list. The cursors are used to dertermine which -// comments to show. The spare cursor functions as a backup in case one -// of the comments gets deleted. -function resetCursors(state, props) { - const comments = props.root.asset.comments; - if (comments && comments.nodes.length) { - const idCursors = [comments.nodes[0].id]; - if (comments.nodes[1]) { - idCursors.push(comments.nodes[1].id); - } - return {idCursors}; - } - return {idCursors: []}; -} - -// invalidateCursor is called whenever a comment is removed which is referenced -// by one of the 2 id cursors. It returns a new set of id cursors calculated -// using the help of the backup cursor. -function invalidateCursor(invalidated, state, props) { - const alt = invalidated === 1 ? 0 : 1; - const comments = props.root.asset.comments; - const idCursors = []; - if (state.idCursors[alt]) { - idCursors.push(state.idCursors[alt]); - const index = comments.nodes.findIndex((node) => node.id === idCursors[0]); - const nextInLine = comments.nodes[index + 1]; - if (nextInLine) { - idCursors.push(nextInLine.id); - } - } - return {idCursors}; -} +import styles from './Stream.css'; class Stream extends React.Component { constructor(props) { super(props); this.state = { - ...resetCursors(this.state, props), keepCommentBox: false, - loadingState: '', }; } componentWillReceiveProps(next) { - const {root: {asset: {comments: prevComments}}} = this.props; - const {root: {asset: {comments: nextComments}}} = next; - - if (!prevComments && nextComments) { - this.setState(resetCursors); - return; - } // Keep comment box when user was live suspended, banned, ... if (!this.userIsDegraged(this.props) && this.userIsDegraged(next)) { this.setState({keepCommentBox: true}); } - - if ( - prevComments && nextComments && - nextComments.nodes.length < prevComments.nodes.length - ) { - - // Invalidate first cursor if referenced comment was removed. - if (this.state.idCursors[0] && !hasComment(nextComments.nodes, this.state.idCursors[0])) { - this.setState(invalidateCursor(0, this.state, next)); - } - - // Invalidate second cursor if referenced comment was removed. - if (this.state.idCursors[1] && !hasComment(nextComments.nodes, this.state.idCursors[1])) { - this.setState(invalidateCursor(1, this.state, next)); - } - } } - viewNewComments = () => { - this.setState(resetCursors); - }; - - setActiveReplyBox = (reactKey) => { + setActiveReplyBox = (id) => { if (!this.props.auth.user) { this.props.showSignInDialog(); } else { - this.props.setActiveReplyBox(reactKey); + this.props.setActiveReplyBox(id); } }; - loadMoreComments = () => { - this.setState({loadingState: 'loading'}); - this.props.loadMoreComments() - .then(() => { - this.setState({loadingState: 'success'}); - }) - .catch((error) => { - this.setState({loadingState: 'error'}); - forEachError(error, ({msg}) => {this.props.addNotification('error', msg);}); - }); - } - - // getVisibileComments returns a list containing comments - // which were authored by current user or comes after the `idCursor`. - getVisibleComments() { - const {root: {asset: {comments}}, auth: {user}} = this.props; - const idCursor = this.state.idCursors[0]; - const userId = user ? user.id : null; - - if (!comments) { - return []; - } - - const view = []; - let pastCursor = false; - comments.nodes.forEach((comment) => { - if (comment.id === idCursor) { - pastCursor = true; - } - if (pastCursor || comment.user.id === userId) { - view.push(comment); - } - }); - return view; - } - userIsDegraged({auth: {user}} = this.props) { return !can(user, 'INTERACT_WITH_COMMUNITY'); } render() { const { + data, + root, + activeReplyBox, + setActiveReplyBox, + appendItemArray, commentClassNames, - root: {asset, asset: {comments}, comment, me}, + root: {asset, asset: {comments, totalCommentCount}, comment, me}, postComment, addNotification, + editComment, postFlag, postDontAgree, deleteAction, showSignInDialog, + updateItem, addTag, ignoreUser, + activeStreamTab, + setActiveStreamTab, + loadNewReplies, + loadMoreComments, + viewAllComments, auth: {loggedIn, user}, removeTag, - pluginProps, editName } = this.props; - const {keepCommentBox, loadingState} = this.state; - const view = this.getVisibleComments(); + const {keepCommentBox} = this.state; const open = asset.closedAt === null; // even though the permalinked comment is the highlighted one, we're displaying its parent + replies @@ -194,7 +103,15 @@ class Stream extends React.Component { } return ( -
+
+ {comment && + } {open ?
@@ -211,7 +128,7 @@ class Stream extends React.Component { {t( 'stream.temporarily_suspended', - this.props.root.settings.organizationName, + root.settings.organizationName, timeago(user.suspension.until) )} } @@ -223,10 +140,10 @@ class Stream extends React.Component { />} {showCommentBox && - :
- - - {view.map((comment) => { - return commentIsIgnored(comment) - ? - : ; - })} - - -
} + :
+ + + Featured + + + All Comments {totalCommentCount} + + + + + TODO + + + + + +
+ }
); } diff --git a/client/coral-embed-stream/src/constants/stream.js b/client/coral-embed-stream/src/constants/stream.js index 992767455..622a86c18 100644 --- a/client/coral-embed-stream/src/constants/stream.js +++ b/client/coral-embed-stream/src/constants/stream.js @@ -4,3 +4,4 @@ export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS'; 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'; diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js index bf05caf29..fff925a0f 100644 --- a/client/coral-embed-stream/src/containers/Embed.js +++ b/client/coral-embed-stream/src/containers/Embed.js @@ -16,7 +16,6 @@ import {addNotification} from 'coral-framework/actions/notification'; import t from 'coral-framework/services/i18n'; import {setActiveTab} from '../actions/embed'; -import {viewAllComments} from '../actions/stream'; const {logout, checkLogin} = authActions; const {fetchAssetSuccess} = assetActions; @@ -185,7 +184,6 @@ const mapDispatchToProps = (dispatch) => logout, checkLogin, setActiveTab, - viewAllComments, fetchAssetSuccess, addNotification, }, diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index 28947b77e..21a29dd38 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -10,7 +10,7 @@ import { import {notificationActions, authActions} from 'coral-framework'; import {editName} from 'coral-framework/actions/user'; -import {setActiveReplyBox} from '../actions/stream'; +import {setActiveReplyBox, setActiveTab, viewAllComments} from '../actions/stream'; import Stream from '../components/Stream'; import Comment from './Comment'; import {withFragments} from 'coral-framework/hocs'; @@ -308,6 +308,8 @@ const mapStateToProps = (state) => ({ assetUrl: state.stream.assetUrl, activeTab: state.embed.activeTab, previousTab: state.embed.previousTab, + activeStreamTab: state.stream.activeTab, + previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames }); @@ -317,6 +319,8 @@ const mapDispatchToProps = (dispatch) => addNotification, setActiveReplyBox, editName, + viewAllComments, + setActiveStreamTab: setActiveTab, }, dispatch); export default compose( diff --git a/client/coral-embed-stream/src/reducers/stream.js b/client/coral-embed-stream/src/reducers/stream.js index 2d6e3e8b2..4f4fcf47f 100644 --- a/client/coral-embed-stream/src/reducers/stream.js +++ b/client/coral-embed-stream/src/reducers/stream.js @@ -20,11 +20,19 @@ const initialState = { assetId: getQueryVariable('asset_id'), assetUrl: getQueryVariable('asset_url'), commentId: getQueryVariable('comment_id'), - commentClassNames: [] + commentClassNames: [], + activeTab: 'all', + previousTab: '', }; export default function stream(state = initialState, action) { switch (action.type) { + case actions.SET_ACTIVE_TAB: + return { + ...state, + activeTab: action.tab, + previousTab: state.activeTab, + }; case authActions.LOGOUT: return { ...state, diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index bd56cb748..4d7d534dd 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -25,24 +25,24 @@ body { min-height: 600px; } -button { - margin: 5px 0px 5px 0px; +.coralButton { + margin: 5px 10px 5px 0px; background: none; padding: 0px; border: none; font-size: inherit; } -button:hover { +.coralButton:hover { border-radius: 2px; color: #767676; } -button i { +.coralButton i { margin-right: 3px; } -hr { +.coralHr { border: 0; height: 0; border-top: 1px solid rgba(0, 0, 0, 0.1); diff --git a/client/coral-framework/styles/reset.css b/client/coral-framework/styles/reset.css new file mode 100644 index 000000000..14ae4e723 --- /dev/null +++ b/client/coral-framework/styles/reset.css @@ -0,0 +1,29 @@ +.buttonReset { + + /* reset button */ + user-select: none; + outline: invert none medium; + border: none; + touch-action: manipulation; + padding: 0; + + position: relative; + overflow: hidden; + + /* Unify anchor and button. */ + cursor: pointer; + display: inline-block; + box-sizing: border-box; + text-align: center; + text-decoration: none; + align-items: flex-start; + vertical-align: middle; + whiteSpace: nowrap; + background: transparent; + font-size: inherit; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important; + &::-moz-focus-inner: { + border: 0; + } +} diff --git a/client/coral-plugin-best/BestButton.css b/client/coral-plugin-best/BestButton.css new file mode 100644 index 000000000..b1f169ff6 --- /dev/null +++ b/client/coral-plugin-best/BestButton.css @@ -0,0 +1,4 @@ +.button { + composes: buttonReset from "coral-framework/styles/reset.css"; + margin: 5px 10px 5px 0px; +} diff --git a/client/coral-plugin-best/BestButton.js b/client/coral-plugin-best/BestButton.js index 59204bbb8..ac6d03fd8 100644 --- a/client/coral-plugin-best/BestButton.js +++ b/client/coral-plugin-best/BestButton.js @@ -3,7 +3,8 @@ import React, {Component, PropTypes} from 'react'; import t from 'coral-framework/services/i18n'; import {Icon} from 'coral-ui'; -import classnames from 'classnames'; +import cn from 'classnames'; +import styles from './BestButton.css'; // tag string for best comments export const BEST_TAG = 'BEST'; @@ -95,7 +96,7 @@ export class BestButton extends Component { return ( diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js deleted file mode 100644 index cca99e729..000000000 --- a/client/coral-plugin-comment-count/CommentCount.js +++ /dev/null @@ -1,17 +0,0 @@ -import React, {PropTypes} from 'react'; - -import t from 'coral-framework/services/i18n'; - -const name = 'coral-plugin-comment-count'; - -const CommentCount = ({count}) => { - return
- {`${count} ${count === 1 ? t('comment_singular') : t('comment_plural')}`} -
; -}; - -CommentCount.propTypes = { - count: PropTypes.number.isRequired -}; - -export default CommentCount; diff --git a/client/coral-plugin-flags/components/styles.css b/client/coral-plugin-flags/components/styles.css index 6de9df387..0990252b4 100644 --- a/client/coral-plugin-flags/components/styles.css +++ b/client/coral-plugin-flags/components/styles.css @@ -1,4 +1,5 @@ .button { + composes: buttonReset from "coral-framework/styles/reset.css"; margin: 5px 0px 5px 10px; } diff --git a/client/coral-plugin-replies/ReplyButton.css b/client/coral-plugin-replies/ReplyButton.css new file mode 100644 index 000000000..b1f169ff6 --- /dev/null +++ b/client/coral-plugin-replies/ReplyButton.css @@ -0,0 +1,4 @@ +.button { + composes: buttonReset from "coral-framework/styles/reset.css"; + margin: 5px 10px 5px 0px; +} diff --git a/client/coral-plugin-replies/ReplyButton.js b/client/coral-plugin-replies/ReplyButton.js index a66e3c156..5c0b6b918 100644 --- a/client/coral-plugin-replies/ReplyButton.js +++ b/client/coral-plugin-replies/ReplyButton.js @@ -2,15 +2,17 @@ import React, {PropTypes} from 'react'; import t from 'coral-framework/services/i18n'; -import classnames from 'classnames'; +import styles from './ReplyButton.css'; +import cn from 'classnames'; const name = 'coral-plugin-replies'; const ReplyButton = ({onClick}) => { return ( + + ); + } +} + +Tab.propTypes = { + className: PropTypes.string, + classNames: PropTypes.shape({ + root: PropTypes.string, + rootActive: PropTypes.string, + rootSub: PropTypes.string, + rootSubActive: PropTypes.string, + button: PropTypes.string, + buttonActive: PropTypes.string, + buttonSub: PropTypes.string, + buttonSubActive: PropTypes.string, + }), + active: PropTypes.bool, + onTabClick: PropTypes.func, + sub: PropTypes.bool, +}; + +export default Tab; diff --git a/client/coral-ui/components/TabBar.css b/client/coral-ui/components/TabBar.css index b5d44d570..181fa1ddc 100644 --- a/client/coral-ui/components/TabBar.css +++ b/client/coral-ui/components/TabBar.css @@ -1,44 +1,14 @@ -.base { +.root { list-style: none; border-bottom: solid 1px #D8D8D8; padding: 0; + margin: 0; } -.base li { - color: #4E5259; - border: solid 1px #D8D8D8; - background: #F0F0F0; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - display: inline-block; - border-bottom: none; - padding: 8px 10px; - margin-right: -1px; - user-select: none; - font-size: 13px; -} - -.base li:hover { - background: #d5d5d5; - cursor: pointer; -} - -.material { +.rootSub { list-style: none; border: none; padding: 0; -} - -.material li { - color: black; - border: none; - border-bottom: solid 2px white; - background: white; - padding: 8px 0; - margin-right: 40px; -} - -.material li:hover { - background: white; - border-bottom: solid 2px grey; + margin: 0; + border-bottom: solid 2px #eee; } diff --git a/client/coral-ui/components/TabBar.js b/client/coral-ui/components/TabBar.js index 08f466ffc..433213a30 100644 --- a/client/coral-ui/components/TabBar.js +++ b/client/coral-ui/components/TabBar.js @@ -1,37 +1,66 @@ import React from 'react'; import styles from './TabBar.css'; +import cn from 'classnames'; +import Tab from './Tab'; +import PropTypes from 'prop-types'; class TabBar extends React.Component { - constructor(props) { - super(props); - this.handleClickTab = this.handleClickTab.bind(this); - } - handleClickTab(tabId) { - if (this.props.onChange) { - this.props.onChange(tabId); - } + getRootClassName({className, classNames = {}, sub} = this.props) { + return cn( + 'talk-tab-bar', + className, + { + [classNames.root || styles.root]: !sub, + [classNames.rootSub || styles.rootSub]: sub, + } + ); } render() { - const {children, activeTab, cStyle = 'base'} = this.props; + const { + children, + activeTab, + tabClassNames, + classNames: _a, + onTabClick: _b, + 'aria-controls': ariaControls, + sub, + ...rest, + } = this.props; + return ( -
-
    - {React.Children.toArray(children) - .filter((child) => !child.props.restricted) - .map((child, tabId) => - React.cloneElement(child, { - tabId, - active: child.props.id === activeTab, - onTabClick: this.handleClickTab, - cStyle - }) - )} -
-
+
    + {React.Children.toArray(children) + .map((child, i) => + React.cloneElement(child, { + tabId: (child.props.tabId !== undefined) ? child.props.tabId : i, + active: child.props.tabId === activeTab, + onTabClick: this.props.onTabClick, + classNames: tabClassNames, + 'aria-controls': ariaControls, + sub, + }) + )} +
); } } +TabBar.propTypes = { + className: PropTypes.string, + classNames: PropTypes.shape({ + root: PropTypes.string, + rootSub: PropTypes.string, + }), + tabClassNames: Tab.propTypes.classNames, + activeTab: PropTypes.string, + onTabClick: PropTypes.func, + sub: PropTypes.bool, +}; + export default TabBar; diff --git a/client/coral-ui/components/TabContent.css b/client/coral-ui/components/TabContent.css new file mode 100644 index 000000000..debafa97c --- /dev/null +++ b/client/coral-ui/components/TabContent.css @@ -0,0 +1,3 @@ +.root { + padding-top: 10px; +} diff --git a/client/coral-ui/components/TabContent.js b/client/coral-ui/components/TabContent.js index 737c9a5e6..768000cb6 100644 --- a/client/coral-ui/components/TabContent.js +++ b/client/coral-ui/components/TabContent.js @@ -1,6 +1,33 @@ import React from 'react'; +import cn from 'classnames'; +import PropTypes from 'prop-types'; +import styles from './TabContent.css'; -export default ({children, show = true}) => ( - show ?
{children}
: null +function getRootClassName(className) { + return cn('talk-tab-content', className, styles.root); +} + +const TabContent = ({children, className, activeTab, sub, ...rest}) => ( +
+ { + React.Children.toArray(children) + .filter((child) => child.props.tabId === activeTab) + .map((child, i) => + React.cloneElement(child, { + tabId: (child.props.tabId !== undefined) ? child.props.tabId : i, + sub, + })) + } +
); +TabContent.propTypes = { + className: PropTypes.string, + activeTab: PropTypes.string, + sub: PropTypes.bool, +}; + +export default TabContent; diff --git a/client/coral-ui/components/TabCount.css b/client/coral-ui/components/TabCount.css new file mode 100644 index 000000000..f95792a0b --- /dev/null +++ b/client/coral-ui/components/TabCount.css @@ -0,0 +1,18 @@ + +.root, .rootSub { + display: inline-block; + position: relative; + top: -2px; + background: #616161; + color: white; + font-weight: normal; + font-size: 10px; + padding: 2px; + margin-left: 2px; + margin-top: -2px; + min-width: 20px; +} + +.rootSubActive { + background: #10589b; +} diff --git a/client/coral-ui/components/TabCount.js b/client/coral-ui/components/TabCount.js new file mode 100644 index 000000000..89ccf1479 --- /dev/null +++ b/client/coral-ui/components/TabCount.js @@ -0,0 +1,29 @@ +import React from 'react'; +import cn from 'classnames'; +import styles from './TabCount.css'; + +function getNumber(no) { + let result = Number.parseInt(no); + if (no >= 1000) { + result = `${Math.round(result / 100) / 10}k`; + } + return result; +} + +function getRootClassName({className, active, sub}) { + return cn( + 'talk-tab-count', + className, + { + [styles.root]: !sub, + [styles.rootSub]: sub, + [styles.rootActive]: active && !sub, + [styles.rootSubActive]: active && sub, + 'talk-tab-active': active, + } + ); +} + +export default ({children, active, sub, className}) => ( + {getNumber(children)} +); diff --git a/client/coral-ui/components/TabPane.js b/client/coral-ui/components/TabPane.js new file mode 100644 index 000000000..1bdf19f0a --- /dev/null +++ b/client/coral-ui/components/TabPane.js @@ -0,0 +1,24 @@ +import React from 'react'; +import cn from 'classnames'; +import PropTypes from 'prop-types'; + +function getRootClassName(className) { + return cn('talk-pane', className); +} + +const TabPane = ({children, className, tabId: _a, sub: _b, ...rest}) => ( +
+ {children} +
+); + +TabPane.propTypes = { + className: PropTypes.string, + tabId: PropTypes.string, + sub: PropTypes.bool, +}; + +export default TabPane; diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index 3e9456727..8a538d120 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -4,7 +4,9 @@ export {default as CoralLogo} from './components/CoralLogo'; export {default as FabButton} from './components/FabButton'; export {default as TabBar} from './components/TabBar'; export {default as Tab} from './components/Tab'; +export {default as TabCount} from './components/TabCount'; export {default as TabContent} from './components/TabContent'; +export {default as TabPane} from './components/TabPane'; export {default as Button} from './components/Button'; export {default as Spinner} from './components/Spinner'; export {default as Tooltip} from './components/Tooltip'; diff --git a/locales/en.yml b/locales/en.yml index 44db5e1b8..e3bf42e5e 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -2,6 +2,7 @@ en: your_account_has_been_suspended: Your account has been temporarily suspended. your_account_has_been_banned: Your account has been banned. your_username_has_been_rejected: Your account has been suspended because your username has been deemed inappropriate. To restore your account please enter a new username. + embed_comments_tab: Comments bandialog: are_you_sure: "Are you sure you would like to ban {0}?" ban_user: "Ban User?" diff --git a/locales/es.yml b/locales/es.yml index a6b1bc4cd..fe6030b74 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -2,6 +2,7 @@ es: your_account_has_been_suspended: Su cuenta ha sido temporalmente suspendida. your_account_has_been_banned: Su cuenta ha sido suspendida. your_username_has_been_rejected: Su cuenta ha sido suspendida porque tu nombre de usuario ha sido considerado no apropiado para el espacio. Para recuperar la cuenta, por favor ingresar un nuevo nombre de usuario. + embed_comments_tab: Comentarios bandialog: are_you_sure: "¿Estás segura que quieres suspender a {0}?" ban_user: "¿Quieres suspender el Usuario?" diff --git a/plugins/coral-plugin-like/client/LikeButton.js b/plugins/coral-plugin-like/client/LikeButton.js index 7d17ef056..fd5c7d2c8 100644 --- a/plugins/coral-plugin-like/client/LikeButton.js +++ b/plugins/coral-plugin-like/client/LikeButton.js @@ -46,7 +46,7 @@ class LikeButton extends React.Component { onClick={this.handleClick} > {t(alreadyReacted ? 'coral-plugin-like.liked' : 'coral-plugin-like.like')} - + {count > 0 && count}
diff --git a/plugins/coral-plugin-like/client/styles.css b/plugins/coral-plugin-like/client/styles.css index cb372fa47..6e9fdb517 100644 --- a/plugins/coral-plugin-like/client/styles.css +++ b/plugins/coral-plugin-like/client/styles.css @@ -23,3 +23,7 @@ } } } + +.icon { + padding: 0 2px; +} diff --git a/plugins/coral-plugin-love/client/LoveButton.js b/plugins/coral-plugin-love/client/LoveButton.js index 7450ad668..d97803195 100644 --- a/plugins/coral-plugin-love/client/LoveButton.js +++ b/plugins/coral-plugin-love/client/LoveButton.js @@ -46,7 +46,7 @@ class LoveButton extends React.Component { onClick={this.handleClick} > {t(alreadyReacted ? 'coral-plugin-love.loved' : 'coral-plugin-love.love')} - + {count > 0 && count}
diff --git a/plugins/coral-plugin-love/client/styles.css b/plugins/coral-plugin-love/client/styles.css index e16e17ca4..316104e95 100644 --- a/plugins/coral-plugin-love/client/styles.css +++ b/plugins/coral-plugin-love/client/styles.css @@ -23,3 +23,7 @@ } } } + +.icon { + padding: 0 2px; +} diff --git a/plugins/coral-plugin-respect/client/styles.css b/plugins/coral-plugin-respect/client/styles.css index f0b780b86..e984dc3af 100644 --- a/plugins/coral-plugin-respect/client/styles.css +++ b/plugins/coral-plugin-respect/client/styles.css @@ -26,5 +26,5 @@ } .icon { - padding: 0 5px; + padding: 0 2px; } diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css index 47cda9a1a..a657bbeea 100644 --- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css @@ -1,11 +1,9 @@ .root { - float: right; - text-align: right; + display: flex; + justify-content: flex-end; position: relative; - display: inline-block; - min-width: 220px; + width: 100%; z-index: 10; - position: relative; cursor: pointer; } diff --git a/plugins/talk-plugin-permalink/client/components/styles.css b/plugins/talk-plugin-permalink/client/components/styles.css index f14e87881..244cc2acc 100644 --- a/plugins/talk-plugin-permalink/client/components/styles.css +++ b/plugins/talk-plugin-permalink/client/components/styles.css @@ -42,8 +42,8 @@ } .button { + composes: buttonReset from "coral-framework/styles/reset.css"; margin: 5px 0px 5px 10px; - cursor: pointer; } .copyButton { From ef172ae5f8d2b09b35c0600cc3cb7e8f2ba6d532 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 16:48:34 +0700 Subject: [PATCH 02/11] Change streamBox to streamFilter --- .../src/components/Stream.css | 6 +++ .../src/components/Stream.js | 10 ++-- client/coral-embed-stream/style/default.css | 4 -- .../client/components/ViewingOptions.css | 14 ++--- .../client/components/ViewingOptions.js | 53 +++++++++++-------- .../client/index.js | 2 +- 6 files changed, 51 insertions(+), 38 deletions(-) diff --git a/client/coral-embed-stream/src/components/Stream.css b/client/coral-embed-stream/src/components/Stream.css index f2c4c48c5..6c8ca66f0 100644 --- a/client/coral-embed-stream/src/components/Stream.css +++ b/client/coral-embed-stream/src/components/Stream.css @@ -10,3 +10,9 @@ .tabPanel { margin-top: 8px; } + +.filterWrapper { + position: absolute; + right: 0; + margin-top: 6px; +} diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 1c84c2d47..97678a597 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -12,6 +12,7 @@ import t, {timeago} from 'coral-framework/services/i18n'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; import {Button, TabBar, Tab, TabCount, TabContent, TabPane} from 'coral-ui'; +import cn from 'classnames'; import {getTopLevelParent} from '../graphql/utils'; import AllCommentsPane from './AllCommentsPane'; @@ -163,10 +164,6 @@ class Stream extends React.Component { /> )} -
- -
- {/* the highlightedComment is isolated after the user followed a permalink */} {highlightedComment ? :
+
+ +
Featured diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 4d7d534dd..023a2cc3d 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -181,10 +181,6 @@ body { display: none; } -.talk-stream-wrapper-box { - padding: 10px 0; -} - .talk-stream-comments-container { position: relative; } diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css index a657bbeea..9c302d07e 100644 --- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css @@ -1,18 +1,17 @@ .root { - display: flex; - justify-content: flex-end; - position: relative; - width: 100%; - z-index: 10; - cursor: pointer; +} + +.button { + composes: buttonReset from "coral-framework/styles/reset.css"; } .list { background: white; position: absolute; box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.15); - right: 3px; + right: 0px; top: 20px; + z-index: 10; } .list > ul, .list > ul > li { @@ -23,4 +22,5 @@ .list > ul > li { padding: 10px; list-style: none; + white-space: nowrap; } diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js index dc63a2373..b64123fdb 100644 --- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; import styles from './ViewingOptions.css'; -import {Slot} from 'plugin-api/beta/client/components'; +import {Slot, ClickOutside} from 'plugin-api/beta/client/components'; import {Icon} from 'plugin-api/beta/client/components/ui'; const ViewingOptions = (props) => { @@ -12,29 +12,38 @@ const ViewingOptions = (props) => { props.closeViewingOptions(); } }; + + const handleClickOutside = () => { + if (props.open) { + props.closeViewingOptions(); + } + }; + return ( -
-
- Viewing Options - {props.open ? : } - + +
+
+ +
+ { + props.open ? ( +
+
    + { + React.Children.map(, (component) => { + return React.createElement('li', { + className: 'coral-plugin-viewing-options-item' + }, component); + }) + } +
+
+ ) : null + }
- { - props.open ? ( -
-
    - { - React.Children.map(, (component) => { - return React.createElement('li', { - className: 'coral-plugin-viewing-options-item' - }, component); - }) - } -
-
- ) : null - } -
+ ); }; diff --git a/plugins/coral-plugin-viewing-options/client/index.js b/plugins/coral-plugin-viewing-options/client/index.js index 519ce42aa..4e9001c1b 100644 --- a/plugins/coral-plugin-viewing-options/client/index.js +++ b/plugins/coral-plugin-viewing-options/client/index.js @@ -4,6 +4,6 @@ import reducer from './reducer'; export default { reducer, slots: { - streamBox: [ViewingOptions] + streamFilter: [ViewingOptions] } }; From 362c07218180fedce5934e9bea93d13ddbfc3a42 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 16:54:55 +0700 Subject: [PATCH 03/11] If only one tab left make it not clickable --- client/coral-ui/components/Tab.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/coral-ui/components/Tab.css b/client/coral-ui/components/Tab.css index d1ef5ea9c..b5bf2463c 100644 --- a/client/coral-ui/components/Tab.css +++ b/client/coral-ui/components/Tab.css @@ -62,3 +62,8 @@ margin-bottom: 0px; } +.root:only-child .button, .rootSub:only-child .buttonSub { + cursor: default; + pointer-events: none; +} + From aac47c46865cab213e6dd4dcecb7123af2265570 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 17:57:45 +0700 Subject: [PATCH 04/11] Add margin --- client/coral-embed-stream/src/components/Stream.css | 4 ++++ client/coral-embed-stream/src/components/Stream.js | 2 +- plugins/coral-plugin-auth/client/components/styles.css | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/components/Stream.css b/client/coral-embed-stream/src/components/Stream.css index 6c8ca66f0..afedab0eb 100644 --- a/client/coral-embed-stream/src/components/Stream.css +++ b/client/coral-embed-stream/src/components/Stream.css @@ -16,3 +16,7 @@ right: 0; margin-top: 6px; } + +.tabContainer { + margin-top: 28px; +} diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 97678a597..ae457ffbe 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -195,7 +195,7 @@ class Stream extends React.Component { editComment={editComment} liveUpdates={true} /> - :
+ :
diff --git a/plugins/coral-plugin-auth/client/components/styles.css b/plugins/coral-plugin-auth/client/components/styles.css index c8fbf9a30..735f34d31 100644 --- a/plugins/coral-plugin-auth/client/components/styles.css +++ b/plugins/coral-plugin-auth/client/components/styles.css @@ -66,7 +66,7 @@ input.error{ } .userBox { - padding: 10px 0 20px; + margin: 10px 0 20px; letter-spacing: 0.1px; } From ab72995a2c63ad2f6bd818014e9d5ef745c2bf2e Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 20:46:28 +0700 Subject: [PATCH 05/11] Implement streamTabs and streamTabPanes slot --- .eslintignore | 1 + .gitignore | 1 + .../src/components/Stream.js | 26 ++++++++++++++----- .../src/containers/Stream.js | 10 +++++++ client/coral-framework/helpers/plugins.js | 26 ++++++++++++++----- .../coral-framework/loaders/plugins-loader.js | 2 +- client/coral-ui/components/Tab.css | 1 + plugins/talk-plugin-featured/client/.babelrc | 14 ++++++++++ .../client/.eslintrc.json | 23 ++++++++++++++++ .../client/components/Tab.js | 8 ++++++ .../client/components/TabPane.js | 14 ++++++++++ .../client/containers/Tab.js | 16 ++++++++++++ .../client/containers/TabPane.js | 21 +++++++++++++++ plugins/talk-plugin-featured/client/index.js | 9 +++++++ plugins/talk-plugin-featured/index.js | 2 ++ 15 files changed, 161 insertions(+), 13 deletions(-) create mode 100644 plugins/talk-plugin-featured/client/.babelrc create mode 100644 plugins/talk-plugin-featured/client/.eslintrc.json create mode 100644 plugins/talk-plugin-featured/client/components/Tab.js create mode 100644 plugins/talk-plugin-featured/client/components/TabPane.js create mode 100644 plugins/talk-plugin-featured/client/containers/Tab.js create mode 100644 plugins/talk-plugin-featured/client/containers/TabPane.js create mode 100644 plugins/talk-plugin-featured/client/index.js create mode 100644 plugins/talk-plugin-featured/index.js diff --git a/.eslintignore b/.eslintignore index 3a259c106..b58e4f058 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,4 +13,5 @@ plugins/* !plugins/coral-plugin-viewing-options !plugins/coral-plugin-comment-content !plugins/talk-plugin-permalink +!plugins/talk-plugin-featured node_modules diff --git a/.gitignore b/.gitignore index 0be1266c6..0e809b43a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,6 @@ plugins/* !plugins/coral-plugin-viewing-options !plugins/coral-plugin-comment-content !plugins/talk-plugin-permalink +!plugins/talk-plugin-featured **/node_modules/* diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index ae457ffbe..6205a2a10 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -9,6 +9,7 @@ import {ModerationLink} from 'coral-plugin-moderation'; import RestrictedMessageBox from 'coral-framework/components/RestrictedMessageBox'; import t, {timeago} from 'coral-framework/services/i18n'; +import {getSlotComponents} from 'coral-framework/helpers/plugins'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; import {Button, TabBar, Tab, TabCount, TabContent, TabPane} from 'coral-ui'; @@ -202,17 +203,30 @@ class Stream extends React.Component {
- - Featured - + {getSlotComponents('streamTabs').map((PluginComponent) => ( + + + + ))} All Comments {totalCommentCount} - - TODO - + {getSlotComponents('streamTabPanes').map((PluginComponent) => ( + + + + ))} !pluginConfig || !pluginConfig[o.plugin] || !pluginConfig[o.plugin].disable_components) - + .filter((o) => !pluginConfig || !pluginConfig[o.name] || !pluginConfig[o.name].disable_components) .filter((o) => o.module.slots[slot]) - .map((o) => o.module.slots[slot])); + .map((o) => o.module.slots[slot]) + ); } export function isSlotEmpty(slot) { @@ -113,7 +112,22 @@ export function injectPluginsReducers() { const reducers = merge( ...plugins .filter((o) => o.module.reducer) - .map((o) => ({[camelize(o.plugin)] : o.module.reducer})) + .map((o) => ({[camelize(o.name)] : o.module.reducer})) ); injectReducers(reducers); } + +function addMetaDataToSlotComponents() { + + // Add talkPluginName to Slot Components. + plugins.forEach((plugin) => { + const slots = plugin.module.slots; + slots && Object.keys(slots).forEach((slot) => { + slots[slot].forEach((component) => { + component.talkPluginName = plugin.name; + }); + }); + }); +} + +addMetaDataToSlotComponents(); diff --git a/client/coral-framework/loaders/plugins-loader.js b/client/coral-framework/loaders/plugins-loader.js index 4feae42ca..a9fed95ab 100644 --- a/client/coral-framework/loaders/plugins-loader.js +++ b/client/coral-framework/loaders/plugins-loader.js @@ -22,7 +22,7 @@ module.exports = function(source) { const config = this.exec(source, this.resourcePath); const plugins = getPluginList(config).map((plugin) => `{ module: require('${plugin}/client'), - plugin: '${plugin}' + name: '${plugin}' }`); return stripIndent` diff --git a/client/coral-ui/components/Tab.css b/client/coral-ui/components/Tab.css index b5bf2463c..b3c026b3d 100644 --- a/client/coral-ui/components/Tab.css +++ b/client/coral-ui/components/Tab.css @@ -60,6 +60,7 @@ font-weight: bold; border-bottom: solid 3px #10589b; margin-bottom: 0px; + padding: 6px 10px; } .root:only-child .button, .rootSub:only-child .buttonSub { diff --git a/plugins/talk-plugin-featured/client/.babelrc b/plugins/talk-plugin-featured/client/.babelrc new file mode 100644 index 000000000..63b1c53de --- /dev/null +++ b/plugins/talk-plugin-featured/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" + ] +} diff --git a/plugins/talk-plugin-featured/client/.eslintrc.json b/plugins/talk-plugin-featured/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/talk-plugin-featured/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-featured/client/components/Tab.js b/plugins/talk-plugin-featured/client/components/Tab.js new file mode 100644 index 000000000..f5306ebaa --- /dev/null +++ b/plugins/talk-plugin-featured/client/components/Tab.js @@ -0,0 +1,8 @@ +import React from 'react'; +import {TabCount} from 'plugin-api/beta/client/components/ui'; + +export default ({active, asset: {recentComments}}) => ( + + Featured {recentComments.length} + +); diff --git a/plugins/talk-plugin-featured/client/components/TabPane.js b/plugins/talk-plugin-featured/client/components/TabPane.js new file mode 100644 index 000000000..7e6f5834b --- /dev/null +++ b/plugins/talk-plugin-featured/client/components/TabPane.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default ({asset: {recentComments}}) => ( +
+ {recentComments.map((comment) => ( +

+

{comment.user.username}
+
+ {comment.body} +
+

+ ))} +
+); diff --git a/plugins/talk-plugin-featured/client/containers/Tab.js b/plugins/talk-plugin-featured/client/containers/Tab.js new file mode 100644 index 000000000..2a38933c6 --- /dev/null +++ b/plugins/talk-plugin-featured/client/containers/Tab.js @@ -0,0 +1,16 @@ +import {compose, gql} from 'react-apollo'; +import withFragments from 'coral-framework/hocs/withFragments'; +import Tab from '../components/Tab'; + +const enhance = compose( + withFragments({ + asset: gql` + fragment TalkFeatured_Tab_asset on Asset { + recentComments { + id + } + }`, + }), +); + +export default enhance(Tab); diff --git a/plugins/talk-plugin-featured/client/containers/TabPane.js b/plugins/talk-plugin-featured/client/containers/TabPane.js new file mode 100644 index 000000000..13046cb00 --- /dev/null +++ b/plugins/talk-plugin-featured/client/containers/TabPane.js @@ -0,0 +1,21 @@ +import {compose, gql} from 'react-apollo'; +import withFragments from 'coral-framework/hocs/withFragments'; +import TabPane from '../components/TabPane'; + +const enhance = compose( + withFragments({ + asset: gql` + fragment TalkFeatured_TabPane_asset on Asset { + recentComments { + id + body + user { + id + username + } + } + }`, + }), +); + +export default enhance(TabPane); diff --git a/plugins/talk-plugin-featured/client/index.js b/plugins/talk-plugin-featured/client/index.js new file mode 100644 index 000000000..69dfaa6cb --- /dev/null +++ b/plugins/talk-plugin-featured/client/index.js @@ -0,0 +1,9 @@ +import Tab from './containers/Tab'; +import TabPane from './containers/TabPane'; + +export default { + slots: { + streamTabs: [Tab], + streamTabPanes: [TabPane], + } +}; diff --git a/plugins/talk-plugin-featured/index.js b/plugins/talk-plugin-featured/index.js new file mode 100644 index 000000000..85dfb349b --- /dev/null +++ b/plugins/talk-plugin-featured/index.js @@ -0,0 +1,2 @@ +module.exports = {}; + From 34a4a005d1b5600387916a15aa1b1c512c8b8e25 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 21:12:52 +0700 Subject: [PATCH 06/11] Add margin to highlighted comment --- .../src/components/Stream.css | 4 ++ .../src/components/Stream.js | 62 ++++++++++--------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/client/coral-embed-stream/src/components/Stream.css b/client/coral-embed-stream/src/components/Stream.css index afedab0eb..06f1efd1f 100644 --- a/client/coral-embed-stream/src/components/Stream.css +++ b/client/coral-embed-stream/src/components/Stream.css @@ -17,6 +17,10 @@ margin-top: 6px; } +.highlightedContainer { + margin-top: 28px; +} + .tabContainer { margin-top: 28px; } diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 6205a2a10..67e22ab6a 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -167,35 +167,39 @@ class Stream extends React.Component { {/* the highlightedComment is isolated after the user followed a permalink */} {highlightedComment - ? + ? ( +
+ +
+ ) :
Date: Mon, 10 Jul 2017 21:53:55 +0700 Subject: [PATCH 07/11] Don't use div inside p --- plugins/talk-plugin-featured/client/components/TabPane.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/talk-plugin-featured/client/components/TabPane.js b/plugins/talk-plugin-featured/client/components/TabPane.js index 7e6f5834b..54ac013e6 100644 --- a/plugins/talk-plugin-featured/client/components/TabPane.js +++ b/plugins/talk-plugin-featured/client/components/TabPane.js @@ -3,12 +3,13 @@ import React from 'react'; export default ({asset: {recentComments}}) => (
{recentComments.map((comment) => ( -

+

{comment.user.username}
{comment.body}
-

+
+
))}
); From 883dfedfab4db9e4a75abcfe1149c9a4169b8bce Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 21:57:25 +0700 Subject: [PATCH 08/11] Fix new count styles --- .../src/components/NewCount.js | 5 +++-- client/coral-embed-stream/style/default.css | 16 ++-------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/client/coral-embed-stream/src/components/NewCount.js b/client/coral-embed-stream/src/components/NewCount.js index 8f3b77dcb..328c76481 100644 --- a/client/coral-embed-stream/src/components/NewCount.js +++ b/client/coral-embed-stream/src/components/NewCount.js @@ -1,4 +1,5 @@ import React, {PropTypes} from 'react'; +import {Button} from 'coral-ui'; import t from 'coral-framework/services/i18n'; @@ -6,11 +7,11 @@ const NewCount = ({count, loadMore}) => { return
{ count ? - + : null }
; diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 023a2cc3d..2a73c761c 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -422,9 +422,8 @@ button.comment__action-button[disabled], .talk-new-comments { width: 100%; - display: flex; - justify-content: center; - cursor: pointer; + text-align: center; + margin: 4px 0; } .talk-load-more-replies { @@ -433,12 +432,6 @@ button.comment__action-button[disabled], box-sizing: border-box; } -.talk-new-comments { - position: relative; - top: 1.8em; - z-index: 100; -} - .talk-load-more-replies .talk-load-more-button { background-color: transparent; color: #979797; @@ -451,11 +444,6 @@ button.comment__action-button[disabled], color: white; } -.talk-new-comments button.talk-load-more{ - width: initial; -} - - @media (min-device-width : 300px) and (max-device-width : 420px) { .commentActionsLeft.comment__action-container .coral-plugin-replies-reply-button { visibility: collapse; From e10c1f1a1f45d0b8e0c305ed96616081a5d4d723 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 22:45:28 +0700 Subject: [PATCH 09/11] Docs --- client/coral-ui/components/Tab.js | 21 +++++++++++++++++++++ client/coral-ui/components/TabBar.js | 20 ++++++++++++++++++++ client/coral-ui/components/TabContent.js | 10 ++++++++++ client/coral-ui/components/TabCount.js | 20 +++++++++++++++++++- client/coral-ui/components/TabPane.js | 10 ++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) diff --git a/client/coral-ui/components/Tab.js b/client/coral-ui/components/Tab.js index 75b30085a..4fd85b303 100644 --- a/client/coral-ui/components/Tab.js +++ b/client/coral-ui/components/Tab.js @@ -3,6 +3,10 @@ import styles from './Tab.css'; import cn from 'classnames'; import PropTypes from 'prop-types'; +/** + * The `Tab` component is used inside the `TabBar` Component, to + * render tabs. + */ class Tab extends React.Component { handleTabClick = () => { if (this.props.onTabClick) { @@ -69,7 +73,11 @@ class Tab extends React.Component { } Tab.propTypes = { + + // className to be added to the root element. className: PropTypes.string, + + // classNames allows full design customization of the component. classNames: PropTypes.shape({ root: PropTypes.string, rootActive: PropTypes.string, @@ -80,9 +88,22 @@ Tab.propTypes = { buttonSub: PropTypes.string, buttonSubActive: PropTypes.string, }), + + // active indicates that this tab is currently active. + // This is injected by the `TabBar` component. active: PropTypes.bool, + + // onTabClick is fired whenever the tab was clicked. The tabId is passed as + // the first argument. onTabClick: PropTypes.func, + + // Sub indicates that this is a tab of a sub-tab-panel. + // This is injected by the `TabBar` component. sub: PropTypes.bool, + + // `aria-controls` should be set to the `id` of the `TabContent` for accessibility. + // This is injected by the `TabBar` component. + 'aria-controls': PropTypes.string, }; export default Tab; diff --git a/client/coral-ui/components/TabBar.js b/client/coral-ui/components/TabBar.js index 433213a30..1a51c60f2 100644 --- a/client/coral-ui/components/TabBar.js +++ b/client/coral-ui/components/TabBar.js @@ -4,6 +4,10 @@ import cn from 'classnames'; import Tab from './Tab'; import PropTypes from 'prop-types'; +/** + * The `TabBar` component accepts `Tab` components to create + * a tab bar. + */ class TabBar extends React.Component { getRootClassName({className, classNames = {}, sub} = this.props) { @@ -52,15 +56,31 @@ class TabBar extends React.Component { } TabBar.propTypes = { + + // className to be added to the root element. className: PropTypes.string, + + // classNames allows full design customization of the component. classNames: PropTypes.shape({ root: PropTypes.string, rootSub: PropTypes.string, }), + + // classNames to be passed to the children. tabClassNames: Tab.propTypes.classNames, + + // activeTab should be set to the currently active tabId. activeTab: PropTypes.string, + + // onTabClick is fired whenever the tab was clicked. The tabId is passed as + // the first argument. onTabClick: PropTypes.func, + + // Sub indicates that this is a sub-tab-panel. sub: PropTypes.bool, + + // `aria-controls` should be set to the `id` of the `TabContent` for accessibility. + 'aria-controls': PropTypes.string, }; export default TabBar; diff --git a/client/coral-ui/components/TabContent.js b/client/coral-ui/components/TabContent.js index 768000cb6..99676f7cc 100644 --- a/client/coral-ui/components/TabContent.js +++ b/client/coral-ui/components/TabContent.js @@ -7,6 +7,10 @@ function getRootClassName(className) { return cn('talk-tab-content', className, styles.root); } +/** + * The `TabContent` component accepts `TabPane` components to render + * the content of a `Tab`. + */ const TabContent = ({children, className, activeTab, sub, ...rest}) => (
( ); TabContent.propTypes = { + + // className to be added to the root element. className: PropTypes.string, + + // activeTab should be set to the currently active tabId. activeTab: PropTypes.string, + + // Sub indicates that this component belongs to a sub-tab-panel. sub: PropTypes.bool, }; diff --git a/client/coral-ui/components/TabCount.js b/client/coral-ui/components/TabCount.js index 89ccf1479..01a3840a9 100644 --- a/client/coral-ui/components/TabCount.js +++ b/client/coral-ui/components/TabCount.js @@ -1,6 +1,7 @@ import React from 'react'; import cn from 'classnames'; import styles from './TabCount.css'; +import PropTypes from 'prop-types'; function getNumber(no) { let result = Number.parseInt(no); @@ -24,6 +25,23 @@ function getRootClassName({className, active, sub}) { ); } -export default ({children, active, sub, className}) => ( +/** + * The `TabCount` renders a count number next to a tab name. + */ +const TabCount = ({children, active, sub, className}) => ( {getNumber(children)} ); + +TabCount.propTypes = { + + // className to be added to the root element. + className: PropTypes.string, + + // active indicates that the related tab is currently active. + active: PropTypes.bool, + + // Sub indicates that this component belongs to a sub-tab-panel. + sub: PropTypes.bool, +}; + +export default TabCount; diff --git a/client/coral-ui/components/TabPane.js b/client/coral-ui/components/TabPane.js index 1bdf19f0a..16750b335 100644 --- a/client/coral-ui/components/TabPane.js +++ b/client/coral-ui/components/TabPane.js @@ -6,6 +6,10 @@ function getRootClassName(className) { return cn('talk-pane', className); } +/** + * The `TabPane` component is used inside the `TabContent` component to render + * the content of a `Tab`. + */ const TabPane = ({children, className, tabId: _a, sub: _b, ...rest}) => (
( ); TabPane.propTypes = { + + // className to be added to the root element. className: PropTypes.string, + tabId: PropTypes.string, + + // Sub indicates that this component belongs to a sub-tab-panel. + // This is injected by the `TabContent` component. sub: PropTypes.bool, }; From 8960f67a4ddc337a8450959e87ce74fd8bcb14a7 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 22:49:06 +0700 Subject: [PATCH 10/11] Add todo comments --- plugins/talk-plugin-featured/client/components/Tab.js | 1 + plugins/talk-plugin-featured/client/components/TabPane.js | 1 + plugins/talk-plugin-featured/client/containers/Tab.js | 1 + plugins/talk-plugin-featured/client/containers/TabPane.js | 1 + 4 files changed, 4 insertions(+) diff --git a/plugins/talk-plugin-featured/client/components/Tab.js b/plugins/talk-plugin-featured/client/components/Tab.js index f5306ebaa..af11368b9 100644 --- a/plugins/talk-plugin-featured/client/components/Tab.js +++ b/plugins/talk-plugin-featured/client/components/Tab.js @@ -1,6 +1,7 @@ import React from 'react'; import {TabCount} from 'plugin-api/beta/client/components/ui'; +// TODO: This is just example code, and needs to replaced by an actual implementation. export default ({active, asset: {recentComments}}) => ( Featured {recentComments.length} diff --git a/plugins/talk-plugin-featured/client/components/TabPane.js b/plugins/talk-plugin-featured/client/components/TabPane.js index 54ac013e6..107c806b4 100644 --- a/plugins/talk-plugin-featured/client/components/TabPane.js +++ b/plugins/talk-plugin-featured/client/components/TabPane.js @@ -1,5 +1,6 @@ import React from 'react'; +// TODO: This is just example code, and needs to replaced by an actual implementation. export default ({asset: {recentComments}}) => (
{recentComments.map((comment) => ( diff --git a/plugins/talk-plugin-featured/client/containers/Tab.js b/plugins/talk-plugin-featured/client/containers/Tab.js index 2a38933c6..0bcb49e13 100644 --- a/plugins/talk-plugin-featured/client/containers/Tab.js +++ b/plugins/talk-plugin-featured/client/containers/Tab.js @@ -2,6 +2,7 @@ import {compose, gql} from 'react-apollo'; import withFragments from 'coral-framework/hocs/withFragments'; import Tab from '../components/Tab'; +// TODO: This is just example code, and needs to replaced by an actual implementation. const enhance = compose( withFragments({ asset: gql` diff --git a/plugins/talk-plugin-featured/client/containers/TabPane.js b/plugins/talk-plugin-featured/client/containers/TabPane.js index 13046cb00..19c4d855d 100644 --- a/plugins/talk-plugin-featured/client/containers/TabPane.js +++ b/plugins/talk-plugin-featured/client/containers/TabPane.js @@ -2,6 +2,7 @@ import {compose, gql} from 'react-apollo'; import withFragments from 'coral-framework/hocs/withFragments'; import TabPane from '../components/TabPane'; +// TODO: This is just example code, and needs to replaced by an actual implementation. const enhance = compose( withFragments({ asset: gql` From b05a6c00ce5ab7e58d117684261749fb1e5e9856 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 11 Jul 2017 20:37:19 +0700 Subject: [PATCH 11/11] Correct aria-controls --- client/coral-embed-stream/src/components/Embed.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 85c8d3ca5..57632d199 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -35,7 +35,7 @@ export default class Embed extends React.Component { onTabClick={this.changeTab} activeTab={activeTab} className='talk-stream-tabbar' - aria-controls='talk-embed-tab' + aria-controls='talk-embed-stream-tab-content' > {t('embed_comments_tab')} @@ -53,7 +53,7 @@ export default class Embed extends React.Component {