diff --git a/.eslintignore b/.eslintignore
index b58e4f058..afa7bf16d 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -13,5 +13,6 @@ plugins/*
!plugins/coral-plugin-viewing-options
!plugins/coral-plugin-comment-content
!plugins/talk-plugin-permalink
-!plugins/talk-plugin-featured
+!plugins/talk-plugin-featured-comments
+
node_modules
diff --git a/.gitignore b/.gitignore
index 0e809b43a..bd8edcd94 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,6 @@ plugins/*
!plugins/coral-plugin-viewing-options
!plugins/coral-plugin-comment-content
!plugins/talk-plugin-permalink
-!plugins/talk-plugin-featured
+!plugins/talk-plugin-featured-comments
**/node_modules/*
diff --git a/client/coral-embed-stream/src/actions/stream.js b/client/coral-embed-stream/src/actions/stream.js
index d936879e9..7250b86f3 100644
--- a/client/coral-embed-stream/src/actions/stream.js
+++ b/client/coral-embed-stream/src/actions/stream.js
@@ -1,35 +1,24 @@
import pym from 'coral-framework/services/pym';
import * as actions from '../constants/stream';
+import {buildUrl} from 'coral-framework/utils';
+import queryString from 'query-string';
export const setActiveReplyBox = (id) => ({type: actions.SET_ACTIVE_REPLY_BOX, id});
-function removeParam(key, sourceURL) {
- let rtn = sourceURL.split('?')[0];
- let param;
- let params_arr = [];
- let queryString = (sourceURL.indexOf('?') !== -1) ? sourceURL.split('?')[1] : '';
- if (queryString !== '') {
- params_arr = queryString.split('&');
- for (let i = params_arr.length - 1; i >= 0; i -= 1) {
- param = params_arr[i].split('=')[0];
- if (param === key) {
- params_arr.splice(i, 1);
- }
- }
- rtn = `${rtn}?${params_arr.join('&')}`;
- }
- return rtn;
-}
-
export const viewAllComments = () => {
+ const search = queryString.stringify({
+ ...queryString.parse(location.search),
+ comment_id: undefined,
+ });
+
// remove the comment_id url param
- const modifiedUrl = removeParam('comment_id', location.href);
+ const url = buildUrl({...location, search});
try {
// "window" here refers to the embedded iframe
- window.history.replaceState({}, document.title, modifiedUrl);
+ window.history.replaceState({}, document.title, url);
// also change the parent url
pym.sendMessage('coral-view-all-comments');
@@ -38,6 +27,28 @@ export const viewAllComments = () => {
return {type: actions.VIEW_ALL_COMMENTS};
};
+export const viewComment = (id) => {
+
+ const search = queryString.stringify({
+ ...queryString.parse(location.search),
+ comment_id: id,
+ });
+
+ // remove the comment_id url param
+ const url = buildUrl({...location, search});
+
+ try {
+
+ // "window" here refers to the embedded iframe
+ window.history.replaceState({}, document.title, url);
+
+ // also change the parent url
+ pym.sendMessage('coral-view-comment', id);
+ } catch (e) { /* not sure if we're worried about old browsers */ }
+
+ return {type: actions.VIEW_COMMENT, id};
+};
+
export const addCommentClassName = (className) => ({
type: actions.ADD_COMMENT_CLASSNAME,
className
diff --git a/client/coral-embed-stream/src/components/AllCommentsPane.js b/client/coral-embed-stream/src/components/AllCommentsPane.js
index 6af72475a..7166aa6e9 100644
--- a/client/coral-embed-stream/src/components/AllCommentsPane.js
+++ b/client/coral-embed-stream/src/components/AllCommentsPane.js
@@ -125,8 +125,6 @@ class AllCommentsPane extends React.Component {
root,
comments,
commentClassNames,
- addTag,
- removeTag,
ignoreUser,
setActiveReplyBox,
activeReplyBox,
@@ -173,8 +171,6 @@ class AllCommentsPane extends React.Component {
currentUser={currentUser}
postFlag={postFlag}
postDontAgree={postDontAgree}
- addTag={addTag}
- removeTag={removeTag}
ignoreUser={ignoreUser}
commentIsIgnored={commentIsIgnored}
loadMore={loadNewReplies}
diff --git a/client/coral-embed-stream/src/components/Comment.css b/client/coral-embed-stream/src/components/Comment.css
index 9a176534d..7eb2ece71 100644
--- a/client/coral-embed-stream/src/components/Comment.css
+++ b/client/coral-embed-stream/src/components/Comment.css
@@ -17,8 +17,6 @@
.comment {
padding-left: 15px;
- display: flex;
- flex-flow: row;
}
.commentLevel0 {
@@ -67,17 +65,6 @@
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}
-/* element in the top right of the Comment */
-.topRight {
- float: right;
- margin-top: 10px;
- text-align: right;
-}
-
-.topRight > * {
- text-align: initial;
-}
-
.topRight .popover {
margin-top: 1em;
right: 0px;
@@ -89,11 +76,6 @@
border-bottom: 2px solid currentColor;
}
-.topRightMenu {
- cursor: pointer;
- margin-top: 5px;
-}
-
.editCommentForm {
margin-bottom: 10px;
}
@@ -144,7 +126,7 @@
}
.commentInfoBar {
- float: right;
+ margin-left: auto;
}
@keyframes enter {
@@ -158,7 +140,6 @@
}
.commentContainer {
- flex: auto;
}
.commentAvatar {
@@ -173,3 +154,19 @@
vertical-align: middle;
font-size: 14px;
}
+
+.commentFooter {
+ padding-top: 8px;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+}
+
+.content {
+}
+
+.footer {
+
+}
diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js
index 9e6a70efe..8e0539279 100644
--- a/client/coral-embed-stream/src/components/Comment.js
+++ b/client/coral-embed-stream/src/components/Comment.js
@@ -1,4 +1,5 @@
-import React, {PropTypes} from 'react';
+import React from 'react';
+import PropTypes from 'prop-types';
import AuthorName from 'coral-plugin-author-name/AuthorName';
import TagLabel from 'coral-plugin-tag-label/TagLabel';
@@ -13,13 +14,6 @@ import {THREADING_LEVEL} from '../constants/stream';
import merge from 'lodash/merge';
import mapValues from 'lodash/mapValues';
-import {
- BestButton,
- IfUserCanModifyBest,
- BEST_TAG,
- commentIsBest,
- BestIndicator
-} from 'coral-plugin-best/BestButton';
import LoadMore from './LoadMore';
import {getEditableUntilDate} from './util';
import {TopRightMenu} from './TopRightMenu';
@@ -178,19 +172,13 @@ export default class Comment extends React.Component {
}).isRequired,
// given a comment, return whether it should be rendered as ignored
- commentIsIgnored: React.PropTypes.func,
-
- // dispatch action to add a tag to a comment
- addTag: React.PropTypes.func,
-
- // dispatch action to remove a tag from a comment
- removeTag: React.PropTypes.func,
+ commentIsIgnored: PropTypes.func,
// dispatch action to ignore another user
- ignoreUser: React.PropTypes.func,
+ ignoreUser: PropTypes.func,
// edit a comment, passed (id, asset_id, { body })
- editComment: React.PropTypes.func,
+ editComment: PropTypes.func,
}
editComment = (...args) => {
@@ -303,6 +291,8 @@ export default class Comment extends React.Component {
render () {
const {
asset,
+ data,
+ root,
depth,
comment,
postFlag,
@@ -321,8 +311,6 @@ export default class Comment extends React.Component {
addNotification,
charCountEnable,
showSignInDialog,
- addTag,
- removeTag,
liveUpdates,
commentIsIgnored,
commentClassNames = []
@@ -347,40 +335,6 @@ export default class Comment extends React.Component {
myFlag = dontAgreeSummary.find((s) => s.current_user);
}
- // call a function, and if it errors, call addNotification('error', ...) (e.g. to show user a snackbar)
- const notifyOnError = (fn, errorToMessage) =>
- async function(...args) {
- if (typeof errorToMessage !== 'function') {
- errorToMessage = (error) => error.message;
- }
- try {
- return await fn(...args);
- } catch (error) {
- addNotification('error', errorToMessage(error));
- throw error;
- }
- };
-
- const addBestTag = notifyOnError(
- () =>
- addTag({
- id: comment.id,
- name: BEST_TAG,
- assetId: asset.id,
- }),
- () => 'Failed to tag comment as best'
- );
-
- const removeBestTag = notifyOnError(
- () =>
- removeTag({
- id: comment.id,
- name: BEST_TAG,
- assetId: asset.id,
- }),
- () => 'Failed to remove best comment tag'
- );
-
/**
* conditionClassNames
* adds classNames based on condition
@@ -424,6 +378,15 @@ export default class Comment extends React.Component {
}
);
+ // props that are passed down the slots.
+ const slotProps = {
+ data,
+ root,
+ asset,
+ comment,
+ depth,
+ };
+
return (
-
-
- {isStaff(comment.tags) ?
Staff : null}
- {commentIsBest(comment)
- ?
- : null }
+
+
+ {isStaff(comment.tags) ?
Staff : null}
-
-
- {
- (comment.editing && comment.editing.edited)
- ? ({t('comment.edited')})
- : null
- }
-
-
-
-
- { (currentUser && (comment.user.id === currentUser.id)) &&
-
- /* User can edit/delete their own comment for a short window after posting */
-
+
+
{
- commentIsStillEditable(comment) &&
- Edit
+ (comment.editing && comment.editing.edited)
+ ? ({t('comment.edited')})
+ : null
}
- }
- { (currentUser && (comment.user.id !== currentUser.id)) &&
- /* TopRightMenu allows currentUser to ignore other users' comments */
-
-
+
+
+ { (currentUser && (comment.user.id === currentUser.id)) &&
+
+ /* User can edit/delete their own comment for a short window after posting */
+
+ {
+ commentIsStillEditable(comment) &&
+ Edit
+ }
- }
- {
- this.state.isEditing
- ?
- :
-
-
- }
+ }
+ { (currentUser && (comment.user.id !== currentUser.id)) &&
-
-
-
-
-
-
-
- {!disableReply &&
-
-
- }
+ /* TopRightMenu allows currentUser to ignore other users' comments */
+
+
+
+ }
-
+
+
+
-
+ {!disableReply &&
+
+
+ }
+
+
- {activeReplyBox === comment.id
- ? {
- setActiveReplyBox('');
- }}
+ {activeReplyBox === comment.id
+ ? {
+ setActiveReplyBox('');
+ }}
+ charCountEnable={charCountEnable}
+ maxCharCount={maxCharCount}
+ setActiveReplyBox={setActiveReplyBox}
+ parentId={(depth < THREADING_LEVEL) ? comment.id : parentId}
+ addNotification={addNotification}
+ postComment={postComment}
+ currentUser={currentUser}
+ assetId={asset.id}
+ />
+ : null}
+
+
+ {view.map((reply) => {
+ return commentIsIgnored(reply)
+ ?
+ :
- : null}
-
-
- {view.map((reply) => {
- return commentIsIgnored(reply)
- ?
- : ;
- })}
-
-
-
+ showSignInDialog={showSignInDialog}
+ commentIsIgnored={commentIsIgnored}
+ liveUpdates={liveUpdates}
+ reactKey={reply.id}
+ key={reply.id}
+ comment={reply}
+ />;
+ })}
+
+
+
);
diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js
index 5fde7c1df..2a1381862 100644
--- a/client/coral-embed-stream/src/components/Stream.js
+++ b/client/coral-embed-stream/src/components/Stream.js
@@ -1,4 +1,5 @@
-import React, {PropTypes} from 'react';
+import React from 'react';
+import PropTypes from 'prop-types';
import {StreamError} from './StreamError';
import Comment from '../components/Comment';
import SuspendedAccount from './SuspendedAccount';
@@ -35,6 +36,29 @@ class Stream extends React.Component {
if (!this.userIsDegraged(this.props) && this.userIsDegraged(next)) {
this.setState({keepCommentBox: true});
}
+
+ this.fallbackAllTab(next);
+ }
+
+ componentDidMount() {
+ this.fallbackAllTab();
+ }
+
+ fallbackAllTab(props = this.props) {
+ if (props.activeStreamTab !== 'all') {
+ const slotPlugins = this.getSlotComponents('streamTabs', props).map((c) => c.talkPluginName);
+ if (slotPlugins.indexOf(props.activeStreamTab) === -1) {
+ props.setActiveStreamTab('all');
+ }
+ }
+ }
+
+ getSlotProps({data, root, root: {asset}} = this.props) {
+ return {data, root, asset};
+ }
+
+ getSlotComponents(slot, props = this.props) {
+ return getSlotComponents(slot, props.reduxState, this.getSlotProps(props));
}
setActiveReplyBox = (id) => {
@@ -66,7 +90,6 @@ class Stream extends React.Component {
deleteAction,
showSignInDialog,
updateItem,
- addTag,
ignoreUser,
activeStreamTab,
setActiveStreamTab,
@@ -74,8 +97,6 @@ class Stream extends React.Component {
loadMoreComments,
viewAllComments,
auth: {loggedIn, user},
- removeTag,
- reduxState,
editName
} = this.props;
const {keepCommentBox} = this.state;
@@ -99,7 +120,7 @@ class Stream extends React.Component {
};
const showCommentBox = loggedIn && ((!banned && !temporarilySuspended && !highlightedComment) || keepCommentBox);
- const streamTabProps = {data, root, asset};
+ const slotProps = this.getSlotProps();
if (!comment && !comments) {
console.error('Talk: No comments came back from the graph given that query. Please, check the query params.');
@@ -158,7 +179,10 @@ class Stream extends React.Component {
: {asset.settings.closedMessage}
}
-
+
{loggedIn && (
-
+
- {getSlotComponents('streamTabs', reduxState, streamTabProps).map((PluginComponent) => (
+ {this.getSlotComponents('streamTabs').map((PluginComponent) => (
@@ -222,10 +247,10 @@ class Stream extends React.Component {
- {getSlotComponents('streamTabPanes', reduxState, streamTabProps).map((PluginComponent) => (
+ {this.getSlotComponents('streamTabPanes').map((PluginComponent) => (
))}
@@ -235,8 +260,6 @@ class Stream extends React.Component {
root={root}
comments={comments}
commentClassNames={commentClassNames}
- addTag={addTag}
- removeTag={removeTag}
ignoreUser={ignoreUser}
setActiveReplyBox={setActiveReplyBox}
activeReplyBox={activeReplyBox}
@@ -269,12 +292,6 @@ Stream.propTypes = {
addNotification: PropTypes.func.isRequired,
postComment: PropTypes.func.isRequired,
- // dispatch action to add a tag to a comment
- addTag: PropTypes.func,
-
- // dispatch action to remove a tag from a comment
- removeTag: PropTypes.func,
-
// dispatch action to ignore another user
ignoreUser: React.PropTypes.func,
diff --git a/client/coral-embed-stream/src/components/Toggleable.css b/client/coral-embed-stream/src/components/Toggleable.css
index 0ce8a119b..bbdd5e3d6 100644
--- a/client/coral-embed-stream/src/components/Toggleable.css
+++ b/client/coral-embed-stream/src/components/Toggleable.css
@@ -2,21 +2,25 @@
outline: none;
}
-/**
+.toggler {
+ composes: buttonReset from "coral-framework/styles/reset.css";
+}
+
+/**
* Up/Down Chevrons for the top right menu
*/
.chevron {
}
.chevron:before {
content: '⌃';
- display: inline-block;
+ display: inline-block;
position: relative;
- top: 0.25em;
+ top: 0.25em;
}
-/* Down Arrow */
+/* Down Arrow */
.chevron.down:before {
- display: inline-block;
+ display: inline-block;
position: relative;
transform: rotate(180deg);
top: 0;
diff --git a/client/coral-embed-stream/src/components/Toggleable.js b/client/coral-embed-stream/src/components/Toggleable.js
index c7996d866..1c70f2468 100644
--- a/client/coral-embed-stream/src/components/Toggleable.js
+++ b/client/coral-embed-stream/src/components/Toggleable.js
@@ -27,9 +27,8 @@ export default class Toggleable extends React.Component {
const {isOpen} = this.state;
return (
-
- {isOpen ? upArrow : downArrow}
+
+
{isOpen ? children : null}
diff --git a/client/coral-embed-stream/src/constants/stream.js b/client/coral-embed-stream/src/constants/stream.js
index 622a86c18..7ac9427cf 100644
--- a/client/coral-embed-stream/src/constants/stream.js
+++ b/client/coral-embed-stream/src/constants/stream.js
@@ -1,6 +1,7 @@
export const SET_ACTIVE_REPLY_BOX = 'SET_ACTIVE_REPLY_BOX';
export const ADDTL_COMMENTS_ON_LOAD_MORE = 10;
export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS';
+export const VIEW_COMMENT = 'VIEW_COMMENT';
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;
diff --git a/client/coral-embed-stream/src/containers/Comment.js b/client/coral-embed-stream/src/containers/Comment.js
index 479c6f302..5d43e9eb7 100644
--- a/client/coral-embed-stream/src/containers/Comment.js
+++ b/client/coral-embed-stream/src/containers/Comment.js
@@ -21,12 +21,19 @@ export default withFragments({
${getSlotFragmentSpreads(slots, 'root')}
}
`,
+ asset: gql`
+ fragment CoralEmbedStream_Comment_asset on Asset {
+ __typename
+ ${getSlotFragmentSpreads(slots, 'asset')}
+ }
+ `,
comment: gql`
fragment CoralEmbedStream_Comment_comment on Comment {
id
body
created_at
status
+ replyCount
tags {
tag {
name
diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js
index aa0700ad5..a31dbe911 100644
--- a/client/coral-embed-stream/src/containers/Embed.js
+++ b/client/coral-embed-stream/src/containers/Embed.js
@@ -94,7 +94,7 @@ class EmbedContainer extends React.Component {
if (!prevProps.root.comment && this.props.root.comment) {
// Scroll to a permalinked comment if one is in the URL once the page is done rendering.
- setTimeout(() => pym.scrollParentToChildEl('coralStream'), 0);
+ setTimeout(() => pym.scrollParentToChildEl('talk-embed-stream-container'), 0);
}
}
diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js
index 6be8b9ee6..ea0676e51 100644
--- a/client/coral-embed-stream/src/containers/Stream.js
+++ b/client/coral-embed-stream/src/containers/Stream.js
@@ -4,8 +4,8 @@ import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {ADDTL_COMMENTS_ON_LOAD_MORE, THREADING_LEVEL} from '../constants/stream';
import {
- withPostComment, withPostFlag, withPostDontAgree, withDeleteAction,
- withAddTag, withRemoveTag, withIgnoreUser, withEditComment,
+ withPostComment, withPostFlag, withPostDontAgree,
+ withDeleteAction, withIgnoreUser, withEditComment
} from 'coral-framework/graphql/mutations';
import * as authActions from 'coral-framework/actions/auth';
@@ -159,7 +159,6 @@ const commentFragment = gql`
id
...${getDefinitionName(Comment.fragments.comment)}
${nest(`
- replyCount(excludeIgnored: $excludeIgnored)
replies(excludeIgnored: $excludeIgnored) {
nodes {
id
@@ -210,7 +209,6 @@ const LOAD_MORE_QUERY = gql`
id
...${getDefinitionName(Comment.fragments.comment)}
${nest(`
- replyCount(excludeIgnored: $excludeIgnored)
replies(limit: 3, excludeIgnored: $excludeIgnored) {
nodes {
id
@@ -278,6 +276,7 @@ const fragments = {
endCursor
}
${getSlotFragmentSpreads(slots, 'asset')}
+ ...${getDefinitionName(Comment.fragments.asset)}
}
me {
status
@@ -291,6 +290,7 @@ const fragments = {
${getSlotFragmentSpreads(slots, 'root')}
...${getDefinitionName(Comment.fragments.root)}
}
+ ${Comment.fragments.asset}
${Comment.fragments.root}
${commentFragment}
`,
@@ -329,8 +329,6 @@ export default compose(
withPostComment,
withPostFlag,
withPostDontAgree,
- withAddTag,
- withRemoveTag,
withIgnoreUser,
withDeleteAction,
withEditComment,
diff --git a/client/coral-embed-stream/src/graphql/utils.js b/client/coral-embed-stream/src/graphql/utils.js
index 523cc571f..4f51b0ae2 100644
--- a/client/coral-embed-stream/src/graphql/utils.js
+++ b/client/coral-embed-stream/src/graphql/utils.js
@@ -1,4 +1,6 @@
import update from 'immutability-helper';
+import {insertCommentsSorted} from 'coral-framework/utils';
+
function determineCommentDepth(comment) {
let depth = 0;
let cur = comment;
@@ -142,14 +144,6 @@ export function findCommentInEmbedQuery(root, callbackOrId) {
return findComment(root.asset.comments.nodes, callback);
}
-const ascending = (a, b) => {
- const dateA = new Date(a.created_at);
- const dateB = new Date(b.created_at);
- if (dateA < dateB) { return -1; }
- if (dateA > dateB) { return 1; }
- return 0;
-};
-
function findAndInsertFetchedComments(parent, comments, parent_id) {
const isAsset = parent.__typename === 'Asset';
const connectionField = isAsset ? 'comments' : 'replies';
@@ -162,11 +156,9 @@ function findAndInsertFetchedComments(parent, comments, parent_id) {
if (isAsset) {
return nodes.concat(comments.nodes);
}
- return nodes
- .concat(comments.nodes.filter(
+ return insertCommentsSorted(nodes, comments.nodes.filter(
(comment) => !nodes.some((node) => node.id === comment.id)
- ))
- .sort(ascending);
+ ));
}},
},
});
diff --git a/client/coral-embed-stream/src/reducers/stream.js b/client/coral-embed-stream/src/reducers/stream.js
index 4f4fcf47f..86d5301b7 100644
--- a/client/coral-embed-stream/src/reducers/stream.js
+++ b/client/coral-embed-stream/src/reducers/stream.js
@@ -17,11 +17,11 @@ function getQueryVariable(variable) {
const initialState = {
activeReplyBox: '',
- assetId: getQueryVariable('asset_id'),
- assetUrl: getQueryVariable('asset_url'),
- commentId: getQueryVariable('comment_id'),
+ assetId: getQueryVariable('asset_id') || '',
+ assetUrl: getQueryVariable('asset_url') || '',
+ commentId: getQueryVariable('comment_id') || '',
commentClassNames: [],
- activeTab: 'all',
+ activeTab: process.env.TALK_DEFAULT_STREAM_TAB,
previousTab: '',
};
@@ -48,6 +48,11 @@ export default function stream(state = initialState, action) {
...state,
commentId: '',
};
+ case actions.VIEW_COMMENT:
+ return {
+ ...state,
+ commentId: action.id,
+ };
case actions.ADD_COMMENT_CLASSNAME :
return {
...state,
diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js
index 702308c3a..eb61f3b90 100644
--- a/client/coral-embed/src/index.js
+++ b/client/coral-embed/src/index.js
@@ -1,7 +1,8 @@
import pym from 'pym.js';
import URLSearchParams from 'url-search-params';
-import {stringify} from 'querystring';
+import {buildUrl} from 'coral-framework/utils';
+import queryString from 'query-string';
// TODO: Styles should live in a separate file
const snackbarStyles = {
@@ -39,7 +40,7 @@ function buildStreamIframeUrl(talkBaseUrl, query) {
'embed/stream?'
].join('');
- url += stringify(query);
+ url += queryString.stringify(query);
return url;
}
@@ -98,10 +99,37 @@ function configurePymParent(pymParent, opts) {
// remove the permalink comment id from the hash
pymParent.onMessage('coral-view-all-comments', function() {
+
+ const search = queryString.stringify({
+ ...queryString.parse(location.search),
+ commentId: undefined,
+ });
+
+ // remove the commentId url param
+ const url = buildUrl({...location, search});
+
window.history.replaceState(
{},
document.title,
- location.origin + location.pathname + location.search
+ url,
+ );
+ });
+
+ // remove the permalink comment id from the hash
+ pymParent.onMessage('coral-view-comment', function(id) {
+
+ const search = queryString.stringify({
+ ...queryString.parse(location.search),
+ commentId: id,
+ });
+
+ // remove the commentId url param
+ const url = buildUrl({...location, search});
+
+ window.history.replaceState(
+ {},
+ document.title,
+ url,
);
});
@@ -193,12 +221,18 @@ Talk.render = function(el, opts) {
let urlParams = new URLSearchParams(window.location.search);
- query.comment_id = urlParams.get('commentId');
+ if (urlParams.get('commentId')) {
+ query.comment_id = urlParams.get('commentId');
+ }
- query.asset_id = opts.asset_id;
+ if (opts.asset_id) {
+ query.asset_id = opts.asset_id;
+ }
- query.asset_url = opts.asset_url;
- if (!query.asset_url) {
+ if (opts.asset_url) {
+ query.asset_url = opts.asset_url;
+ }
+ else {
try {
query.asset_url = document.querySelector('link[rel="canonical"]').href;
} catch (e) {
diff --git a/client/coral-framework/graphql/mutations.js b/client/coral-framework/graphql/mutations.js
index 3b9ef01f4..91d2ae0ab 100644
--- a/client/coral-framework/graphql/mutations.js
+++ b/client/coral-framework/graphql/mutations.js
@@ -1,6 +1,117 @@
import {gql} from 'react-apollo';
import withMutation from '../hocs/withMutation';
+function convertItemType(item_type) {
+ switch (item_type) {
+ case 'COMMENTS':
+ return 'Comment';
+ case 'USERS':
+ return 'User';
+ case 'ASSETS':
+ return 'Asset';
+ default:
+ throw new Error(`Unknown item_type ${item_type}`);
+ }
+}
+
+function getTagFragment(item_type) {
+ return gql`
+ fragment Coral_UpdateFragment on ${convertItemType(item_type)} {
+ tags {
+ tag {
+ name
+ }
+ }
+ }
+ `;
+}
+
+export const withAddTag = withMutation(
+ gql`
+ mutation AddTag($id: ID!, $asset_id: ID!, $name: String!, $item_type: TAGGABLE_ITEM_TYPE!) {
+ addTag(tag: {name: $name, id: $id, item_type: $item_type, asset_id: $asset_id}) {
+ ...ModifyTagResponse
+ }
+ }
+ `, {
+ props: ({mutate}) => ({
+ addTag: ({id, name, assetId, itemType}) => {
+ return mutate({
+ variables: {
+ id,
+ name,
+ asset_id: assetId,
+ item_type: itemType,
+ },
+ optimisticResponse: {
+ addTag: {
+ __typename: 'ModifyTagResponse',
+ errors: null,
+ }
+ },
+ update: (proxy) => {
+ const fragmentId = `${convertItemType(itemType)}_${id}`;
+ const fragment = getTagFragment(itemType);
+
+ // Read the data from our cache for this query.
+ const data = proxy.readFragment({fragment, id: fragmentId});
+
+ data.tags.push({
+ tag: {
+ __typename: 'Tag',
+ name
+ },
+ __typename: 'TagLink'
+ });
+
+ // Write our data back to the cache.
+ proxy.writeFragment({fragment, id: fragmentId, data});
+ },
+ });
+ }}),
+ });
+
+export const withRemoveTag = withMutation(
+ gql`
+ mutation RemoveTag($id: ID!, $asset_id: ID!, $name: String!, $item_type: TAGGABLE_ITEM_TYPE!) {
+ removeTag(tag: {name: $name, id: $id, item_type: $item_type, asset_id: $asset_id}) {
+ ...ModifyTagResponse
+ }
+ }
+ `, {
+ props: ({mutate}) => ({
+ removeTag: ({id, name, assetId, itemType}) => {
+ return mutate({
+ variables: {
+ id,
+ name,
+ asset_id: assetId,
+ item_type: itemType,
+ },
+ optimisticResponse: {
+ removeTag: {
+ __typename: 'ModifyTagResponse',
+ errors: null,
+ }
+ },
+ update: (proxy) => {
+ const fragmentId = `${convertItemType(itemType)}_${id}`;
+ const fragment = getTagFragment(itemType);
+
+ // Read the data from our cache for this query.
+ const data = proxy.readFragment({fragment, id: fragmentId});
+
+ const idx = data.tags.findIndex((i) => i.tag.name === name);
+
+ data.tags = [...data.tags.slice(0, idx), ...data.tags.slice(idx + 1)];
+
+ // Write our data back to the cache.
+ proxy.writeFragment({fragment, id: fragmentId, data});
+ }
+ });
+ }}),
+ });
+
export const withSetCommentStatus = withMutation(
gql`
mutation SetCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){
@@ -173,98 +284,6 @@ export const withDeleteAction = withMutation(
}}),
});
-const COMMENT_FRAGMENT = gql`
- fragment CoralBest_UpdateFragment on Comment {
- tags {
- tag {
- name
- }
- }
- }
- `;
-
-export const withAddTag = withMutation(
- gql`
- mutation AddTag($id: ID!, $asset_id: ID!, $name: String!) {
- addTag(tag: {name: $name, id: $id, item_type: COMMENTS, asset_id: $asset_id}) {
- ...ModifyTagResponse
- }
- }
- `, {
- props: ({mutate}) => ({
- addTag: ({id, name, assetId}) => {
- return mutate({
- variables: {
- id,
- name,
- asset_id: assetId
- },
- optimisticResponse: {
- addTag: {
- __typename: 'ModifyTagResponse',
- errors: null,
- }
- },
- update: (proxy) => {
- const fragmentId = `Comment_${id}`;
-
- // Read the data from our cache for this query.
- const data = proxy.readFragment({fragment: COMMENT_FRAGMENT, id: fragmentId});
-
- data.tags.push({
- tag: {
- __typename: 'Tag',
- name: 'BEST'
- },
- __typename: 'TagLink'
- });
-
- // Write our data back to the cache.
- proxy.writeFragment({fragment: COMMENT_FRAGMENT, id: fragmentId, data});
- },
- });
- }}),
- });
-
-export const withRemoveTag = withMutation(
- gql`
- mutation RemoveTag($id: ID!, $asset_id: ID!, $name: String!) {
- removeTag(tag: {name: $name, id: $id, item_type: COMMENTS, asset_id: $asset_id}) {
- ...ModifyTagResponse
- }
- }
- `, {
- props: ({mutate}) => ({
- removeTag: ({id, name, assetId}) => {
- return mutate({
- variables: {
- id,
- name,
- asset_id: assetId
- },
- optimisticResponse: {
- removeTag: {
- __typename: 'ModifyTagResponse',
- errors: null,
- }
- },
- update: (proxy) => {
- const fragmentId = `Comment_${id}`;
-
- // Read the data from our cache for this query.
- const data = proxy.readFragment({fragment: COMMENT_FRAGMENT, id: fragmentId});
-
- const idx = data.tags.findIndex((i) => i.tag.name === 'BEST');
-
- data.tags = [...data.tags.slice(0, idx), ...data.tags.slice(idx + 1)];
-
- // Write our data back to the cache.
- proxy.writeFragment({fragment: COMMENT_FRAGMENT, id: fragmentId, data});
- }
- });
- }}),
- });
-
export const withIgnoreUser = withMutation(
gql`
mutation IgnoreUser($id: ID!) {
diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js
index cc8147c19..1af2a9a59 100644
--- a/client/coral-framework/helpers/plugins.js
+++ b/client/coral-framework/helpers/plugins.js
@@ -2,12 +2,12 @@ import React from 'react';
import uniq from 'lodash/uniq';
import pick from 'lodash/pick';
import merge from 'lodash/merge';
-import plugins from 'pluginsConfig';
-import flatten from 'lodash/flatten';
import flattenDeep from 'lodash/flattenDeep';
+import flatten from 'lodash/flatten';
import {loadTranslations} from 'coral-framework/services/i18n';
import {injectReducers} from 'coral-framework/services/store';
import camelize from './camelize';
+import plugins from 'pluginsConfig';
export function getSlotComponents(slot, reduxState, props = {}) {
const pluginConfig = reduxState.config.pluginConfig || {};
diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js
index 61413e03d..3768b3940 100644
--- a/client/coral-framework/utils/index.js
+++ b/client/coral-framework/utils/index.js
@@ -146,6 +146,38 @@ export function forEachError(error, callback) {
});
}
+const ascending = (a, b) => {
+ const dateA = new Date(a.created_at);
+ const dateB = new Date(b.created_at);
+ if (dateA < dateB) { return -1; }
+ if (dateA > dateB) { return 1; }
+ return 0;
+};
+
+const descending = (a, b) => ascending(a, b) * -1;
+
+export function insertCommentsSorted(nodes, comments, sortOrder = 'CHRONOLOGICAL') {
+ const added = nodes.concat(comments);
+ if (sortOrder === 'CHRONOLOGICAL') {
+ return added.sort(ascending);
+ }
+ if (sortOrder === 'REVERSE_CHRONOLOGICAL') {
+ return added.sort(descending);
+ }
+ throw new Error(`Unknown sort order ${sortOrder}`);
+}
+
+export const isTagged = (tags, which) => tags.some((t) => t.tag.name === which);
+
+export function buildUrl({protocol, hostname, port, pathname, search, hash} = window.location) {
+ if (search && search[0] !== '?') {
+ search = `?${search}`;
+ } else if (search === '?') {
+ search = '';
+ }
+ return `${protocol}//${hostname}${port ? `:${port}` : ''}${pathname}${search}${hash}`;
+}
+
/**
* getSlotFragmentSpreads will return a string in the
* expected format for slot fragments, given `slots` and `resource`.
diff --git a/client/coral-plugin-best/BestButton.css b/client/coral-plugin-best/BestButton.css
deleted file mode 100644
index ca77f01f2..000000000
--- a/client/coral-plugin-best/BestButton.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.button {
- composes: buttonReset from "coral-framework/styles/reset.css";
- margin: 5px 10px 5px 0px;
-}
-
-.tagIcon {
- font-size: 12px;
- vertical-align: middle;
-}
-
-.icon {
- font-size: 12px;
- vertical-align: middle;
-}
diff --git a/client/coral-plugin-best/BestButton.js b/client/coral-plugin-best/BestButton.js
deleted file mode 100644
index 34bdad201..000000000
--- a/client/coral-plugin-best/BestButton.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import React, {Component, PropTypes} from 'react';
-
-import t from 'coral-framework/services/i18n';
-
-import {Icon} from 'coral-ui';
-import cn from 'classnames';
-import styles from './BestButton.css';
-
-// tag string for best comments
-export const BEST_TAG = 'BEST';
-
-export const commentIsBest = ({tags} = {}) => tags.some((t) => t.tag.name === BEST_TAG);
-
-const name = 'coral-plugin-best';
-
-// It would be best if the backend/api held this business logic
-const canModifyBestTag = ({roles = []} = {}) => roles && ['ADMIN', 'MODERATOR'].some((role) => roles.includes(role));
-
-// Put this on a comment to show that it is best
-
-export const BestIndicator = ({children = }) => (
-
- { children }
-
-);
-
-/**
- * Component that only renders children if the provided user prop can modify best tags
- */
-export const IfUserCanModifyBest = ({user, children}) => {
- if (!(user && canModifyBestTag(user))) {return null;}
- return children;
-};
-
-/**
- * Button that lets a moderator tag a comment as "Best".
- * Used to recognize really good comments.
- */
-export class BestButton extends Component {
- static propTypes = {
-
- // whether the comment is already tagged as best
- isBest: PropTypes.bool.isRequired,
-
- // set that this comment is best
- addBest: PropTypes.func.isRequired,
-
- // remove the best status
- removeBest: PropTypes.func.isRequired,
- }
-
- constructor(props) {
- super(props);
- this.onClickAddBest = this.onClickAddBest.bind(this);
- this.onClickRemoveBest = this.onClickRemoveBest.bind(this);
- }
-
- state = {
- isSaving: false
- }
-
- async onClickAddBest(e) {
- e.preventDefault();
- const {addBest} = this.props;
- if (!addBest) {
- console.warn('BestButton#onClickAddBest called even though there is no addBest prop. doing nothing');
- return;
- }
- this.setState({isSaving: true});
- try {
- await addBest();
- } finally {
- this.setState({isSaving: false});
- }
- }
-
- async onClickRemoveBest(e) {
- e.preventDefault();
- const {removeBest} = this.props;
- if (!removeBest) {
- console.warn('BestButton#onClickAddBest called even though there is no removeBest prop. doing nothing');
- return;
- }
- this.setState({isSaving: true});
- try {
- await removeBest();
- } finally {
- this.setState({isSaving: false});
- }
- }
-
- render() {
- const {isBest, addBest, removeBest} = this.props;
- const {isSaving} = this.state;
- const disabled = isSaving || !(isBest ? removeBest : addBest);
- return (
-
- );
- }
-}
diff --git a/locales/en.yml b/locales/en.yml
index e3bf42e5e..af985b593 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -32,7 +32,6 @@ en:
comment_post_notif_premod: "Thank you for posting. Our moderation team will review your comment shortly."
comment_post_banned_word: "Your comment contains one or more words that are not permitted, so it will not be published. If you think this message is incorrect, please contact our moderation team."
characters_remaining: "characters remaining"
- comment_is_best: "This comment is one of the best"
comment_offensive: "This comment is offensive"
comment_singular: Comment
comment_plural: Comments
@@ -306,7 +305,6 @@ en:
report_notif: "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly."
report_notif_remove: "Your report has been removed."
reported: Reported
- set_best: "Tag as Best"
settings:
all_comments: "All Comments"
from_settings_page: "From the Profile Page you can see your comment history."
@@ -363,7 +361,6 @@ en:
write_message: "Write a message"
send: Send
thank_you: "We value your safety and feedback. A moderator will review your report."
- unset_best: "Untag as Best"
user:
bio_flags: "flags for this bio"
user_bio: "User Bio"
diff --git a/locales/es.yml b/locales/es.yml
index fe6030b74..963723e9e 100644
--- a/locales/es.yml
+++ b/locales/es.yml
@@ -32,7 +32,6 @@ es:
comment_post_notif_premod: "Gracias por el comentario. Nuestro equipo de moderación va a revisarlo muy pronto."
comment_post_banned_word: "Tu comentario contiene una o más palabras que no están permitidas en nuestro espacio, por lo que no será publicado. Si crees que es un error, por favor contacta a nuestro equipo de moderación."
characters_remaining: "carácteres restantes"
- comment_is_best: "Este comentario es uno de los mejores"
comment_offensive: "Este comentario es ofensivo"
comment_singular: Comentario
comment_plural: Comentarios
@@ -298,7 +297,6 @@ es:
report_notif: "Gracias por reportar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar."
report_notif_remove: "Tu reporte ha sido eliminado."
reported: "Reportado"
- set_best: "Etiquetar como el mejor"
settings:
all_comments: "Todos los comentarios"
from_settings_page: "Desde la página de configuración puedes ver tu historial de comentarios."
@@ -386,7 +384,6 @@ es:
write_message: "Escribir un mensaje"
send: "Enviar"
thank_you: "Valoramos tanto su seguridad en este espacio como sus comentarios. Un o una moderadora va a leer su reporte."
- unset_best: "Des-etiquetar como el mejor"
user:
bio_flags: "reportes para este bio"
user_bio: "Bio de Usuario"
diff --git a/package.json b/package.json
index 3e3254fd4..9cb065320 100644
--- a/package.json
+++ b/package.json
@@ -111,6 +111,7 @@
"passport-jwt": "^2.2.1",
"passport-local": "^1.0.0",
"prop-types": "^15.5.10",
+ "query-strings": "^0.0.1",
"react-apollo": "^1.1.0",
"react-input-autosize": "^1.1.4",
"react-recaptcha": "^2.2.6",
diff --git a/plugin-api/beta/client/actions/notification.js b/plugin-api/beta/client/actions/notification.js
new file mode 100644
index 000000000..c1a75cb7a
--- /dev/null
+++ b/plugin-api/beta/client/actions/notification.js
@@ -0,0 +1 @@
+export {addNotification} from 'coral-framework/actions/notification';
diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js
index 78408f6c2..68547692d 100644
--- a/plugin-api/beta/client/hocs/index.js
+++ b/plugin-api/beta/client/hocs/index.js
@@ -1,4 +1,5 @@
export {default as withReaction} from './withReaction';
+export {default as withTags} from './withTags';
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/withTags.js b/plugin-api/beta/client/hocs/withTags.js
new file mode 100644
index 000000000..75d434bef
--- /dev/null
+++ b/plugin-api/beta/client/hocs/withTags.js
@@ -0,0 +1,111 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {compose, gql} from 'react-apollo';
+import {getDisplayName} from 'coral-framework/helpers/hoc';
+import {capitalize} from 'coral-framework/helpers/strings';
+import {withAddTag, withRemoveTag} from 'coral-framework/graphql/mutations';
+import withFragments from 'coral-framework/hocs/withFragments';
+import {addNotification} from 'coral-framework/actions/notification';
+import {forEachError, isTagged} from 'coral-framework/utils';
+
+export default (tag) => (WrappedComponent) => {
+ if (typeof tag !== 'string') {
+ console.error('Tag must be a valid string');
+ return null;
+ }
+
+ const Tag = capitalize(tag);
+ const TAG = tag.toUpperCase();
+
+ class WithTags extends React.Component {
+ loading = false;
+
+ postTag = () => {
+ const {comment, asset, addNotification} = this.props;
+
+ if (this.loading) {
+ return;
+ }
+
+ this.loading = true;
+
+ this.props.addTag({
+ id: comment.id,
+ name: TAG,
+ assetId: asset.id,
+ itemType: 'COMMENTS',
+ })
+ .then(() => {
+ this.loading = false;
+ })
+ .catch((err) => {
+ this.loading = false;
+ forEachError(err, ({msg}) => addNotification('error', msg));
+ });
+ }
+
+ deleteTag = () => {
+ const {comment, asset, addNotification} = this.props;
+
+ if (this.loading) {
+ return;
+ }
+
+ this.props.removeTag({
+ id: comment.id,
+ name: TAG,
+ assetId: asset.id,
+ itemType: 'COMMENTS',
+ })
+ .then(() => {
+ this.loading = false;
+ })
+ .catch((err) => {
+ this.loading = false;
+ forEachError(err, ({msg}) => addNotification('error', msg));
+ });
+ }
+
+ render() {
+ const {comment} = this.props;
+
+ const alreadyTagged = isTagged(comment.tags, TAG);
+
+ return ;
+ }
+ }
+
+ const mapStateToProps = (state) => ({
+ user: state.auth.toJS().user,
+ });
+
+ const mapDispatchToProps = (dispatch) =>
+ bindActionCreators({addNotification}, dispatch);
+
+ const enhance = compose(
+ withFragments({
+ comment: gql`
+ fragment ${Tag}Button_comment on Comment {
+ tags {
+ tag {
+ name
+ }
+ }
+ }`
+ }),
+ connect(mapStateToProps, mapDispatchToProps),
+ withAddTag,
+ withRemoveTag
+ );
+
+ WithTags.displayName = `WithTags(${getDisplayName(WrappedComponent)})`;
+
+ return enhance(WithTags);
+};
diff --git a/plugin-api/beta/client/services/index.js b/plugin-api/beta/client/services/index.js
index 4e94a782a..54ad90fe7 100644
--- a/plugin-api/beta/client/services/index.js
+++ b/plugin-api/beta/client/services/index.js
@@ -1,4 +1,4 @@
-export {t} from 'coral-framework/services/i18n';
+export {t, timeago} from 'coral-framework/services/i18n';
export {can} from 'coral-framework/services/perms';
import {isSlotEmpty as ise} from 'coral-framework/helpers/plugins';
diff --git a/plugin-api/beta/client/utils/index.js b/plugin-api/beta/client/utils/index.js
index 44c3af6c0..e0ff635e0 100644
--- a/plugin-api/beta/client/utils/index.js
+++ b/plugin-api/beta/client/utils/index.js
@@ -1 +1,7 @@
-export {getSlotFragmentSpreads} from 'coral-framework/utils';
+export {
+ isTagged,
+ insertCommentsSorted,
+ getSlotFragmentSpreads,
+ forEachError,
+ getDefinitionName,
+} from 'coral-framework/utils';
diff --git a/plugins.default.json b/plugins.default.json
index 425dccf93..4a1ea3282 100644
--- a/plugins.default.json
+++ b/plugins.default.json
@@ -3,7 +3,8 @@
"coral-plugin-auth",
"coral-plugin-respect",
"coral-plugin-offtopic",
- "coral-plugin-facebook-auth"
+ "coral-plugin-facebook-auth",
+ "talk-plugin-featured-comments"
],
"client": [
"coral-plugin-respect",
@@ -11,6 +12,7 @@
"coral-plugin-offtopic",
"coral-plugin-viewing-options",
"coral-plugin-comment-content",
- "talk-plugin-permalink"
+ "talk-plugin-permalink",
+ "talk-plugin-featured-comments"
]
}
diff --git a/plugins/coral-plugin-like/client/styles.css b/plugins/coral-plugin-like/client/styles.css
index 859581bd4..a1d88b1b0 100644
--- a/plugins/coral-plugin-like/client/styles.css
+++ b/plugins/coral-plugin-like/client/styles.css
@@ -9,6 +9,7 @@
padding: 0px;
border: none;
font-size: inherit;
+ vertical-align: middle;
&:hover {
color: #767676;
diff --git a/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js b/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js
index 0e028c6bf..0277072eb 100644
--- a/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js
+++ b/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js
@@ -1,13 +1,12 @@
import React from 'react';
import styles from './styles.css';
import {t} from 'plugin-api/beta/client/services';
-
-const isOffTopic = (tags) => !!tags.filter((t) => t.tag.name === 'OFF_TOPIC').length;
+import {isTagged} from 'plugin-api/beta/client/utils';
export default (props) => (
{
- isOffTopic(props.comment.tags) && props.depth === 0 ? (
+ isTagged(props.comment.tags, 'OFF_TOPIC') && props.depth === 0 ? (
{t('off_topic')}
diff --git a/plugins/coral-plugin-respect/client/styles.css b/plugins/coral-plugin-respect/client/styles.css
index 362e02b8f..1022fe20d 100644
--- a/plugins/coral-plugin-respect/client/styles.css
+++ b/plugins/coral-plugin-respect/client/styles.css
@@ -9,6 +9,7 @@
padding: 0px;
border: none;
font-size: inherit;
+ vertical-align: middle;
&:hover {
color: #767676;
diff --git a/plugins/talk-plugin-featured/client/.babelrc b/plugins/talk-plugin-featured-comments/client/.babelrc
similarity index 99%
rename from plugins/talk-plugin-featured/client/.babelrc
rename to plugins/talk-plugin-featured-comments/client/.babelrc
index 63b1c53de..60be246eb 100644
--- a/plugins/talk-plugin-featured/client/.babelrc
+++ b/plugins/talk-plugin-featured-comments/client/.babelrc
@@ -11,4 +11,4 @@
"transform-async-to-generator",
"transform-react-jsx"
]
-}
+}
\ No newline at end of file
diff --git a/plugins/talk-plugin-featured/client/.eslintrc.json b/plugins/talk-plugin-featured-comments/client/.eslintrc.json
similarity index 100%
rename from plugins/talk-plugin-featured/client/.eslintrc.json
rename to plugins/talk-plugin-featured-comments/client/.eslintrc.json
diff --git a/plugins/talk-plugin-featured-comments/client/components/.babelrc b/plugins/talk-plugin-featured-comments/client/components/.babelrc
new file mode 100644
index 000000000..60be246eb
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/.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-featured-comments/client/components/Button.css b/plugins/talk-plugin-featured-comments/client/components/Button.css
new file mode 100644
index 000000000..11e35d40b
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/Button.css
@@ -0,0 +1,21 @@
+.button {
+
+ /* TODO: figure out the best location to include the `reset.css` */
+ composes: buttonReset from "coral-framework/styles/reset.css";
+
+ color: #2a2a2a;
+ margin-right: 10px;
+}
+
+.button {
+ color: #767676;
+}
+
+.button.featured {
+ color: #10589b;
+}
+
+.icon {
+ font-size: 18px;
+ vertical-align: top;
+}
diff --git a/plugins/talk-plugin-featured-comments/client/components/Button.js b/plugins/talk-plugin-featured-comments/client/components/Button.js
new file mode 100644
index 000000000..b9d0cc95f
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/Button.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import cn from 'classnames';
+import styles from './Button.css';
+import {pluginName} from '../../package.json';
+import {can} from 'plugin-api/beta/client/services';
+import {withTags} from 'plugin-api/beta/client/hocs';
+import {Icon} from 'plugin-api/beta/client/components/ui';
+
+const Button = (props) => {
+ const {alreadyTagged, deleteTag, postTag, user} = props;
+
+ return can(user, 'MODERATE_COMMENTS') ? (
+
+ ) : null ;
+};
+
+export default withTags('featured')(Button);
+
diff --git a/plugins/talk-plugin-featured-comments/client/components/Comment.css b/plugins/talk-plugin-featured-comments/client/components/Comment.css
new file mode 100644
index 000000000..36f3e790e
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/Comment.css
@@ -0,0 +1,67 @@
+.root {
+ margin: 10px 0 35px;
+}
+
+.root:last-child {
+ margin: 10px 0;
+}
+
+.goTo {
+ color: #1d5294;
+ font-size: 13px;
+ padding: 5px 0;
+ display: inline-block;
+}
+
+.repliesIcon {
+ font-size: 16px;
+ vertical-align: middle;
+ line-height: 13px;
+ font-weight: bold;
+}
+
+.goToIcon {
+ font-size: 16px;
+ vertical-align: middle;
+ line-height: 13px;
+ font-weight: bold;
+}
+
+.goTo {
+
+ /* TODO: figure out the best location to include the `reset.css` */
+ composes: buttonReset from "coral-framework/styles/reset.css";
+}
+
+.goTo:hover {
+ text-decoration: underline;
+}
+
+.quote {
+ line-height: 20px;
+ text-align: left;
+ letter-spacing: 0.1px;
+ margin: 0;
+ quotes: '\201c' '\201d';
+ margin-bottom: 10px;
+}
+
+.quote:before {
+ content: open-quote;
+}
+
+.quote:after {
+ content: close-quote;
+}
+
+.footer {
+ display: flex;
+}
+
+.reactionsContainer, .actionsContainer {
+ flex: auto;
+}
+
+.actionsContainer {
+ text-align: right;
+}
diff --git a/plugins/talk-plugin-featured-comments/client/components/Comment.js b/plugins/talk-plugin-featured-comments/client/components/Comment.js
new file mode 100644
index 000000000..01b5f864c
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/Comment.js
@@ -0,0 +1,56 @@
+import React from 'react';
+import cn from 'classnames';
+import styles from './Comment.css';
+import {t, timeago} from 'plugin-api/beta/client/services';
+import {Slot} from 'plugin-api/beta/client/components';
+import {Icon} from 'plugin-api/beta/client/components/ui';
+import {pluginName} from '../../package.json';
+
+class Comment extends React.Component {
+
+ viewComment = () => {
+ this.props.viewComment(this.props.comment.id);
+ }
+
+ render() {
+ const {comment, asset, root, data} = this.props;
+ return (
+
+
+
+ {comment.body}
+
+
+
+
+ {comment.user.username}
+
+
+ ,{' '}{timeago(comment.created_at)}
+
+
+
+
+
+ );
+ }
+}
+
+export default Comment;
diff --git a/plugins/talk-plugin-featured-comments/client/components/LoadMore.js b/plugins/talk-plugin-featured-comments/client/components/LoadMore.js
new file mode 100644
index 000000000..a168ed43a
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/LoadMore.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {Button} from 'coral-ui';
+import t from 'coral-framework/services/i18n';
+import cn from 'classnames';
+
+class LoadMore extends React.Component {
+ render () {
+ const {loadingState, loadMore} = this.props;
+ const disabled = loadingState === 'loading';
+ return (
+
+
+
+ );
+ }
+}
+
+LoadMore.propTypes = {
+ loadMore: PropTypes.func.isRequired,
+ loadingState: PropTypes.oneOf(['', 'loading', 'success', 'error']),
+};
+
+export default LoadMore;
diff --git a/plugins/talk-plugin-featured-comments/client/components/Tab.js b/plugins/talk-plugin-featured-comments/client/components/Tab.js
new file mode 100644
index 000000000..c780887c6
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/Tab.js
@@ -0,0 +1,9 @@
+import React from 'react';
+import {TabCount} from 'plugin-api/beta/client/components/ui';
+import {t} from 'plugin-api/beta/client/services';
+
+export default ({active, asset: {featuredCommentsCount}}) => (
+
+ {t('talk-plugin-featured-comments.featured')} {featuredCommentsCount}
+
+);
diff --git a/plugins/talk-plugin-featured-comments/client/components/TabPane.js b/plugins/talk-plugin-featured-comments/client/components/TabPane.js
new file mode 100644
index 000000000..3cf8b2cfd
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/TabPane.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import Comment from '../containers/Comment';
+import LoadMore from './LoadMore';
+import {forEachError} from 'plugin-api/beta/client/utils';
+
+class TabPane extends React.Component {
+ state = {
+ loadingState: '',
+ };
+
+ 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);});
+ });
+ }
+
+ render() {
+ const {root, data, asset: {featuredComments, ...asset}, viewComment} = this.props;
+ return (
+
+ {featuredComments.nodes.map((comment) =>
+
+ )}
+ {featuredComments.hasNextPage &&
+
+ }
+
+ );
+ }
+}
+
+export default TabPane;
diff --git a/plugins/talk-plugin-featured-comments/client/components/Tag.css b/plugins/talk-plugin-featured-comments/client/components/Tag.css
new file mode 100644
index 000000000..8874bfa0c
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/Tag.css
@@ -0,0 +1,20 @@
+.tagIcon {
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+.icon {
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+.tag {
+ background-color: #10589b;
+ font-size: 12px;
+ font-weight: bold;
+ color: white;
+ display: inline-block;
+ margin: 0px 5px;
+ padding: 5px 5px;
+ border-radius: 2px;
+}
\ No newline at end of file
diff --git a/plugins/talk-plugin-featured-comments/client/components/Tag.js b/plugins/talk-plugin-featured-comments/client/components/Tag.js
new file mode 100644
index 000000000..4afa9629a
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/components/Tag.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import styles from './Tag.css';
+import {t} from 'plugin-api/beta/client/services';
+import {isTagged} from 'plugin-api/beta/client/utils';
+
+export default (props) => (
+
+ {
+ isTagged(props.comment.tags, 'FEATURED') && props.depth === 0 ? (
+
+ {t('talk-plugin-featured-comments.featured')}
+
+ ) : null
+ }
+
+);
diff --git a/plugins/talk-plugin-featured-comments/client/containers/Comment.js b/plugins/talk-plugin-featured-comments/client/containers/Comment.js
new file mode 100644
index 000000000..bba764a1d
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/containers/Comment.js
@@ -0,0 +1,41 @@
+import {gql} from 'react-apollo';
+import Comment from '../components/Comment';
+import {withFragments} from 'plugin-api/beta/client/hocs';
+import {getSlotFragmentSpreads} from 'plugin-api/beta/client/utils';
+
+const slots = [
+ 'commentReactions',
+];
+
+export default withFragments({
+ root: gql`
+ fragment TalkFeaturedComments_Comment_root on RootQuery {
+ __typename
+ ${getSlotFragmentSpreads(slots, 'root')}
+ }
+ `,
+ asset: gql`
+ fragment TalkFeaturedComments_Comment_asset on Asset {
+ __typename
+ ${getSlotFragmentSpreads(slots, 'asset')}
+ }
+ `,
+ comment: gql`
+ fragment TalkFeaturedComments_Comment_comment on Comment {
+ id
+ body
+ created_at
+ replyCount
+ tags {
+ tag {
+ name
+ }
+ }
+ user {
+ id
+ username
+ }
+ ${getSlotFragmentSpreads(slots, 'comment')}
+ }
+ `
+})(Comment);
diff --git a/plugins/talk-plugin-featured-comments/client/containers/Tab.js b/plugins/talk-plugin-featured-comments/client/containers/Tab.js
new file mode 100644
index 000000000..5d92387d6
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/containers/Tab.js
@@ -0,0 +1,15 @@
+import {compose, gql} from 'react-apollo';
+import {withFragments, excludeIf} from 'plugin-api/beta/client/hocs';
+import Tab from '../components/Tab';
+
+const enhance = compose(
+ withFragments({
+ asset: gql`
+ fragment TalkFeaturedComments_Tab_asset on Asset {
+ featuredCommentsCount: totalCommentCount(tags: ["FEATURED"], excludeIgnored: $excludeIgnored)
+ }`,
+ }),
+ excludeIf((props) => props.asset.featuredCommentsCount === 0),
+);
+
+export default enhance(Tab);
diff --git a/plugins/talk-plugin-featured-comments/client/containers/TabPane.js b/plugins/talk-plugin-featured-comments/client/containers/TabPane.js
new file mode 100644
index 000000000..e9d466061
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/containers/TabPane.js
@@ -0,0 +1,98 @@
+import React from 'react';
+import {bindActionCreators} from 'redux';
+import {compose, gql} from 'react-apollo';
+import TabPane from '../components/TabPane';
+import {withFragments, connect} from 'plugin-api/beta/client/hocs';
+import Comment from '../containers/Comment';
+import {addNotification} from 'plugin-api/beta/client/actions/notification';
+import {viewComment} from 'coral-embed-stream/src/actions/stream';
+import {insertCommentsSorted, getDefinitionName} from 'plugin-api/beta/client/utils';
+import update from 'immutability-helper';
+
+class TabPaneContainer extends React.Component {
+
+ loadMore = () => {
+ return this.props.data.fetchMore({
+ query: LOAD_MORE_QUERY,
+ variables: {
+ limit: 5,
+ cursor: this.props.root.asset.featuredComments.endCursor,
+ asset_id: this.props.root.asset.id,
+ sort: 'REVERSE_CHRONOLOGICAL',
+ excludeIgnored: this.props.data.variables.excludeIgnored,
+ },
+ updateQuery: (previous, {fetchMoreResult:{comments}}) => {
+ const updated = update(previous, {
+ asset: {
+ featuredComments: {
+ nodes: {
+ $apply: (nodes) => insertCommentsSorted(nodes, comments.nodes, 'REVERSE_CHRONOLOGICAL'),
+ },
+ hasNextPage: {$set: comments.hasNextPage},
+ endCursor: {$set: comments.endCursor},
+ },
+ }
+ });
+ return updated;
+ },
+ });
+ };
+
+ render() {
+ return ;
+ }
+}
+
+const LOAD_MORE_QUERY = gql`
+ query CoralEmbedStream_LoadMoreComments($limit: Int = 5, $cursor: Date, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) {
+ comments(query: {limit: $limit, cursor: $cursor, tags: ["FEATURED"], asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) {
+ nodes {
+ ...${getDefinitionName(Comment.fragments.comment)}
+ }
+ hasNextPage
+ startCursor
+ endCursor
+ }
+ }
+ ${Comment.fragments.comment}
+`;
+
+const mapDispatchToProps = (dispatch) =>
+ bindActionCreators({
+ viewComment,
+ addNotification,
+ }, dispatch);
+
+const enhance = compose(
+ connect(null, mapDispatchToProps),
+ withFragments({
+ root: gql`
+ fragment TalkFeaturedComments_TabPane_root on RootQuery {
+ __typename
+ ...${getDefinitionName(Comment.fragments.root)}
+ }
+ ${Comment.fragments.root}
+ `,
+ asset: gql`
+ fragment TalkFeaturedComments_TabPane_asset on Asset {
+ id
+ featuredComments: comments(tags: ["FEATURED"], excludeIgnored: $excludeIgnored, deep: true) {
+ nodes {
+ ...${getDefinitionName(Comment.fragments.comment)}
+ }
+ hasNextPage
+ startCursor
+ endCursor
+ }
+ ...${getDefinitionName(Comment.fragments.asset)}
+ }
+ ${Comment.fragments.comment}
+ ${Comment.fragments.asset}
+ `,
+ }),
+);
+
+export default enhance(TabPaneContainer);
diff --git a/plugins/talk-plugin-featured-comments/client/index.js b/plugins/talk-plugin-featured-comments/client/index.js
new file mode 100644
index 000000000..d804e2b55
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/index.js
@@ -0,0 +1,94 @@
+import Tab from './containers/Tab';
+import TabPane from './containers/TabPane';
+import Tag from './components/Tag';
+import Button from './components/Button';
+import translations from './translations.yml';
+import update from 'immutability-helper';
+
+import {findCommentInEmbedQuery} from 'coral-embed-stream/src/graphql/utils';
+import {insertCommentsSorted} from 'plugin-api/beta/client/utils';
+
+export default {
+ translations,
+ slots: {
+ streamTabs: [Tab],
+ streamTabPanes: [TabPane],
+ commentInfoBar: [Tag],
+ commentReactions: [Button]
+ },
+ mutations: {
+ IgnoreUser: ({variables}) => ({
+ updateQueries: {
+ CoralEmbedStream_Embed: (previous) => {
+ const ignoredUserId = variables.id;
+ const newNodes = previous.asset.featuredComments.nodes.filter((n) => n.user.id !== ignoredUserId);
+ const removedCount = previous.asset.featuredComments.nodes.length - newNodes.length;
+ const updated = update(previous, {
+ asset: {
+ featuredComments: {
+ nodes: {$set: newNodes}
+ },
+ featuredCommentsCount: {
+ $apply: (value) => value - removedCount,
+ }
+ }
+ });
+ return updated;
+ }
+ }
+ }),
+ AddTag: ({variables}) => ({
+ updateQueries: {
+ CoralEmbedStream_Embed: (previous) => {
+
+ if (variables.name !== 'FEATURED') {
+ return;
+ }
+
+ const comment = findCommentInEmbedQuery(previous, variables.id);
+
+ const updated = update(previous, {
+ asset: {
+ featuredComments: {
+ nodes: {
+ $apply: (nodes) => insertCommentsSorted(nodes, comment, 'REVERSE_CHRONOLOGICAL')
+ }
+ },
+ featuredCommentsCount: {
+ $apply: (value) => value + 1
+ }
+ }
+ });
+
+ return updated;
+ },
+ }
+ }),
+ RemoveTag: ({variables}) => ({
+ updateQueries: {
+ CoralEmbedStream_Embed: (previous) => {
+
+ if (variables.name !== 'FEATURED') {
+ return;
+ }
+
+ const updated = update(previous, {
+ asset: {
+ featuredComments: {
+ nodes: {
+ $apply: (nodes) =>
+ nodes.filter((n) => n.id !== variables.id)
+ }
+ },
+ featuredCommentsCount: {
+ $apply: (value) => value - 1
+ }
+ }
+ });
+
+ return updated;
+ },
+ }
+ })
+ },
+};
diff --git a/plugins/talk-plugin-featured-comments/client/translations.yml b/plugins/talk-plugin-featured-comments/client/translations.yml
new file mode 100644
index 000000000..7f6db9370
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/client/translations.yml
@@ -0,0 +1,8 @@
+en:
+ talk-plugin-featured-comments:
+ featured: Featured
+ go_to_conversation: Go to conversation
+es:
+ talk-plugin-featured-comments:
+ featured: Remarcado
+ go_to_conversation: Ir al comentario
diff --git a/plugins/talk-plugin-featured-comments/index.js b/plugins/talk-plugin-featured-comments/index.js
new file mode 100644
index 000000000..88f8344ae
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/index.js
@@ -0,0 +1,14 @@
+module.exports = {
+ tags: [
+ {
+ name: 'FEATURED',
+ permissions: {
+ public: true,
+ self: true,
+ roles: []
+ },
+ models: ['COMMENTS'],
+ created_at: new Date()
+ }
+ ]
+};
diff --git a/plugins/talk-plugin-featured-comments/package.json b/plugins/talk-plugin-featured-comments/package.json
new file mode 100644
index 000000000..9dabc6d0d
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@coralproject/talk-plugin-featured-comments",
+ "pluginName": "talk-plugin-featured-comments",
+ "version": "0.0.1",
+ "description": "Provides support for featured comments",
+ "main": "index.js",
+ "author": "The Coral Project Team ",
+ "license": "Apache-2.0"
+}
diff --git a/plugins/talk-plugin-featured-comments/yarn.lock b/plugins/talk-plugin-featured-comments/yarn.lock
new file mode 100644
index 000000000..fb57ccd13
--- /dev/null
+++ b/plugins/talk-plugin-featured-comments/yarn.lock
@@ -0,0 +1,4 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
diff --git a/plugins/talk-plugin-featured/client/components/Tab.js b/plugins/talk-plugin-featured/client/components/Tab.js
deleted file mode 100644
index af11368b9..000000000
--- a/plugins/talk-plugin-featured/client/components/Tab.js
+++ /dev/null
@@ -1,9 +0,0 @@
-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
deleted file mode 100644
index 107c806b4..000000000
--- a/plugins/talk-plugin-featured/client/components/TabPane.js
+++ /dev/null
@@ -1,16 +0,0 @@
-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) => (
-
-
{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
deleted file mode 100644
index 0bcb49e13..000000000
--- a/plugins/talk-plugin-featured/client/containers/Tab.js
+++ /dev/null
@@ -1,17 +0,0 @@
-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`
- 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
deleted file mode 100644
index 19c4d855d..000000000
--- a/plugins/talk-plugin-featured/client/containers/TabPane.js
+++ /dev/null
@@ -1,22 +0,0 @@
-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`
- 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
deleted file mode 100644
index 69dfaa6cb..000000000
--- a/plugins/talk-plugin-featured/client/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-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
deleted file mode 100644
index 85dfb349b..000000000
--- a/plugins/talk-plugin-featured/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-module.exports = {};
-
diff --git a/test/e2e/pages/embedStreamPage.js b/test/e2e/pages/embedStreamPage.js
index 2d59d5e57..536e58b38 100644
--- a/test/e2e/pages/embedStreamPage.js
+++ b/test/e2e/pages/embedStreamPage.js
@@ -160,12 +160,6 @@ module.exports = {
},
registerButton: {
selector: '#signInDialog #coralRegister'
- },
- setBestButton: {
- selector: '.e2e__set-best-comment'
- },
- unsetBestButton: {
- selector: '.e2e__unset-best-comment'
}
}
};
diff --git a/test/e2e/tests/Commenter/BestCommentTest.js b/test/e2e/tests/Commenter/BestCommentTest.js
deleted file mode 100644
index 37e396149..000000000
--- a/test/e2e/tests/Commenter/BestCommentTest.js
+++ /dev/null
@@ -1,25 +0,0 @@
-module.exports = {
- '@tags': ['like', 'comments', 'commenter'],
- before: (client) => {
- const embedStreamPage = client.page.embedStreamPage();
- const {users} = client.globals;
-
- embedStreamPage
- .navigate()
- .ready();
-
- embedStreamPage
- .login(users.commenter);
- },
- 'Commenters should not see the set-best-comment button': (client) => {
- const embedStreamPage = client.page.embedStreamPage();
-
- embedStreamPage
- .postComment('Hi everyone. Isn\'t this the BEST comment!?')
- .waitForElementVisible('@likeButton')
- .expect.element('@setBestButton').to.not.be.present;
- },
- after: (client) => {
- client.end();
- }
-};
diff --git a/test/e2e/tests/Moderator/BestCommentTest.js b/test/e2e/tests/Moderator/BestCommentTest.js
deleted file mode 100644
index 5cdc2e5ed..000000000
--- a/test/e2e/tests/Moderator/BestCommentTest.js
+++ /dev/null
@@ -1,50 +0,0 @@
-module.exports = {
- '@tags': ['like', 'comments', 'commenter'],
- before: (client) => {
- const embedStreamPage = client.page.embedStreamPage();
- const {users} = client.globals;
-
- embedStreamPage
- .navigate()
- .ready();
-
- embedStreamPage
- .login(users.moderator);
- },
- 'Moderator marks/unmarks their comment as BEST': (client) => {
- const embedStreamPage = client.page.embedStreamPage();
-
- const setBestCommentButton = '.e2e__set-best-comment';
- const unsetBestCommentButton = '.e2e__unset-best-comment';
-
- embedStreamPage
- .postComment(`Hi everyone. Isn't this the BEST comment!? ${String(Math.random()).slice(2)}`)
- .waitForElementVisible(setBestCommentButton, 2000)
- .click(setBestCommentButton)
- .waitForElementVisible(unsetBestCommentButton, 2000);
-
- // on refresh, it should still be tagged as best :)
- client.refresh();
- embedStreamPage.ready()
-
- // (bengo) I have no idea why, but if the selector here is '@unsetBestButton', it doesn't find it... I think nightwatch bug?
- // this is why I am not using @elements. Advice appreciated.
- .waitForElementVisible(unsetBestCommentButton, 2000);
-
- // now remove the best tag
- embedStreamPage
- .click(unsetBestCommentButton);
-
- embedStreamPage
- .waitForElementVisible(setBestCommentButton, 2000);
-
- // on refresh it should still be untagged best
- client.refresh();
- embedStreamPage.ready()
- .waitForElementVisible(setBestCommentButton);
-
- },
- after: (client) => {
- client.end();
- }
-};
diff --git a/webpack.config.js b/webpack.config.js
index 5c6655d17..1f4c16ee4 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -109,7 +109,8 @@ const config = {
}),
new webpack.EnvironmentPlugin({
'TALK_PLUGINS_JSON': '{}',
- 'TALK_THREADING_LEVEL': '3'
+ 'TALK_THREADING_LEVEL': '3',
+ 'TALK_DEFAULT_STREAM_TAB': 'all',
})
],
resolveLoader: {
diff --git a/yarn.lock b/yarn.lock
index b2b169db2..710b89fa2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1560,6 +1560,10 @@ check-types@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-1.4.0.tgz#eed63bbac9ea49a0e26a096314058b03b08dd62b"
+check-valid-url@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/check-valid-url/-/check-valid-url-0.0.2.tgz#938fc545fc90b71edf800f7345bfd36f3aa0a057"
+
cheerio@^0.20.0:
version "0.20.0"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.20.0.tgz#5c710f2bab95653272842ba01c6ea61b3545ec35"
@@ -4628,10 +4632,6 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
dependencies:
jsonify "~0.0.0"
-json-stringify-pretty-compact@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.0.4.tgz#d5161131be27fd9748391360597fcca250c6c5ce"
-
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -6806,6 +6806,12 @@ query-string@^4.1.0, query-string@^4.2.2:
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
+query-strings@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/query-strings/-/query-strings-0.0.1.tgz#d22bab97c9d39e2267b3b8e5f78592424b3e58cd"
+ dependencies:
+ check-valid-url "0.0.2"
+
querystring-es3@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"