Skip to content

Commit

Permalink
adds a moderator window
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen Plaza committed Nov 7, 2015
1 parent b6779c8 commit 89dd2a3
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 27 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

##A Firebase Javascript Chat Application with Moderation

*(status: in progress, moderation not fully implemented)*
*(status: in progress)*

This package provides a simple, scalable Javascript chat application using the REACT framework and [Firebase](https://www.firebase.com/).

It currently allows annoymous user login to read/write messages to a common location (chat room). The most recent messages in the chat room are first loaded.

Main TODO: Moderation mode that requires messages to be approved by a trusted moderator.

## Quick Start Guide

This package contains a 'dist' directory which contains the chat widget in index.html (see release zip file). Before opening this page in a web browser, the index.html file should be modified. The Firebase account address must be specified in place of YOURADDR (signup for Firebase is free). The moderator mode can be toggled by setting data-moderatormode to "true" or "false". To use the moderator mode, an email/password must be registered for the given Firebase account.

This widget also allows one to enable a special moderator window so that all messages first go to that window and only show up on the main window if approved. To enable this, set data-moderatorwindow to "true". The moderator window should be "true" or "false" on both moderator and non-moderator windows.

## Installation and Usage

% npm install
Expand All @@ -35,7 +35,7 @@ This chat application orders messages by timestamp. If there are a lot of messa

## Moderation Mode

To enable moderation mode, set "data-moderationmode" to "true". Currently, moderation mode requires that a email/password has been setup on Firebase for the given reference location. The moderator mode lets the user delete certain posts. TBD: allow messages to pass to the moderator before being public.
To enable moderation mode, set "data-moderationmode" to "true". Currently, moderation mode requires that a email/password has been setup on Firebase for the given reference location. The moderator mode lets the user delete certain posts.

## A Note on Authentication and Permissions

Expand All @@ -53,7 +53,7 @@ For now, authentication is anonymous or uses a Firebase email/password for the m

## TODO

* Add a moderator mode to accept/reject posts.
* Hide old messages.
* Allow component to take a Firebase token for login to allow server side control of user access and moderation.
* Provide a panel to allow moderators to broadcast messages.
* Provide a panel to allow moderators to broadcast messages and polls.
* Export chat widget component for reuse in other REACT environments, allow for multiple chat windows.
2 changes: 1 addition & 1 deletion src/application.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</head>

<body>
<div id="firesidechat" data-fireaddr="https://<YOURADDR>.firebaseio.com/chat/exampleroom" data-moderatormode="false" />
<div id="firesidechat" data-fireaddr="https://boiling-fire-3068.firebaseio.com/chat/exampleroom" data-moderatormode="true" data-moderatorwindow="false" />

</body>
</html>
12 changes: 10 additions & 2 deletions src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,24 @@ var Master = require('./components/ChatWidget.react');
* Renders component just to a DIV with DVIDServiceWidget.
*/
function loadInterface() {
var fireaddr, ismoderator,
var fireaddr, ismoderator, hasmwindow,
element = document.getElementById("firesidechat");

fireaddr = element.getAttribute("data-fireaddr");

var moderator = element.getAttribute("data-moderatormode");
ismoderator = false;
if (moderator == "true") {
ismoderator = true;
}
React.render(<Master fireaddr={fireaddr} ismoderator={ismoderator} />, element);

var mwindow = element.getAttribute("data-moderatorwindow");
hasmwindow = false;
if (mwindow == "true") {
hasmwindow = true;
}

React.render(<Master fireaddr={fireaddr} ismoderator={ismoderator} hasmwindow={hasmwindow} />, element);
}

// do not render component until
Expand Down
8 changes: 7 additions & 1 deletion src/js/components/ChatWidget.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,18 @@ var ChatWidget = React.createClass({
this.setState({chatRef: chatRef});
},
render: function () {
var mwindow = <a />
if (this.props.hasmwindow && this.props.ismoderator) {
mwindow = <ChatWindow firebase={this.props.fireaddr} userName={this.state.userName} ismoderator={this.props.ismoderator} ismwindow={true} hasmwindow={true} />
}

if (this.state.userMode) {
return (
<div className="container-fluid">
<Login callback={this.userCallback} />
<p>{this.state.numUsers} users are connected</p>
<ChatWindow firebase={this.state.chatRef} userName={this.state.userName} ismoderator={this.props.ismoderator} />
{mwindow}
<ChatWindow firebase={this.props.fireaddr} userName={this.state.userName} ismoderator={this.props.ismoderator} ismwindow={false} hasmwindow={this.props.hasmwindow} />
</div>
);
} else {
Expand Down
78 changes: 61 additions & 17 deletions src/js/components/ChatWindow.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,31 @@ var ChatWindow = React.createClass({
getInitialState: function () {
return {
messages: [],
loadedAll: false
loadedAll: false,
readfirebase: null,
modfirebase: null
}
},
componentWillMount: function () {
var readfirebase = this.props.firebase + "/anonymous";
if (this.props.ismwindow) {
readfirebase = this.props.firebase + "/moderated";
}

var modfirebase = this.props.firebase + "/moderated";
if (this.props.ismwindow || (!(this.props.hasmwindow)) || this.props.ismoderator) {
modfirebase = this.props.firebase + "/anonymous";
}

var readfirebaseconn = new Firebase(readfirebase);
var modfirebaseconn = new Firebase(modfirebase);

this.setState({readfirebase: readfirebaseconn, modfirebase: modfirebaseconn});

// get initial messages (only grab last few) -- watch for changes
// check if child was removed
this.props.firebase.orderByChild("timestamp").limitToLast(InitialLimit).on("child_added", this.updateMsgs);
this.props.firebase.orderByChild("timestamp").limitToLast(InitialLimit).on("child_removed", this.delMsgs);
readfirebaseconn.orderByChild("timestamp").limitToLast(InitialLimit).on("child_added", this.updateMsgs);
readfirebaseconn.orderByChild("timestamp").limitToLast(InitialLimit).on("child_removed", this.delMsgs);
},
submitMsg: function () {
// grab text and submit with username -- do not explicitly extend messages?
Expand All @@ -25,7 +42,7 @@ var ChatWindow = React.createClass({
if (value === "") {
return;
}
this.props.firebase.push({
this.state.modfirebase.push({
text: value,
ismoderator: this.props.ismoderator,
timestamp: Firebase.ServerValue.TIMESTAMP,
Expand Down Expand Up @@ -57,11 +74,11 @@ var ChatWindow = React.createClass({
},
componentWillUnmount: function() {
// global deref -- dangerous ?!
this.props.firebase.off()
this.state.readfirebase.off()
},
showMore: function () {
// show in increments of PageLimit
var query = this.props.firebase.orderByChild("timestamp").endAt(this.state.messages[0].timestamp).limitToLast(PageLimit);
var query = this.state.readfirebase.orderByChild("timestamp").endAt(this.state.messages[0].timestamp).limitToLast(PageLimit);

// query older data and prepend to the currently loaded data
// only need to call once
Expand Down Expand Up @@ -90,17 +107,35 @@ var ChatWindow = React.createClass({
},
removeKey: function (delkey) {
// get child and delete
this.props.firebase.child(delkey).remove();
if (this.props.ismwindow) {
this.state.readfirebase.child(delkey).remove();
} else {
this.state.modfirebase.child(delkey).remove();
}
},
acceptMsg: function (val) {
// accept message
this.state.modfirebase.push({
text: val.text,
ismoderator: val.ismoderator,
timestamp: val.timestamp, // reset timestamp ??
username: val.username
});
this.state.readfirebase.child(val.key).remove();
},
render: function () {
// render submission box with button
var msgbox = (
<div className="form">
<input type="text" maxLength="256" className="form-control" id="message" />

<button style={{marginTop: "1em"}} type="button" className="btn btn-primary" onClick={this.submitMsg}>Submit Message</button>
</div>
);
var msgbox = <a />;

if (!this.props.ismwindow) {
var msgbox = (
<div className="form">
<input type="text" maxLength="256" className="form-control" id="message" />

<button style={{marginTop: "1em"}} type="button" className="btn btn-primary" onClick={this.submitMsg}>Submit Message</button>
</div>
);
}
// render text with messages (alternate colors)
var messages = this.state.messages.slice().reverse();
var count = 0;
Expand All @@ -113,7 +148,11 @@ var ChatWindow = React.createClass({
<center><a onClick={this.showMore}>Show More</a></center>
)
}


var modstr = "";
if (this.props.ismwindow) {
modstr = "mod";
}

return (
<div>
Expand All @@ -128,25 +167,30 @@ var ChatWindow = React.createClass({
color = "White";
}
count += 1;
var rowref = "crow"+count.toString();
var rowref = "crow"+count.toString() + modstr;

// moderator mode allows messages to be deleted
var that = this;
var removeMessage = <td />;
var acceptMessage = <td />;
var moderatorSym = <a />;

if (val.ismoderator) {
moderatorSym = <span className="glyphicon glyphicon-star" aria-hidden="true"></span>;
}

if (this.props.ismoderator) {
removeMessage = <td><button key={val.key} id={val.key} onClick={that.removeKey.bind(null, val.key)} className="btn btn-default" aria-label="Left Align"> <span className="glyphicon glyphicon-remove" aria-hidden="true"></span></button></td>;
removeMessage = <td><button key={val.key+modstr} id={val.key+modstr} onClick={that.removeKey.bind(null, val.key)} className="btn btn-default" aria-label="Left Align"> <span className="glyphicon glyphicon-remove" aria-hidden="true"></span></button></td>;

if (this.props.ismwindow) {
acceptMessage = <td><button key={val.key+modstr+"a"} id={val.key+modstr+"a"} onClick={that.acceptMsg.bind(null, val)} className="btn btn-default" aria-label="Left Align"> <span className="glyphicon glyphicon-ok" aria-hidden="true"></span></button></td>;
}
}

return (
<tr key={rowref} style={{backgroundColor: color}}>
{removeMessage}
{acceptMessage}
<td style={{width: "10em", padding: "1em"}}><b>{val.username}</b>{moderatorSym}:</td>
<td style={{padding: "1em"}}>{val.text}</td>
<td style={{width: "10em", align: "right", padding: "1em"}}><font style={{align: "right"}}><i>{timestr}</i></font></td>
Expand Down

0 comments on commit 89dd2a3

Please sign in to comment.