mirror of
https://github.com/wassname/talk.git
synced 2026-08-12 12:30:39 +08:00
new merge conflicts to master
This commit is contained in:
@@ -10,4 +10,5 @@ plugins/*
|
||||
!plugins/coral-plugin-like
|
||||
!plugins/coral-plugin-mod
|
||||
!plugins/coral-plugin-love
|
||||
!plugins/coral-plugin-viewing-options
|
||||
node_modules
|
||||
|
||||
@@ -23,5 +23,6 @@ plugins/*
|
||||
!plugins/coral-plugin-like
|
||||
!plugins/coral-plugin-mod
|
||||
!plugins/coral-plugin-love
|
||||
!plugins/coral-plugin-viewing-options
|
||||
|
||||
**/node_modules/*
|
||||
|
||||
@@ -4,21 +4,20 @@
|
||||
"es6": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": "../.eslintrc.json",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }]
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,3 +37,13 @@ export const viewAllComments = () => {
|
||||
|
||||
return {type: actions.VIEW_ALL_COMMENTS};
|
||||
};
|
||||
|
||||
export const addCommentClassName = (className) => ({
|
||||
type: actions.ADD_COMMENT_CLASSNAME,
|
||||
className
|
||||
});
|
||||
|
||||
export const removeCommentClassName = (idx) => ({
|
||||
type: actions.REMOVE_COMMENT_CLASSNAME,
|
||||
idx
|
||||
});
|
||||
|
||||
@@ -98,6 +98,10 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.commentInfoBar {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@keyframes enter {
|
||||
0% {background-color: rgba(0, 0, 0, 0);}
|
||||
50% {background-color: rgba(255,255,0, 0.2);}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
|
||||
import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton';
|
||||
|
||||
import AuthorName from 'coral-plugin-author-name/AuthorName';
|
||||
|
||||
import TagLabel from 'coral-plugin-tag-label/TagLabel';
|
||||
import Content from 'coral-plugin-commentcontent/CommentContent';
|
||||
import PubDate from 'coral-plugin-pubdate/PubDate';
|
||||
@@ -20,15 +19,15 @@ import {
|
||||
} from 'coral-plugin-best/BestButton';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import LoadMore from './LoadMore';
|
||||
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
|
||||
import {TopRightMenu} from './TopRightMenu';
|
||||
import classnames from 'classnames';
|
||||
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
|
||||
import {EditableCommentContent} from './EditableCommentContent';
|
||||
import {getActionSummary, iPerformedThisAction} from 'coral-framework/utils';
|
||||
import {getEditableUntilDate} from './util';
|
||||
import styles from './Comment.css';
|
||||
|
||||
const isStaff = (tags) => !tags.every((t) => t.name !== 'STAFF');
|
||||
const hasTag = (tags, lookupTag) => !!tags.filter((tag) => tag.name === lookupTag).length;
|
||||
const hasComment = (nodes, id) => nodes.some((node) => node.id === id);
|
||||
|
||||
// resetCursors will return the id cursors of the first and second newest comment in
|
||||
@@ -74,8 +73,7 @@ const ActionButton = ({children}) => {
|
||||
);
|
||||
};
|
||||
|
||||
class Comment extends React.Component {
|
||||
|
||||
export default class Comment extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -273,28 +271,29 @@ class Comment extends React.Component {
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
comment,
|
||||
parentId,
|
||||
currentUser,
|
||||
asset,
|
||||
depth,
|
||||
postComment,
|
||||
addNotification,
|
||||
showSignInDialog,
|
||||
highlighted,
|
||||
comment,
|
||||
postFlag,
|
||||
parentId,
|
||||
ignoreUser,
|
||||
highlighted,
|
||||
postComment,
|
||||
currentUser,
|
||||
postDontAgree,
|
||||
setActiveReplyBox,
|
||||
activeReplyBox,
|
||||
deleteAction,
|
||||
addCommentTag,
|
||||
removeCommentTag,
|
||||
ignoreUser,
|
||||
liveUpdates,
|
||||
disableReply,
|
||||
commentIsIgnored,
|
||||
maxCharCount,
|
||||
charCountEnable
|
||||
addCommentTag,
|
||||
addNotification,
|
||||
charCountEnable,
|
||||
showSignInDialog,
|
||||
removeCommentTag,
|
||||
liveUpdates,
|
||||
commentIsIgnored,
|
||||
commentClassNames = []
|
||||
} = this.props;
|
||||
|
||||
const view = this.getVisibileReplies();
|
||||
@@ -350,9 +349,39 @@ class Comment extends React.Component {
|
||||
() => 'Failed to remove best comment tag'
|
||||
);
|
||||
|
||||
/**
|
||||
* classNamesToAdd
|
||||
* adds classNames based on condition
|
||||
* classnames is an array of objects with key as classnames and value as conditions
|
||||
* i.e:
|
||||
* {
|
||||
* 'myClassName': { tags: ['STAFF']}
|
||||
* }
|
||||
*
|
||||
* This will add myClassName to comments tagged with STAFF TAG.
|
||||
* **/
|
||||
|
||||
const classNamesToAdd = commentClassNames.reduce((acc, className) => {
|
||||
let res = [];
|
||||
|
||||
// Adding classNames based on tags
|
||||
Object.keys(className).forEach((cn) => {
|
||||
const condition = className[cn];
|
||||
condition.tags.forEach((tag) => {
|
||||
if (hasTag(comment.tags, tag)) {
|
||||
res = [...res, cn];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Compare rest of the comment obj to the condition if needed
|
||||
|
||||
return [...acc, ...res];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(commentClass, 'talk-stream-comment-wrapper', {[styles.enter]: this.state.animateEnter})}
|
||||
className={cn(commentClass, 'talk-stream-comment-wrapper', classNamesToAdd, {[styles.enter]: this.state.animateEnter})}
|
||||
id={`c_${comment.id}`}
|
||||
style={{marginLeft: depth * 30}}
|
||||
>
|
||||
@@ -377,11 +406,13 @@ class Comment extends React.Component {
|
||||
</span>
|
||||
|
||||
<Slot
|
||||
className={styles.commentInfoBar}
|
||||
fill="commentInfoBar"
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
depth={depth}
|
||||
comment={comment}
|
||||
commentId={comment.id}
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
inline
|
||||
/>
|
||||
|
||||
@@ -543,8 +574,6 @@ class Comment extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default Comment;
|
||||
|
||||
// return whether the comment is editable
|
||||
function commentIsStillEditable (comment) {
|
||||
const editing = comment && comment.editing;
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import LoadMore from './LoadMore';
|
||||
|
||||
import Comment from '../components/Comment';
|
||||
import SuspendedAccount from './SuspendedAccount';
|
||||
import RestrictedMessageBox
|
||||
from 'coral-framework/components/RestrictedMessageBox';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import InfoBox from 'coral-plugin-infobox/InfoBox';
|
||||
import {can} from 'coral-framework/services/perms';
|
||||
import {ModerationLink} from 'coral-plugin-moderation';
|
||||
import RestrictedMessageBox
|
||||
from 'coral-framework/components/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 t, {timeago} from 'coral-framework/services/i18n';
|
||||
import {TransitionGroup} from 'react-transition-group';
|
||||
|
||||
const hasComment = (nodes, id) => nodes.some((node) => node.id === id);
|
||||
@@ -122,6 +121,7 @@ class Stream extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
commentClassNames,
|
||||
root: {asset, asset: {comments}, comment, me},
|
||||
postComment,
|
||||
addNotification,
|
||||
@@ -130,10 +130,10 @@ class Stream extends React.Component {
|
||||
deleteAction,
|
||||
showSignInDialog,
|
||||
addCommentTag,
|
||||
removeCommentTag,
|
||||
pluginProps,
|
||||
ignoreUser,
|
||||
auth: {loggedIn, user},
|
||||
removeCommentTag,
|
||||
pluginProps,
|
||||
editName
|
||||
} = this.props;
|
||||
const view = this.getVisibleComments();
|
||||
@@ -202,11 +202,17 @@ class Stream extends React.Component {
|
||||
/>}
|
||||
</div>
|
||||
: <p>{asset.settings.closedMessage}</p>}
|
||||
{loggedIn &&
|
||||
|
||||
{loggedIn && (
|
||||
<ModerationLink
|
||||
assetId={asset.id}
|
||||
isAdmin={can(user, 'MODERATE_COMMENTS')}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="talk-stream-wrapper-box">
|
||||
<Slot fill="streamBox" />
|
||||
</div>
|
||||
|
||||
{/* the highlightedComment is isolated after the user followed a permalink */}
|
||||
{highlightedComment
|
||||
@@ -235,7 +241,7 @@ class Stream extends React.Component {
|
||||
editComment={this.props.editComment}
|
||||
liveUpdates={true}
|
||||
/>
|
||||
: <div>
|
||||
: <div className="talk-stream-comments-container">
|
||||
<NewCount
|
||||
count={comments.nodes.length - view.length}
|
||||
loadMore={this.viewNewComments}
|
||||
@@ -245,6 +251,7 @@ class Stream extends React.Component {
|
||||
return commentIsIgnored(comment)
|
||||
? <IgnoredCommentTombstone key={comment.id} />
|
||||
: <Comment
|
||||
commentClassNames={commentClassNames}
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
disableReply={!open}
|
||||
|
||||
@@ -41,7 +41,10 @@ class SuspendedAccount extends Component {
|
||||
<span>{
|
||||
canEditName ?
|
||||
t('framework.edit_name.msg')
|
||||
: t('framework.banned_account_msg')
|
||||
:
|
||||
<span>
|
||||
<b>{t('framework.banned_account_header')}</b><br/> {t('framework.banned_account_body')}
|
||||
</span>
|
||||
}</span>
|
||||
{
|
||||
canEditName ?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
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 ADD_COMMENT_CLASSNAME = 'ADD_COMMENT_CLASSNAME';
|
||||
export const REMOVE_COMMENT_CLASSNAME = 'REMOVE_COMMENT_CLASSNAME';
|
||||
|
||||
@@ -290,6 +290,7 @@ const mapStateToProps = (state) => ({
|
||||
assetUrl: state.stream.assetUrl,
|
||||
activeTab: state.embed.activeTab,
|
||||
previousTab: state.embed.previousTab,
|
||||
commentClassNames: state.stream.commentClassNames
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -171,6 +171,7 @@ const extension = {
|
||||
id: auth.toJS().user.id,
|
||||
},
|
||||
})),
|
||||
tags: tags.map((t) => ({name: t, __typename: 'Tag'})),
|
||||
status: null,
|
||||
replyCount: 0,
|
||||
parent: parent_id
|
||||
|
||||
@@ -4,6 +4,6 @@ import stream from './stream';
|
||||
|
||||
export default {
|
||||
embed,
|
||||
stream,
|
||||
config,
|
||||
stream,
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ const initialState = {
|
||||
assetId: getQueryVariable('asset_id'),
|
||||
assetUrl: getQueryVariable('asset_url'),
|
||||
commentId: getQueryVariable('comment_id'),
|
||||
commentClassNames: []
|
||||
};
|
||||
|
||||
export default function stream(state = initialState, action) {
|
||||
@@ -39,6 +40,19 @@ export default function stream(state = initialState, action) {
|
||||
...state,
|
||||
commentId: '',
|
||||
};
|
||||
case actions.ADD_COMMENT_CLASSNAME :
|
||||
return {
|
||||
...state,
|
||||
commentClassNames: [...state.commentClassNames, action.className]
|
||||
};
|
||||
case actions.REMOVE_COMMENT_CLASSNAME :
|
||||
return {
|
||||
...state,
|
||||
commentClassNames: [
|
||||
...state.commentClassNames.slice(0, action.idx),
|
||||
...state.commentClassNames.slice(action.idx + 1)
|
||||
]
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ hr {
|
||||
.coral-plugin-questionbox-info {
|
||||
top: 0;
|
||||
border: 0;
|
||||
background: rgb(213,213,213);
|
||||
background: #F0F0F0;
|
||||
color: black;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
@@ -134,26 +134,25 @@ hr {
|
||||
|
||||
.coral-plugin-questionbox-icon.bubble{
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
top: 8px;
|
||||
left: 10px;
|
||||
color: #949393;
|
||||
font-size: 20px;
|
||||
color: #9E9E9E;
|
||||
font-size: 24px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.coral-plugin-questionbox-icon.person{
|
||||
z-index: 2;
|
||||
top: 20px;
|
||||
left: 15px;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
position: absolute;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
font-size: 33px;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.coral-plugin-questionbox-box {
|
||||
position: relative;
|
||||
border: 0;
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
margin-left: 0px !important;
|
||||
@@ -178,6 +177,15 @@ hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.talk-stream-wrapper-box {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.talk-stream-comments-container {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.commentStream {
|
||||
/* prevent absolutely positioned final permalink popover from being clipped */
|
||||
padding-bottom: 50px;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
.message {
|
||||
background: #D8D8D8;
|
||||
padding: 25px;
|
||||
margin-bottom: 8px;
|
||||
background-color: white;
|
||||
border: 1px #F0F0F0 solid;
|
||||
text-align: center;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import styles from './Slot.css';
|
||||
import {connect} from 'react-redux';
|
||||
import {getSlotElements} from 'coral-framework/helpers/plugins';
|
||||
|
||||
function Slot ({fill, inline = false, plugin_config: config, ...rest}) {
|
||||
function Slot ({fill, inline = false, className, plugin_config: config, ...rest}) {
|
||||
return (
|
||||
<div className={cn({[styles.inline]: inline, [styles.debug]: config.debug})}>
|
||||
<div className={cn({[styles.inline]: inline, [styles.debug]: config.debug}, className)}>
|
||||
{getSlotElements(fill, {...rest, config})}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export {default as Slot} from './Slot';
|
||||
@@ -0,0 +1,16 @@
|
||||
const regExp = /[-\s]+(.)?/g;
|
||||
|
||||
/**
|
||||
* Convert dash separated strings to camel case.
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
export default function camelize(str) {
|
||||
return str.replace(regExp, toUpper);
|
||||
}
|
||||
|
||||
function toUpper(match, c) {
|
||||
return c ? c.toUpperCase() : '';
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import merge from 'lodash/merge';
|
||||
import flatten from 'lodash/flatten';
|
||||
import flattenDeep from 'lodash/flattenDeep';
|
||||
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 {getDefinitionName, mergeDocuments} from 'coral-framework/utils';
|
||||
import {loadTranslations} from 'coral-framework/services/i18n';
|
||||
import {injectReducers} from 'coral-framework/services/store';
|
||||
import camelize from './camelize';
|
||||
|
||||
/**
|
||||
* Returns React Elements for given slot.
|
||||
@@ -98,7 +99,7 @@ export function injectPluginsReducers() {
|
||||
const reducers = merge(
|
||||
...plugins
|
||||
.filter((o) => o.module.reducer)
|
||||
.map((o) => ({...o.module.reducer}))
|
||||
.map((o) => ({[camelize(o.plugin)] : o.module.reducer}))
|
||||
);
|
||||
injectReducers(reducers);
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export {withReaction} from '../coral-framework/hocs';
|
||||
@@ -1,4 +1,6 @@
|
||||
.moderationLink {
|
||||
display: inline;
|
||||
|
||||
a {
|
||||
color: #679af3;
|
||||
text-decoration: none;
|
||||
|
||||
+5
-4
@@ -87,7 +87,7 @@ en:
|
||||
custom_css_url_desc: "URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external."
|
||||
dashboard: Dashboard
|
||||
days: Days
|
||||
description: "As an admin you may customize the settings for the comment stream for this asset"
|
||||
description: "As an admin, you can customize the settings for the comment stream for this story:"
|
||||
domain_list_text: "Enter the domains you would like to permit for Talk e.g. your local staging and production environments (ex. localhost:3000 staging.domain.com domain.com)."
|
||||
domain_list_title: "Permitted Domains"
|
||||
edit_comment_timeframe_heading: "Edit Comment Timeframe"
|
||||
@@ -101,13 +101,13 @@ en:
|
||||
enable_premod: "Enable Premoderation"
|
||||
enable_premod_description: "Moderators must approve any comment before its published."
|
||||
enable_premod_links_description: "Moderators must approve any comment containing a link before its published."
|
||||
enable_questionbox: "Ask readers a question"
|
||||
enable_questionbox: "Ask Readers a Question"
|
||||
enable_questionbox_description: "This question will appear at the top of this comment stream. Ask readers about a certain issue in the article or pose discussion questions etc."
|
||||
hours: Hours
|
||||
include_comment_stream: "Include Comment Stream Description for Readers"
|
||||
include_comment_stream_desc: "Write a message to be added to the top of your comment stream. Pose a topic include community guidelines etc."
|
||||
include_text: "Include your text here."
|
||||
include_question_here: "Write your question here."
|
||||
include_question_here: "Write your question here:"
|
||||
moderate: Moderate
|
||||
moderation_settings: "Moderation Settings"
|
||||
open: "Open"
|
||||
@@ -202,7 +202,8 @@ en:
|
||||
flag_reason: "Reason for reporting (Optional)"
|
||||
flag_username: "Report username"
|
||||
framework:
|
||||
banned_account_msg: "Your account is currently banned. This means that you cannot Like, Report, or write comments. Please contact us if you have any questions."
|
||||
banned_account_header: "Your account is currently banned."
|
||||
banned_account_body: "This means that you cannot Like, Report, or write comments."
|
||||
because_you_ignored: "Because you ignored the following commenters, their comments are hidden."
|
||||
comment: comment
|
||||
comment_is_ignored: "This comment is hidden because you ignored this user."
|
||||
|
||||
+2
-1
@@ -202,7 +202,8 @@ es:
|
||||
flag_reason: "Razón por la que hacer este reporte (Opcional)"
|
||||
flag_username: "Reportar el nombre de usuario"
|
||||
framework:
|
||||
banned_account_msg: "Tu cuenta se encuentra suspendida. Esto significa que no puedes gustar, marcar o escribir comentarios."
|
||||
banned_account_header: "Tu cuenta se encuentra suspendida."
|
||||
banned_account_body: "Esto significa que no puedes gustar, marcar o escribir comentarios."
|
||||
comment: "comentario"
|
||||
comment_is_ignored: "Este comentario está escondido porque has ignorado al usuario."
|
||||
comments: "comentarios"
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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"] }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {addTag, removeTag} from 'coral-plugin-commentbox/actions';
|
||||
export {addCommentClassName, removeCommentClassName} from 'coral-embed-stream/src/actions/stream';
|
||||
@@ -0,0 +1,2 @@
|
||||
export const commentBoxTagsSelector = (state) => state.commentBox.tags;
|
||||
export const commentClassNamesSelector = (state) => state.stream.commentClassNames;
|
||||
@@ -0,0 +1,2 @@
|
||||
export {Slot} from 'coral-framework/components';
|
||||
export {Icon} from 'coral-ui';
|
||||
@@ -0,0 +1 @@
|
||||
export {withReaction} from 'coral-framework/hocs';
|
||||
@@ -0,0 +1 @@
|
||||
export {t} from 'coral-framework/services/i18n';
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import {Icon} from 'coral-ui';
|
||||
import styles from './styles.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {withReaction} from 'coral-plugin-api';
|
||||
import {withReaction} from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class LoveButton extends React.Component {
|
||||
handleClick = () => {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {OFFTOPIC_TOGGLE_CHECKBOX} from './constants';
|
||||
|
||||
export const toggleCheckbox = () => ({
|
||||
type: OFFTOPIC_TOGGLE_CHECKBOX
|
||||
});
|
||||
@@ -1,18 +1,15 @@
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {addTag, removeTag, clearTags} from 'coral-plugin-commentbox/actions';
|
||||
import styles from './styles.css';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
|
||||
class OffTopicCheckbox extends React.Component {
|
||||
export default class OffTopicCheckbox extends React.Component {
|
||||
|
||||
label = 'OFF_TOPIC';
|
||||
|
||||
componentDidMount() {
|
||||
this.clearTagsHook = this.props.registerHook('postSubmit', () => {
|
||||
const idx = this.props.commentBox.tags.indexOf(this.label);
|
||||
const idx = this.props.tags.indexOf(this.label);
|
||||
this.props.removeTag(idx);
|
||||
});
|
||||
}
|
||||
@@ -22,29 +19,27 @@ class OffTopicCheckbox extends React.Component {
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
const {addTag, removeTag} = this.props;
|
||||
if (e.target.checked) {
|
||||
this.props.addTag(this.label);
|
||||
addTag(this.label);
|
||||
} else {
|
||||
const idx = this.props.commentBox.tags.indexOf(this.label);
|
||||
this.props.removeTag(idx);
|
||||
const idx = this.props.tags.indexOf(this.label);
|
||||
removeTag(idx);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.offTopic}>
|
||||
<label className={styles.offTopicLabel}>
|
||||
<input type="checkbox" onChange={this.handleChange}/>
|
||||
{t('off_topic')}
|
||||
</label>
|
||||
{
|
||||
!this.props.isReply ? (
|
||||
<label className={styles.offTopicLabel}>
|
||||
<input type="checkbox" onChange={this.handleChange}/>
|
||||
{t('off_topic')}
|
||||
</label>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({commentBox}) => ({commentBox});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({addTag, removeTag}, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(OffTopicCheckbox);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.css';
|
||||
|
||||
export default class OffTopicFilter extends React.Component {
|
||||
|
||||
tag = 'OFF_TOPIC';
|
||||
className = 'coral-plugin-off-topic-comment';
|
||||
cn = {[this.className] : {tags: [this.tag]}};
|
||||
|
||||
handleChange = (e) => {
|
||||
if (e.target.checked) {
|
||||
this.props.addCommentClassName(this.cn);
|
||||
this.props.toggleCheckbox();
|
||||
} else {
|
||||
const idx = this.props.commentClassNames.findIndex((i) => i[this.className]);
|
||||
this.props.removeCommentClassName(idx);
|
||||
this.props.toggleCheckbox();
|
||||
}
|
||||
this.props.closeViewingOptions();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.viewingOption}>
|
||||
<label>
|
||||
<input type="checkbox" onChange={this.handleChange} checked={this.props.checked} />
|
||||
Hide Off-Topic Comments
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.css';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
|
||||
const isOffTopic = (tags) => {
|
||||
return !!tags.filter((tag) => tag.name === 'OFF_TOPIC').length;
|
||||
@@ -10,7 +10,7 @@ const isOffTopic = (tags) => {
|
||||
export default (props) => (
|
||||
<span>
|
||||
{
|
||||
isOffTopic(props.comment.tags) ? (
|
||||
isOffTopic(props.comment.tags) && props.depth === 0 ? (
|
||||
<span className={styles.tag}>
|
||||
{t('off_topic')}
|
||||
</span>
|
||||
|
||||
@@ -8,11 +8,20 @@
|
||||
}
|
||||
|
||||
.tag {
|
||||
background: rgba(245, 188, 168, 0.6);
|
||||
background: #3D73D5;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
margin: 0px 5px;
|
||||
padding: 5px 5px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.viewingOption {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
:global(.coral-plugin-off-topic-comment) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const OFFTOPIC_TOGGLE_CHECKBOX = 'OFFTOPIC_TOGGLE_CHECKBOX';
|
||||
@@ -0,0 +1,14 @@
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {addTag, removeTag} from 'plugin-api/alpha/client/actions';
|
||||
import {commentBoxTagsSelector} from 'plugin-api/alpha/client/selectors';
|
||||
import OffTopicCheckbox from '../components/OffTopicCheckbox';
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
tags: commentBoxTagsSelector(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({addTag, removeTag}, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(OffTopicCheckbox);
|
||||
@@ -0,0 +1,30 @@
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {toggleCheckbox} from '../actions';
|
||||
import {commentClassNamesSelector} from 'plugin-api/alpha/client/selectors';
|
||||
import OffTopicFilter from '../components/OffTopicFilter';
|
||||
import {
|
||||
closeViewingOptions
|
||||
} from 'plugins/coral-plugin-viewing-options/client/actions';
|
||||
import {
|
||||
addCommentClassName,
|
||||
removeCommentClassName
|
||||
} from 'plugin-api/alpha/client/actions';
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
commentClassNames: commentClassNamesSelector(state),
|
||||
checked: state.coralPluginOfftopic.checked
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators(
|
||||
{
|
||||
toggleCheckbox,
|
||||
closeViewingOptions,
|
||||
addCommentClassName,
|
||||
removeCommentClassName
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(OffTopicFilter);
|
||||
@@ -1,11 +1,20 @@
|
||||
import OffTopicCheckbox from './components/OffTopicCheckbox';
|
||||
import OffTopicTag from './components/OffTopicTag';
|
||||
import translations from './translations.json';
|
||||
import OffTopicTag from './components/OffTopicTag';
|
||||
import OffTopicFilter from './containers/OffTopicFilter';
|
||||
import OffTopicCheckbox from './containers/OffTopicCheckbox';
|
||||
import reducer from './reducer';
|
||||
|
||||
/**
|
||||
* coral-plugin-offtopic depends on coral-plugin-viewing-options
|
||||
* in other to display filter and use the streamViewingOptions slot
|
||||
*/
|
||||
|
||||
export default {
|
||||
translations,
|
||||
reducer,
|
||||
slots: {
|
||||
commentInputDetailArea: [OffTopicCheckbox],
|
||||
commentInfoBar: [OffTopicTag]
|
||||
commentInfoBar: [OffTopicTag],
|
||||
viewingOptions: [OffTopicFilter]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {OFFTOPIC_TOGGLE_CHECKBOX} from './constants';
|
||||
|
||||
const initialState = {
|
||||
checked: false
|
||||
};
|
||||
|
||||
export default function offTopic (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case OFFTOPIC_TOGGLE_CHECKBOX: {
|
||||
return {
|
||||
...state,
|
||||
checked: !state.checked
|
||||
};
|
||||
}
|
||||
default :
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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"] }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import {VIEWING_OPTIONS_OPEN, VIEWING_OPTIONS_CLOSE} from './constants';
|
||||
|
||||
export const openViewingOptions = () => ({
|
||||
type: VIEWING_OPTIONS_OPEN
|
||||
});
|
||||
|
||||
export const closeViewingOptions = () => ({
|
||||
type: VIEWING_OPTIONS_CLOSE
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
.root {
|
||||
float: right;
|
||||
text-align: right;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
min-width: 220px;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list {
|
||||
background: white;
|
||||
position: absolute;
|
||||
box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.15);
|
||||
right: 3px;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.list > ul, .list > ul > li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.list > ul > li {
|
||||
padding: 10px;
|
||||
list-style: none;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './ViewingOptions.css';
|
||||
import {Slot, Icon} from 'plugin-api/beta/client/components';
|
||||
|
||||
const ViewingOptions = (props) => {
|
||||
const toggleOpen = () => {
|
||||
if (!props.open) {
|
||||
props.openViewingOptions();
|
||||
} else {
|
||||
props.closeViewingOptions();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className={cn([styles.root, 'coral-plugin-viewing-options'])}>
|
||||
<div>
|
||||
<a onClick={toggleOpen}>Viewing Options
|
||||
{props.open ? <Icon name="arrow_drop_up"/> : <Icon name="arrow_drop_down"/>}
|
||||
</a>
|
||||
</div>
|
||||
{
|
||||
props.open ? (
|
||||
<div className={cn([styles.list, 'coral-plugin-viewing-options-list'])}>
|
||||
<ul>
|
||||
{
|
||||
React.Children.map(<Slot fill="viewingOptions" />, (component) => {
|
||||
return React.createElement('li', {
|
||||
className: 'coral-plugin-viewing-options-item'
|
||||
}, component);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewingOptions;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const VIEWING_OPTIONS_OPEN = 'VIEWING_OPTIONS_OPEN';
|
||||
export const VIEWING_OPTIONS_CLOSE = 'VIEWING_OPTIONS_CLOSE';
|
||||
@@ -0,0 +1,13 @@
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import ViewingOptions from '../components/ViewingOptions';
|
||||
import {openViewingOptions, closeViewingOptions} from '../actions';
|
||||
|
||||
const mapStateToProps = ({coralPluginViewingOptions: state}) => ({
|
||||
open: state.open
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({openViewingOptions, closeViewingOptions}, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ViewingOptions);
|
||||
@@ -0,0 +1,9 @@
|
||||
import ViewingOptions from './containers/ViewingOptions';
|
||||
import reducer from './reducer';
|
||||
|
||||
export default {
|
||||
reducer,
|
||||
slots: {
|
||||
streamBox: [ViewingOptions]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import {VIEWING_OPTIONS_OPEN, VIEWING_OPTIONS_CLOSE} from './constants';
|
||||
|
||||
const initialState = {
|
||||
open: false
|
||||
};
|
||||
|
||||
export default function offTopic (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case VIEWING_OPTIONS_OPEN:
|
||||
return {
|
||||
...state,
|
||||
open: true
|
||||
};
|
||||
case VIEWING_OPTIONS_CLOSE:
|
||||
return {
|
||||
...state,
|
||||
open: false
|
||||
};
|
||||
default :
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
@@ -154,6 +154,7 @@ const config = {
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'plugin-api': path.resolve(__dirname, 'plugin-api/'),
|
||||
plugins: path.resolve(__dirname, 'plugins/'),
|
||||
pluginsConfig: pluginsConfigPath
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user