diff --git a/app/assets/stylesheets/chat.scss b/app/assets/stylesheets/chat.scss
index 45103a983..495c17dde 100644
--- a/app/assets/stylesheets/chat.scss
+++ b/app/assets/stylesheets/chat.scss
@@ -1,5 +1,27 @@
// High level class
-.chatchannel {
+.chat {
+ display: flex;
+ height: inherit;
+}
+
+.chat__channels {
+ width: 125px;
+ border: 1px solid #c9c9c9;
+ border-right: none;
+ background: #ededed;
+ height: inherit;
+}
+
+.chat__channels--hidden {
+ display: none;
+}
+
+.chat__activechat {
+ flex-grow: 1;
+ height: inherit;
+}
+
+.activechatchannel {
height: inherit;
max-height: inherit;
display: flex;
@@ -8,7 +30,7 @@
border: 1px solid #c9c9c9;
}
-.chatchannel__messages {
+.activechatchannel__messages {
font-size: 15px;
padding: 10px 0px;
display: flex;
@@ -20,9 +42,10 @@
text-align: left;
overflow-wrap: break-word;
word-wrap: break-word;
+ overscroll-behavior-y: contain;
}
-.chatchannel__alerts {
+.activechatchannel__alerts {
position: relative;
}
@@ -43,7 +66,7 @@
display: none;
}
-.chatchannel__form {
+.activechatchannel__form {
border-top: 1px solid #c9c9c9;
background: #ededed;
width: 100%;
@@ -61,10 +84,67 @@
}
+.chatchannels {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: inherit;
+}
+
+.chatchanneltab {
+ height: 30px;
+ width: 100%;
+ margin-bottom: 5px;
+ border: none;
+ border-left: 8px solid transparent;
+ background: inherit;
+ text-align: start;
+ text-transform: uppercase;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.chatchanneltab--active {
+ background: white;
+ border-left: 8px solid black;
+}
+
+.chatchanneltab--inactive {
+ &:hover {
+ background: #e3e3e3;
+ }
+}
+
+@keyframes example {
+ // animation-name: example;
+ // animation-duration: 4s;
+ // animation-iteration-count: infinite;
+ from {
+ border-left: 8px solid transparent;
+ }
+ to {
+ border-left: 8px solid #ffb6c1;
+ }
+}
+
+.chatchannels__channelslist {
+ padding: 10px 0;
+ flex-grow: 1;
+}
+
+.chatchannels__misc {
+ height: 70px;
+ border-top: 1px solid #c9c9c9
+}
+
.chatmessage__username {
font-weight: 600;
}
+.chatmessage__username--link {
+ color: inherit;
+}
+
.chatmessage__divider {
}
@@ -73,6 +153,13 @@
}
+.chatmessage__currentuser {
+ background: black;
+ color: white;
+ padding: 1px 5px;
+ border-radius: 2px;
+}
+
// Messagecomposer
.messagecomposer {
display: flex;
@@ -87,7 +174,7 @@
margin: 10px;
padding: 6px;
resize: none;
- width: 80%;
+ flex-grow: 1;
}
.messagecomposer__submit {
@@ -96,4 +183,5 @@
border-radius: 3px;
background: #66e2d5;
font-weight: 500px;
+ width: 50px;
}
diff --git a/app/controllers/chat_channels_controller.rb b/app/controllers/chat_channels_controller.rb
index f141ab877..5ad220d9e 100644
--- a/app/controllers/chat_channels_controller.rb
+++ b/app/controllers/chat_channels_controller.rb
@@ -23,22 +23,23 @@ class ChatChannelsController < ApplicationController
banned_user.add_role :banned
banned_user.messages.each(&:destroy!)
Pusher.trigger(@chat_channel.id, "user-banned", { userId: banned_user.id }.to_json)
- render json: { success: "banned!" }, status: 200
+ render json: { status: "success", message: "banned!" }, status: 200
else
- render json: { error: "username not found" }, status: 400
+ render json: { status: "error", message: "username not found" }, status: 400
end
when "/unban"
banned_user = User.find_by_username(command[1])
if banned_user
banned_user.remove_role :banned
- render json: { success: "unbanned!" }, status: 200
+ render json: { status: "success", message: "unbanned!" }, status: 200
else
- render json: { error: "username not found" }, status: 400
+ render json: { status: "error", message: "username not found" }, status: 400
end
when "/clearchannel"
@chat_channel.clear_channel
+ render json: { status: "success", message: "cleared!" }, status: 200
else
- render json: { error: "invalid command" }, status: 400
+ render json: { status: "error", message: "invalid command" }, status: 400
end
end
diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb
index 128241614..6df1cfd01 100644
--- a/app/controllers/messages_controller.rb
+++ b/app/controllers/messages_controller.rb
@@ -16,13 +16,20 @@ class MessagesController < ApplicationController
end
if success
- render json: ["Message created"], status: 201
+ render json: { status: "success", message: "Message created" }, status: 201
else
- result = "Message created but could not trigger Pusher"
- render json: [result, @message.to_json], status: 201
+ error_message = "Message created but could not trigger Pusher"
+ render json: { status: "error", message: error_message }, status: 201
end
else
- render json: e.message, status: 401
+ render json: {
+ status: "error",
+ message: {
+ chat_channel_id: @message.chat_channel_id,
+ message: @message.errors.full_messages,
+ type: "error",
+ },
+ }, status: 401
end
end
@@ -31,14 +38,30 @@ class MessagesController < ApplicationController
def create_pusher_payload(new_message)
{
user_id: new_message.user.id,
+ chat_channel_id: new_message.chat_channel.id,
username: new_message.user.username,
- message: new_message.message_markdown,
+ message: new_message.message_html,
timestamp: new_message.timestamp,
color: new_message.user.bg_color_hex,
}.to_json
end
def message_params
- params.require(:message).permit(:message_html, :user_id, :chat_channel_id)
+ params.require(:message).permit(:message_markdown, :user_id, :chat_channel_id)
+ end
+
+ def user_not_authorized
+ respond_to do |format|
+ format.json do
+ render json: {
+ status: "error",
+ message: {
+ chat_channel_id: message_params[:chat_channel_id],
+ message: "You can not do that because you are banned",
+ type: "error",
+ },
+ }, status: 401
+ end
+ end
end
end
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
index 2d10b2875..b4e1d7ebf 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -38,7 +38,15 @@ class PagesController < ApplicationController
end
end
- def live; end
+ def live
+ @chat_channels = [ChatChannel.find_by_channel_name("Workshop")].to_json
+ end
+
+ def chat
+ workshop = ChatChannel.find_by_channel_name("General")
+ meta = ChatChannel.find_by_channel_name("Meta")
+ @chat_channels = [workshop, meta].to_json
+ end
private # helpers
diff --git a/app/javascript/chat/actions.js b/app/javascript/chat/actions.js
index 6510c0520..a401540cf 100644
--- a/app/javascript/chat/actions.js
+++ b/app/javascript/chat/actions.js
@@ -1,5 +1,5 @@
-export function getAllMessages(successCb, failureCb) {
- fetch('/chat_channels/1', {
+export function getAllMessages(channelId, successCb, failureCb) {
+ fetch(`/chat_channels/${channelId}`, {
Accept: 'application/json',
'Content-Type': 'application/json',
})
@@ -8,7 +8,7 @@ export function getAllMessages(successCb, failureCb) {
.catch(failureCb);
}
-export function sendMessage(message, successCb, failureCb) {
+export function sendMessage(activeChannelId, message, successCb, failureCb) {
fetch('/messages', {
method: 'POST',
headers: {
@@ -18,9 +18,9 @@ export function sendMessage(message, successCb, failureCb) {
},
body: JSON.stringify({
message: {
- message_html: message,
+ message_markdown: message,
user_id: window.currentUser.id,
- chat_channel_id: '1',
+ chat_channel_id: activeChannelId,
},
}),
credentials: 'same-origin',
@@ -30,8 +30,13 @@ export function sendMessage(message, successCb, failureCb) {
.catch(failureCb);
}
-export function conductModeration(message, successCb, failureCb) {
- fetch('/chat_channels/1/moderate', {
+export function conductModeration(
+ activeChannelId,
+ message,
+ successCb,
+ failureCb,
+) {
+ fetch(`/chat_channels/${activeChannelId}/moderate`, {
method: 'POST',
headers: {
Accept: 'application/json',
diff --git a/app/javascript/chat/channels.jsx b/app/javascript/chat/channels.jsx
new file mode 100644
index 000000000..49c36adcd
--- /dev/null
+++ b/app/javascript/chat/channels.jsx
@@ -0,0 +1,34 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+
+const Channels = ({ activeChannelId, chatChannels, handleSwitchChannel }) => {
+ const channels = chatChannels.map(channel => {
+ const otherClassname =
+ parseInt(activeChannelId, 10) === channel.id
+ ? 'chatchanneltab--active'
+ : 'chatchanneltab--inactive';
+ return (
+
+ );
+ });
+
+ return (
+
+ );
+};
+
+Channels.propTypes = {
+ activeChannelId: PropTypes.number.isRequired,
+ chatChannels: PropTypes.array.isRequired,
+ handleSwitchChannel: PropTypes.func.isRequired,
+};
+
+export default Channels;
diff --git a/app/javascript/chat/chat.jsx b/app/javascript/chat/chat.jsx
index 4f383673b..b522d54b5 100644
--- a/app/javascript/chat/chat.jsx
+++ b/app/javascript/chat/chat.jsx
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { conductModeration, getAllMessages, sendMessage } from './actions';
import { hideMessages, scrollToBottom, setupObserver } from './util';
import Alert from './alert';
+import Channels from './channels';
import Compose from './compose';
import Message from './message';
import setupPusher from './pusher';
@@ -20,19 +21,25 @@ class Chat extends Component {
this.receiveNewMessage = this.receiveNewMessage.bind(this);
this.clearChannel = this.clearChannel.bind(this);
this.redactUserMessages = this.redactUserMessages.bind(this);
+ this.handleSwitchChannel = this.handleSwitchChannel.bind(this);
+ const chatChannels = JSON.parse(this.props.chatChannels);
+ const chatOptions = JSON.parse(this.props.chatOptions);
this.state = {
- messages: [],
+ messages: chatChannels.reduce(
+ (accumulator, target) => ({ ...accumulator, [target.id]: [] }),
+ {},
+ ),
scrolled: false,
showAlert: false,
+ chatChannels,
+ activeChannelId: chatChannels[0].id,
+ showChannelsList: chatOptions.showChannelsList,
};
}
componentDidMount() {
- getAllMessages(this.receiveAllMessages);
- setupPusher(this.props.pusherKey, {
- messageCreated: this.receiveNewMessage,
- channelCleared: this.clearChannel,
- redactUserMessages: this.redactUserMessages,
+ this.state.chatChannels.forEach(channel => {
+ this.setupChannel(channel.id);
});
setupObserver(this.observerCallback);
}
@@ -43,8 +50,18 @@ class Chat extends Component {
}
}
+ setupChannel(channelId) {
+ getAllMessages(channelId, this.receiveAllMessages);
+ setupPusher(this.props.pusherKey, {
+ channelId,
+ messageCreated: this.receiveNewMessage,
+ channelCleared: this.clearChannel,
+ redactUserMessages: this.redactUserMessages,
+ });
+ }
+
observerCallback(entries) {
- entries.forEach((entry) => {
+ entries.forEach(entry => {
if (entry.isIntersecting) {
this.setState({ scrolled: false, showAlert: false });
} else {
@@ -54,57 +71,93 @@ class Chat extends Component {
}
receiveAllMessages(res) {
- this.setState({ messages: res.messages });
+ const { chatChannelId, messages } = res;
+ const newMessages = { ...this.state.messages, [chatChannelId]: messages };
+ this.setState({ messages: newMessages });
}
receiveNewMessage(message) {
- const newMessages = this.state.messages.slice();
+ const receivedChatChannelId = message.chat_channel_id;
+ const newMessages = this.state.messages[receivedChatChannelId].slice();
newMessages.push(message);
if (newMessages.length > 150) {
newMessages.shift();
}
+ const newShowAlert =
+ this.state.activeChannelId === receivedChatChannelId
+ ? { showAlert: this.state.scrolled }
+ : {};
this.setState({
- messages: newMessages,
- showAlert: this.state.scrolled,
+ ...newShowAlert,
+ messages: {
+ ...this.state.messages,
+ [receivedChatChannelId]: newMessages,
+ },
});
}
redactUserMessages(res) {
- const newMessages = hideMessages(this.state.messages.slice(), res.userId);
+ // This is shallow clone. This might cause a problem
+ const clonedMessages = Object.assign({}, this.state.messages);
+ const newMessages = hideMessages(clonedMessages, res.userId);
this.setState({ messages: newMessages });
}
- clearChannel() {
- this.setState({ messages: [] });
+ clearChannel(res) {
+ const newMessages = { ...this.state.messages, [res.chat_channel_id]: [] };
+ this.setState({ messages: newMessages });
}
handleKeyDown(e) {
if (e.keyCode === 13) {
e.preventDefault();
- this.handleMessageSubmit(e.target.value);
- e.target.value = '';
+ if (e.target.value.length > 0) {
+ this.handleMessageSubmit(e.target.value);
+ e.target.value = '';
+ }
}
}
handleMessageSubmit(message) {
// should check if user has the priviledge
if (message[0] === '/') {
- conductModeration(message, this.handleSuccess, this.handleFailure);
+ conductModeration(
+ this.state.activeChannelId,
+ message,
+ this.handleSuccess,
+ this.handleFailure,
+ );
} else {
- sendMessage(message, this.handleSuccess, this.handleFailure);
+ sendMessage(
+ this.state.activeChannelId,
+ message,
+ this.handleSuccess,
+ this.handleFailure,
+ );
}
}
+ handleSwitchChannel(e) {
+ e.preventDefault();
+ this.setState({
+ activeChannelId: e.target.dataset.channelId,
+ scrolled: false,
+ showAlert: false,
+ });
+ }
+
handleSubmitOnClick(e) {
e.preventDefault();
const message = document.getElementById('messageform').value;
- this.handleMessageSubmit(message);
- document.getElementById('messageform').value = '';
+ if (message.length > 0) {
+ this.handleMessageSubmit(message);
+ document.getElementById('messageform').value = '';
+ }
}
handleSuccess(response) {
- if (Object.prototype.hasOwnProperty.call(response, 'error')) {
- console.log(response.error);
+ if (response.status === 'error') {
+ this.receiveNewMessage(response.message);
}
}
@@ -113,28 +166,44 @@ class Chat extends Component {
}
renderMessage() {
- return this.state.messages.map(message => (
+ const { activeChannelId, messages } = this.state;
+ return messages[activeChannelId].map(message => (
));
}
- render() {
+ renderChatChannels() {
+ if (this.state.showChannelsList) {
+ return (
+
+
+
+ );
+ }
+ return '';
+ }
+
+ renderActiveChatChannel() {
return (
-
-
+
+
-
+
-
+
);
}
+
+ render() {
+ return (
+
+ {this.renderChatChannels()}
+
{this.renderActiveChatChannel()}
+
+ );
+ }
}
Chat.propTypes = {
pusherKey: PropTypes.number.isRequired,
+ chatChannels: PropTypes.array.isRequired,
+ chatOptions: PropTypes.object.isRequired,
};
export default Chat;
diff --git a/app/javascript/chat/message.jsx b/app/javascript/chat/message.jsx
index 063f59a4c..a6e8d152e 100644
--- a/app/javascript/chat/message.jsx
+++ b/app/javascript/chat/message.jsx
@@ -1,28 +1,53 @@
import { h } from 'preact';
import PropTypes from 'prop-types';
+import ErrorMessage from './messages/errorMessage';
+import Hiddenmessage from './messages/hiddenMessage';
/*
* The prop also contain timeStamp, which is currently not in used
+ *
*/
-const Message = ({
- user, message, color, hidden,
-}) => {
+const Message = ({ user, message, color, type }) => {
const spanStyle = { color };
- const linkStyle = { color: 'inherit' };
- const messageStyle = { color: hidden ? 'lightgray' : 'inherit' };
+
+ if (type === 'error') {
+ return ;
+ } else if (type === 'hidden') {
+ return ;
+ }
+
+ const re = new RegExp(`@${window.currentUser.username}`);
+ const match = re.exec(message);
+ let messageArea;
+
+ if (match) {
+ messageArea = (
+
+ {message.substr(0, match.index)}
+
+ {`@${window.currentUser.username}`}
+
+ {message.substr(match.index + match[0].length)}
+
+ );
+ } else {
+ messageArea = {message};
+ }
return (
-
+
{user}
:
-
- {hidden ? '' : message}
-
+ {messageArea}
);
};
@@ -31,11 +56,13 @@ Message.propTypes = {
user: PropTypes.string.isRequired,
color: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
- hidden: PropTypes.bool,
+ type: PropTypes.string,
+ // hidden: PropTypes.bool,
+ // error: PropTypes.bool,
};
Message.defaultProps = {
- hidden: false,
+ type: 'normalMessage',
};
export default Message;
diff --git a/app/javascript/chat/messages/errorMessage.jsx b/app/javascript/chat/messages/errorMessage.jsx
new file mode 100644
index 000000000..e5b5bc3f3
--- /dev/null
+++ b/app/javascript/chat/messages/errorMessage.jsx
@@ -0,0 +1,23 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+
+const ErrorMessage = ({ message }) => {
+ const errorStyle = { color: 'darksalmon', 'font-size': '13px' };
+ return (
+
+
+ {'Sorry '}
+
+ {`@${window.currentUser.username}`}
+
+ {` ${message}`}
+
+
+ );
+};
+
+ErrorMessage.propTypes = {
+ message: PropTypes.string.isRequired,
+};
+
+export default ErrorMessage;
diff --git a/app/javascript/chat/messages/hiddenMessage.jsx b/app/javascript/chat/messages/hiddenMessage.jsx
new file mode 100644
index 000000000..60f1e3588
--- /dev/null
+++ b/app/javascript/chat/messages/hiddenMessage.jsx
@@ -0,0 +1,30 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+
+const HiddenMessage = ({ user, color }) => {
+ const spanStyle = { color };
+ return (
+
+ );
+};
+
+HiddenMessage.propTypes = {
+ user: PropTypes.string.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default HiddenMessage;
diff --git a/app/javascript/chat/pusher.js b/app/javascript/chat/pusher.js
index 1a1285566..c006a256c 100644
--- a/app/javascript/chat/pusher.js
+++ b/app/javascript/chat/pusher.js
@@ -6,7 +6,7 @@ export default function setupPusher(key, callbackObjects) {
encrypted: true,
});
- const channel = pusher.subscribe('1');
+ const channel = pusher.subscribe(callbackObjects.channelId.toString());
channel.bind('message-created', callbackObjects.messageCreated);
channel.bind('channel-cleared', callbackObjects.channelCleared);
channel.bind('user-banned', callbackObjects.redactUserMessages);
diff --git a/app/javascript/chat/util.js b/app/javascript/chat/util.js
index 6ef4809ea..0ce533ba7 100644
--- a/app/javascript/chat/util.js
+++ b/app/javascript/chat/util.js
@@ -40,11 +40,17 @@ export function setupObserver(callback) {
}
export function hideMessages(messages, userId) {
- const newMessages = messages.map((message) => {
- if (message.user_id === userId) {
- return Object.assign({ hidden: true }, message);
- }
- return message;
- });
- return newMessages;
+ const cleanedMessages = Object.keys(messages).reduce(
+ (accumulator, channelId) => {
+ const newMessages = messages[channelId].map(message => {
+ if (message.user_id === userId) {
+ return Object.assign({ type: 'hidden' }, message);
+ }
+ return message;
+ });
+ return { ...accumulator, [channelId]: newMessages };
+ },
+ {},
+ );
+ return cleanedMessages;
}
diff --git a/app/javascript/packs/chat.jsx b/app/javascript/packs/chat.jsx
index ad79452ab..c4865cef5 100644
--- a/app/javascript/packs/chat.jsx
+++ b/app/javascript/packs/chat.jsx
@@ -2,21 +2,35 @@ import { h, render } from 'preact';
import { getUserDataAndCsrfToken } from '../chat/util';
import Chat from '../chat/chat';
-HTMLDocument.prototype.ready = new Promise((resolve) => {
- if (document.readyState !== 'loading') { return resolve(); }
+HTMLDocument.prototype.ready = new Promise(resolve => {
+ if (document.readyState !== 'loading') {
+ return resolve();
+ }
document.addEventListener('DOMContentLoaded', () => resolve());
+ return null;
});
-document.ready
- .then(getUserDataAndCsrfToken()
- .then((currentUser) => {
- if (document.getElementById('chat')) {
- const { pusherKey } = document.getElementById('chat').dataset;
- window.currentUser = currentUser;
- window.csrfToken = document.querySelector("meta[name='csrf-token']").content;
- render(
- ,
- document.getElementById('chat'),
- );
- }
- }));
+document.ready.then(
+ getUserDataAndCsrfToken().then(currentUser => {
+ if (document.getElementById('chat')) {
+ const { chatChannels, pusherKey, chatOptions } = document.getElementById(
+ 'chat',
+ ).dataset;
+ window.currentUser = currentUser;
+ window.csrfToken = document.querySelector(
+ "meta[name='csrf-token']",
+ ).content;
+ const root = render(
+ ,
+ document.getElementById('chat'),
+ );
+ window.InstantClick.on('change', () => {
+ render('', document.body, root);
+ });
+ }
+ }),
+);
diff --git a/app/models/chat_channel.rb b/app/models/chat_channel.rb
index 7b7fa5a3b..92edffd88 100644
--- a/app/models/chat_channel.rb
+++ b/app/models/chat_channel.rb
@@ -1,11 +1,12 @@
class ChatChannel < ApplicationRecord
has_many :messages
- validates :channel_type, presence: true, inclusion: { in: %w(open) }
+ validates :channel_type, presence: true, inclusion: { in: %w(open invite_only) }
def clear_channel
messages.each(&:destroy!)
- Pusher.trigger(id, "channel-cleared", [].to_json)
+ Pusher.trigger(id, "channel-cleared", { chat_channel_id: id }.to_json)
+ true
rescue Pusher::Error => e
logger.info "PUSHER ERROR: #{e.message}"
end
diff --git a/app/models/message.rb b/app/models/message.rb
index 08f2e9268..beb13996e 100644
--- a/app/models/message.rb
+++ b/app/models/message.rb
@@ -2,10 +2,11 @@ class Message < ApplicationRecord
belongs_to :user
belongs_to :chat_channel
- validates :message_html, presence: true, length: { maximum: 600 }
- validates :message_markdown, presence: true
+ validates :message_html, presence: true
+ validates :message_markdown, presence: true, length: { maximum: 600 }
before_validation :evaluate_markdown
+ before_validation :evaluate_channel_permission
def timestamp
created_at.strftime("%H:%M:%S")
@@ -14,6 +15,16 @@ class Message < ApplicationRecord
private
def evaluate_markdown
- self.message_markdown = message_html
+ self.message_html = message_markdown
+ end
+
+ def evaluate_channel_permission
+ channel_type = ChatChannel.find(chat_channel_id).channel_type
+ return if channel_type == "open"
+ if user&.has_role?(:chatroom_beta_tester)
+ # this is fine
+ else
+ errors.add(:base, "You are not a participant of this chat channel.")
+ end
end
end
diff --git a/app/models/role.rb b/app/models/role.rb
index d33286eae..c192d9c41 100644
--- a/app/models/role.rb
+++ b/app/models/role.rb
@@ -26,6 +26,7 @@ class Role < ApplicationRecord
level_1_member
workshop_pass
video_permission
+ chatroom_beta_tester
),
}
scopify
diff --git a/app/views/chat_channels/show.json.jbuilder b/app/views/chat_channels/show.json.jbuilder
index fa3155479..63402baad 100644
--- a/app/views/chat_channels/show.json.jbuilder
+++ b/app/views/chat_channels/show.json.jbuilder
@@ -1,3 +1,4 @@
+
json.messages @chat_channel.messages.order("created_at DESC").limit(50).reverse do |message|
json.user_id message.user.id
json.username message.user.username
@@ -5,3 +6,7 @@ json.messages @chat_channel.messages.order("created_at DESC").limit(50).reverse
json.timestamp message.timestamp
json.color message.user.bg_color_hex
end
+
+json.key_format! camelize: :lower
+
+json.chat_channel_id @chat_channel.id
diff --git a/app/views/pages/chat.html.erb b/app/views/pages/chat.html.erb
new file mode 100644
index 000000000..ef18d33aa
--- /dev/null
+++ b/app/views/pages/chat.html.erb
@@ -0,0 +1,24 @@
+<% if user_signed_in? %>
+ <%= javascript_pack_tag 'chat', defer: true %>
+<% end %>
+<% title "DEV Chat" %>
+
+
+
+
+
+
+
" data-chat-channels="<%= @chat_channels %>" data-chat-options="<%= {showChannelsList:true}.to_json %>">
+
diff --git a/app/views/pages/live.html.erb b/app/views/pages/live.html.erb
index 032fc6f2a..8c3a95abe 100644
--- a/app/views/pages/live.html.erb
+++ b/app/views/pages/live.html.erb
@@ -39,7 +39,7 @@
<% if (Flipflop.sendbird?) %>
" data-sendbird-livechat-url="<%= ENV["SENDBIRD_LIVECHAT_URL"] %>">
<% else %>
-
">
+
" data-chat-channels="<%= @chat_channels %>" data-chat-options="<%= {showChannelsList:false}.to_json %>">
<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index f27fd4ca1..1bcd36e2d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -148,6 +148,7 @@ Rails.application.routes.draw do
get "/infiniteloop" => "pages#infinite_loop"
get "/faq" => "pages#faq"
get "/live" => "pages#live"
+ get "/chat" => "pages#chat"
get "/swagnets" => "pages#swagnets"
get "/welcome" => "pages#welcome"
get "/💸", to: redirect("t/hiring")
diff --git a/db/migrate/20180508170132_add_channel_name_to_chat_channels.rb b/db/migrate/20180508170132_add_channel_name_to_chat_channels.rb
new file mode 100644
index 000000000..eeffb53fd
--- /dev/null
+++ b/db/migrate/20180508170132_add_channel_name_to_chat_channels.rb
@@ -0,0 +1,5 @@
+class AddChannelNameToChatChannels < ActiveRecord::Migration[5.1]
+ def change
+ add_column :chat_channels, :channel_name, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index e2116f712..e90334e2e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -161,6 +161,7 @@ ActiveRecord::Schema.define(version: 20180508200948) do
end
create_table "chat_channels", force: :cascade do |t|
+ t.string "channel_name"
t.string "channel_type", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
diff --git a/spec/factories/messages.rb b/spec/factories/messages.rb
index e69de29bb..9e306f0f6 100644
--- a/spec/factories/messages.rb
+++ b/spec/factories/messages.rb
@@ -0,0 +1,13 @@
+FactoryBot.define do
+ factory :message do
+ message_markdown { Faker::Lorem.sentence }
+ chat_channel
+
+ trait :ignore_after_callback do
+ after(:build) do |message|
+ message.define_singleton_method(:evaluate_channel_permission) {}
+ message.define_singleton_method(:evaluate_markdown) {}
+ end
+ end
+ end
+end
diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb
index 52e50d3c9..aa8a034c6 100644
--- a/spec/models/message_spec.rb
+++ b/spec/models/message_spec.rb
@@ -1,8 +1,12 @@
require "rails_helper"
RSpec.describe Message, type: :model do
- it { is_expected.to belong_to(:user) }
- it { is_expected.to belong_to(:chat_channel) }
- it { is_expected.to validate_presence_of(:message_html) }
- it { is_expected.to validate_presence_of(:message_markdown) }
+ describe "validations" do
+ subject { build(:message, :ignore_after_callback)}
+
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to belong_to(:chat_channel) }
+ it { is_expected.to validate_presence_of(:message_html) }
+ it { is_expected.to validate_presence_of(:message_markdown) }
+ end
end
diff --git a/spec/requests/messages_spec.rb b/spec/requests/messages_spec.rb
index 7778e4ffe..e52bff4d9 100644
--- a/spec/requests/messages_spec.rb
+++ b/spec/requests/messages_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe "Messages", type: :request do
describe "POST /messages" do
let(:new_message) do
{
- message_html: "hi",
+ message_markdown: "hi",
user_id: user.id,
chat_channel_id: chat_channel.id,
}