diff --git a/client/lib/main.less b/client/lib/main.less index 5b28a5c8..770ff250 100644 --- a/client/lib/main.less +++ b/client/lib/main.less @@ -1345,6 +1345,39 @@ iframe.js { opacity: 0; margin-left: 0; } + + .inline-voting { + .approve, .disapprove { + margin-left: 0.2em; + padding: 0 0.2em; + border-radius: 2px; + } + + .approve { + background: #e0ffe0; + color: #008000; + .touchable-bg(#e0ffe0); + } + + .disapprove { + background: #ffe0e0; + color: #800000; + .touchable-bg(#ffe0e0); + } + + .approved { + color: #008000; + } + + .rejected { + color: #800000; + } + + small, small * { + vertical-align: baseline; + color: #808080; + } + } } .message-tall { diff --git a/client/lib/ui/InlineVoting.js b/client/lib/ui/InlineVoting.js new file mode 100644 index 00000000..53e39fff --- /dev/null +++ b/client/lib/ui/InlineVoting.js @@ -0,0 +1,71 @@ +import React from 'react' +import Reflux from 'reflux' +import Immutable from 'immutable' + +import actions from '../actions' +import chat from '../stores/chat' +import Tree from '../Tree' +import FastButton from './FastButton' +import MessageText from './MessageText' + +export default React.createClass({ + displayName: 'InlineVoting', + + propTypes: { + message: React.PropTypes.instanceOf(Immutable.Map).isRequired, + tree: React.PropTypes.instanceOf(Tree).isRequired, + className: React.PropTypes.string, + title: React.PropTypes.string, + style: React.PropTypes.string, + }, + + mixins: [ + Reflux.connect(chat.store, 'chat'), + ], + + sendMessageIfPossible(text) { + if (this.state.chat.joined && this.state.chat.nick) { + actions.sendMessage(text, this.props.message.get('id')) + } + }, + + upvote(evt) { + this.sendMessageIfPossible('+1') + if (evt) evt.stopPropagation() + }, + + downvote(evt) { + this.sendMessageIfPossible('-1') + if (evt) evt.stopPropagation() + }, + + render() { + let upvotes = 0 + let downvotes = 0 + + this.props.message.get('children').map(id => { + const content = this.props.tree.get(id).get('content') + + if (/\s*\+1\s*/.test(content)) upvotes++ + if (/\s*-1\s*/.test(content)) downvotes++ + }) + + const result = upvotes - downvotes + const resultClass = (result > 0) ? 'approved' : (result < 0) ? 'rejected' : 'neutral' // eslint-disable-line + + const majorityPercent = Math.max(upvotes, downvotes) * 100 / (upvotes + downvotes) + const percentText = ' (' + Math.round(majorityPercent) + '% ' + ((result > 0) ? '+' : '-') + ')' + + return ( + + {upvotes} + + + {downvotes} + + {result} + {result !== 0 && {percentText}} + ) + }, + +}) diff --git a/client/lib/ui/Message.js b/client/lib/ui/Message.js index 463390cf..5ba89639 100644 --- a/client/lib/ui/Message.js +++ b/client/lib/ui/Message.js @@ -11,6 +11,7 @@ import Tree from '../Tree' import FastButton from './FastButton' import Embed from './Embed' import MessageText from './MessageText' +import InlineVoting from './InlineVoting' import ChatEntry from './ChatEntry' import LiveTimeAgo from './LiveTimeAgo' import KeyboardActionHandler from './KeyboardActionHandler' @@ -436,6 +437,15 @@ const Message = React.createClass({ ) lineClasses['line-emote'] = true + } else if (/^\/vote/.test(content) && content.length < 240) { + content = _.trim(content.replace(/^\/vote ?/, '')) + messageRender = ( +
+ + {messageAgo} + +
+ ) } else if (this.state.contentTall && this.props.roomSettings.get('collapse') !== false) { const action = contentExpanded ? 'collapse' : 'expand' const actionMethod = action + 'Content'