diff --git a/.eslintignore b/.eslintignore index cd0c80673..6fecd9d9b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,4 +10,5 @@ plugins/* !plugins/coral-plugin-like !plugins/coral-plugin-mod !plugins/coral-plugin-love +!plugins/coral-plugin-viewing-options node_modules diff --git a/.gitignore b/.gitignore index 11e738d57..abd490356 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,6 @@ plugins/* !plugins/coral-plugin-like !plugins/coral-plugin-mod !plugins/coral-plugin-love +!plugins/coral-plugin-viewing-options **/node_modules/* diff --git a/client/.eslintrc.json b/client/.eslintrc.json index 1144f985d..5735a91a1 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -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"] }] } } diff --git a/client/coral-embed-stream/src/actions/stream.js b/client/coral-embed-stream/src/actions/stream.js index 6a9e47838..9b5d94a9b 100644 --- a/client/coral-embed-stream/src/actions/stream.js +++ b/client/coral-embed-stream/src/actions/stream.js @@ -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 +}); diff --git a/client/coral-embed-stream/src/components/Comment.css b/client/coral-embed-stream/src/components/Comment.css index b0fca75c8..4ff0586da 100644 --- a/client/coral-embed-stream/src/components/Comment.css +++ b/client/coral-embed-stream/src/components/Comment.css @@ -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);} diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js index 228dd4c41..b5eca4ff2 100644 --- a/client/coral-embed-stream/src/components/Comment.js +++ b/client/coral-embed-stream/src/components/Comment.js @@ -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,14 +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 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 @@ -73,8 +73,7 @@ const ActionButton = ({children}) => { ); }; -class Comment extends React.Component { - +export default class Comment extends React.Component { constructor(props) { super(props); @@ -272,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(); @@ -349,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 (
@@ -376,11 +406,13 @@ class Comment extends React.Component { @@ -542,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; diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 31012c9f1..586d28f23 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -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 { />}
:

{asset.settings.closedMessage}

} - {loggedIn && + + {loggedIn && ( } + /> + )} + +
+ +
{/* 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} /> - :
+ :
: ({ assetUrl: state.stream.assetUrl, activeTab: state.embed.activeTab, previousTab: state.embed.previousTab, + commentClassNames: state.stream.commentClassNames }); const mapDispatchToProps = (dispatch) => diff --git a/client/coral-embed-stream/src/graphql/index.js b/client/coral-embed-stream/src/graphql/index.js index 04225eb96..d9d4be12e 100644 --- a/client/coral-embed-stream/src/graphql/index.js +++ b/client/coral-embed-stream/src/graphql/index.js @@ -144,7 +144,7 @@ const extension = { parent_id, asset_id, action_summaries: [], - tags, + tags: tags.map((t) => ({name: t, __typename: 'Tag'})), status: null, replyCount: 0, parent: parent_id diff --git a/client/coral-embed-stream/src/reducers/index.js b/client/coral-embed-stream/src/reducers/index.js index 5ddac4567..61fe950c9 100644 --- a/client/coral-embed-stream/src/reducers/index.js +++ b/client/coral-embed-stream/src/reducers/index.js @@ -4,6 +4,6 @@ import stream from './stream'; export default { embed, - stream, config, + stream, }; diff --git a/client/coral-embed-stream/src/reducers/stream.js b/client/coral-embed-stream/src/reducers/stream.js index 59f068530..9e03e3528 100644 --- a/client/coral-embed-stream/src/reducers/stream.js +++ b/client/coral-embed-stream/src/reducers/stream.js @@ -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; } diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 04b2536a3..c9304c0cf 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -178,6 +178,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; diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 939068e83..22a315623 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -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 ( -
+
{getSlotElements(fill, {...rest, config})}
); diff --git a/client/coral-framework/components/index.js b/client/coral-framework/components/index.js new file mode 100644 index 000000000..b7aaef665 --- /dev/null +++ b/client/coral-framework/components/index.js @@ -0,0 +1 @@ +export {default as Slot} from './Slot'; diff --git a/client/coral-framework/helpers/camelize.js b/client/coral-framework/helpers/camelize.js new file mode 100644 index 000000000..ffc09cebb --- /dev/null +++ b/client/coral-framework/helpers/camelize.js @@ -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() : ''; +} diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js index 0eaecbadd..e0aa05c80 100644 --- a/client/coral-framework/helpers/plugins.js +++ b/client/coral-framework/helpers/plugins.js @@ -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); } diff --git a/client/coral-plugin-api/index.js b/client/coral-plugin-api/index.js deleted file mode 100644 index f0f5ad9cc..000000000 --- a/client/coral-plugin-api/index.js +++ /dev/null @@ -1 +0,0 @@ -export {withReaction} from '../coral-framework/hocs'; diff --git a/client/coral-plugin-moderation/styles.css b/client/coral-plugin-moderation/styles.css index 2e928d3df..734cc1e6f 100644 --- a/client/coral-plugin-moderation/styles.css +++ b/client/coral-plugin-moderation/styles.css @@ -1,4 +1,6 @@ .moderationLink { + display: inline; + a { color: #679af3; text-decoration: none; diff --git a/plugin-api/.babelrc b/plugin-api/.babelrc new file mode 100644 index 000000000..63b1c53de --- /dev/null +++ b/plugin-api/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} diff --git a/plugin-api/.eslintrc.json b/plugin-api/.eslintrc.json new file mode 100644 index 000000000..5735a91a1 --- /dev/null +++ b/plugin-api/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/plugin-api/alpha/client/actions/index.js b/plugin-api/alpha/client/actions/index.js new file mode 100644 index 000000000..8e8e11c0a --- /dev/null +++ b/plugin-api/alpha/client/actions/index.js @@ -0,0 +1,2 @@ +export {addTag, removeTag} from 'coral-plugin-commentbox/actions'; +export {addCommentClassName, removeCommentClassName} from 'coral-embed-stream/src/actions/stream'; diff --git a/plugin-api/alpha/client/components/index.js b/plugin-api/alpha/client/components/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/plugin-api/alpha/client/selectors/index.js b/plugin-api/alpha/client/selectors/index.js new file mode 100644 index 000000000..ad76326cf --- /dev/null +++ b/plugin-api/alpha/client/selectors/index.js @@ -0,0 +1,2 @@ +export const commentBoxTagsSelector = (state) => state.commentBox.tags; +export const commentClassNamesSelector = (state) => state.stream.commentClassNames; diff --git a/plugin-api/beta/client/components/index.js b/plugin-api/beta/client/components/index.js new file mode 100644 index 000000000..0330892bf --- /dev/null +++ b/plugin-api/beta/client/components/index.js @@ -0,0 +1,2 @@ +export {Slot} from 'coral-framework/components'; +export {Icon} from 'coral-ui'; diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js new file mode 100644 index 000000000..7ec875656 --- /dev/null +++ b/plugin-api/beta/client/hocs/index.js @@ -0,0 +1 @@ +export {withReaction} from 'coral-framework/hocs'; diff --git a/plugin-api/beta/client/services/index.js b/plugin-api/beta/client/services/index.js new file mode 100644 index 000000000..16872b9f3 --- /dev/null +++ b/plugin-api/beta/client/services/index.js @@ -0,0 +1 @@ +export {t} from 'coral-framework/services/i18n'; diff --git a/plugin-api/client/actions/index.js b/plugin-api/client/actions/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/plugin-api/client/components/index.js b/plugin-api/client/components/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/plugin-api/client/hocs/index.js b/plugin-api/client/hocs/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/coral-plugin-love/client/LoveButton.js b/plugins/coral-plugin-love/client/LoveButton.js index 430e2658d..4e9b56d44 100644 --- a/plugins/coral-plugin-love/client/LoveButton.js +++ b/plugins/coral-plugin-love/client/LoveButton.js @@ -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 = () => { diff --git a/plugins/coral-plugin-offtopic/client/actions.js b/plugins/coral-plugin-offtopic/client/actions.js new file mode 100644 index 000000000..9cb3947f8 --- /dev/null +++ b/plugins/coral-plugin-offtopic/client/actions.js @@ -0,0 +1,5 @@ +import {OFFTOPIC_TOGGLE_CHECKBOX} from './constants'; + +export const toggleCheckbox = () => ({ + type: OFFTOPIC_TOGGLE_CHECKBOX +}); diff --git a/plugins/coral-plugin-offtopic/client/components/OffTopicCheckbox.js b/plugins/coral-plugin-offtopic/client/components/OffTopicCheckbox.js index 0383507d1..70876f739 100644 --- a/plugins/coral-plugin-offtopic/client/components/OffTopicCheckbox.js +++ b/plugins/coral-plugin-offtopic/client/components/OffTopicCheckbox.js @@ -1,39 +1,45 @@ import React from 'react'; -import {connect} from 'react-redux'; -import {bindActionCreators} from 'redux'; -import {addTag, removeTag} 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'; - handleChange = (e) => { - if (e.target.checked) { - this.props.addTag(this.label); - } else { - const idx = this.props.commentBox.tags.indexOf(this.label); + componentDidMount() { + this.clearTagsHook = this.props.registerHook('postSubmit', () => { + const idx = this.props.tags.indexOf(this.label); this.props.removeTag(idx); + }); + } + + componentWillUnmount() { + this.props.unregisterHook(this.clearTagsHook); + } + + handleChange = (e) => { + const {addTag, removeTag} = this.props; + if (e.target.checked) { + addTag(this.label); + } else { + const idx = this.props.tags.indexOf(this.label); + removeTag(idx); } } render() { return (
- + { + !this.props.isReply ? ( + + ) : null + }
); } } - -const mapStateToProps = ({commentBox}) => ({commentBox}); - -const mapDispatchToProps = (dispatch) => - bindActionCreators({addTag, removeTag}, dispatch); - -export default connect(mapStateToProps, mapDispatchToProps)(OffTopicCheckbox); diff --git a/plugins/coral-plugin-offtopic/client/components/OffTopicFilter.js b/plugins/coral-plugin-offtopic/client/components/OffTopicFilter.js new file mode 100644 index 000000000..fea2ac6fb --- /dev/null +++ b/plugins/coral-plugin-offtopic/client/components/OffTopicFilter.js @@ -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 ( +
+ +
+ ); + } +} diff --git a/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js b/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js index 89a4d03aa..e8029ce5e 100644 --- a/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js +++ b/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js @@ -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) => ( { - isOffTopic(props.comment.tags) ? ( + isOffTopic(props.comment.tags) && props.depth === 0 ? ( {t('off_topic')} diff --git a/plugins/coral-plugin-offtopic/client/components/styles.css b/plugins/coral-plugin-offtopic/client/components/styles.css index f3e290b09..94d7eaa85 100644 --- a/plugins/coral-plugin-offtopic/client/components/styles.css +++ b/plugins/coral-plugin-offtopic/client/components/styles.css @@ -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; +} diff --git a/plugins/coral-plugin-offtopic/client/constants.js b/plugins/coral-plugin-offtopic/client/constants.js new file mode 100644 index 000000000..122d7de17 --- /dev/null +++ b/plugins/coral-plugin-offtopic/client/constants.js @@ -0,0 +1 @@ +export const OFFTOPIC_TOGGLE_CHECKBOX = 'OFFTOPIC_TOGGLE_CHECKBOX'; diff --git a/plugins/coral-plugin-offtopic/client/containers/OffTopicCheckbox.js b/plugins/coral-plugin-offtopic/client/containers/OffTopicCheckbox.js new file mode 100644 index 000000000..286fa8d9d --- /dev/null +++ b/plugins/coral-plugin-offtopic/client/containers/OffTopicCheckbox.js @@ -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); diff --git a/plugins/coral-plugin-offtopic/client/containers/OffTopicFilter.js b/plugins/coral-plugin-offtopic/client/containers/OffTopicFilter.js new file mode 100644 index 000000000..98f5f7aea --- /dev/null +++ b/plugins/coral-plugin-offtopic/client/containers/OffTopicFilter.js @@ -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); diff --git a/plugins/coral-plugin-offtopic/client/index.js b/plugins/coral-plugin-offtopic/client/index.js index f3459441d..383c7351e 100644 --- a/plugins/coral-plugin-offtopic/client/index.js +++ b/plugins/coral-plugin-offtopic/client/index.js @@ -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] } }; diff --git a/plugins/coral-plugin-offtopic/client/reducer.js b/plugins/coral-plugin-offtopic/client/reducer.js new file mode 100644 index 000000000..adab987a0 --- /dev/null +++ b/plugins/coral-plugin-offtopic/client/reducer.js @@ -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; + } +} diff --git a/plugins/coral-plugin-viewing-options/client/.babelrc b/plugins/coral-plugin-viewing-options/client/.babelrc new file mode 100644 index 000000000..60be246eb --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} \ No newline at end of file diff --git a/plugins/coral-plugin-viewing-options/client/.eslintrc.json b/plugins/coral-plugin-viewing-options/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/plugins/coral-plugin-viewing-options/client/actions.js b/plugins/coral-plugin-viewing-options/client/actions.js new file mode 100644 index 000000000..8a6473e3c --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/actions.js @@ -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 +}); diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css new file mode 100644 index 000000000..47cda9a1a --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css @@ -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; +} diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js new file mode 100644 index 000000000..513d9340b --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js @@ -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 ( +
+ + { + props.open ? ( +
+
    + { + React.Children.map(, (component) => { + return React.createElement('li', { + className: 'coral-plugin-viewing-options-item' + }, component); + }) + } +
+
+ ) : null + } +
+ ); +}; + +export default ViewingOptions; diff --git a/plugins/coral-plugin-viewing-options/client/constants.js b/plugins/coral-plugin-viewing-options/client/constants.js new file mode 100644 index 000000000..586fe7a28 --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/constants.js @@ -0,0 +1,2 @@ +export const VIEWING_OPTIONS_OPEN = 'VIEWING_OPTIONS_OPEN'; +export const VIEWING_OPTIONS_CLOSE = 'VIEWING_OPTIONS_CLOSE'; diff --git a/plugins/coral-plugin-viewing-options/client/containers/ViewingOptions.js b/plugins/coral-plugin-viewing-options/client/containers/ViewingOptions.js new file mode 100644 index 000000000..767a9197d --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/containers/ViewingOptions.js @@ -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); diff --git a/plugins/coral-plugin-viewing-options/client/index.js b/plugins/coral-plugin-viewing-options/client/index.js new file mode 100644 index 000000000..519ce42aa --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/index.js @@ -0,0 +1,9 @@ +import ViewingOptions from './containers/ViewingOptions'; +import reducer from './reducer'; + +export default { + reducer, + slots: { + streamBox: [ViewingOptions] + } +}; diff --git a/plugins/coral-plugin-viewing-options/client/reducer.js b/plugins/coral-plugin-viewing-options/client/reducer.js new file mode 100644 index 000000000..ab9f077df --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/reducer.js @@ -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; + } +} diff --git a/plugins/coral-plugin-viewing-options/index.js b/plugins/coral-plugin-viewing-options/index.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/plugins/coral-plugin-viewing-options/index.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/webpack.config.js b/webpack.config.js index ddd2f9577..ce1e0bd29 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -154,6 +154,7 @@ const config = { }, resolve: { alias: { + 'plugin-api': path.resolve(__dirname, 'plugin-api/'), plugins: path.resolve(__dirname, 'plugins/'), pluginsConfig: pluginsConfigPath },