Last active
May 3, 2020 13:59
-
-
Save tbcooney/198266a6e7cc6672882e15595118fc0b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# chatroom_channel.rb | |
class ChatroomsChannel < ApplicationCable::Channel | |
def subscribed | |
current_user.chatrooms.each do |chatroom| | |
stream_from "chatrooms:#{chatroom.id}" | |
end | |
end | |
def unsubscribed | |
stop_all_streams | |
end | |
def send_message(data) | |
@chatroom = Chatroom.find(data["chatroom_id"]) | |
@account = Account.find(data["account_id"]) | |
@facility = Facility.find(data["facility_id"]) | |
message = @chatroom.messages.create( | |
account: @account, | |
source: @facility, | |
content: data["message"], | |
user: current_user | |
) | |
MessageRelayJob.perform_later(message, @facility.id, @chatroom.id) | |
Rails.logger.info data | |
end | |
def typing(data) | |
ActionCable.server.broadcast "chatrooms:#{data["chatroom_id"]}", { | |
typing: data['typing'], | |
user_name: current_user.name, | |
chatroom_id: data["chatroom_id"] | |
} | |
end | |
end | |
# app/jobs/message_relay_job.rb | |
class MessageRelayJob < ApplicationJob | |
queue_as :default | |
def perform(message, facility_id, chatroom_id) | |
chatroom = Chatroom.find(chatroom_id) | |
facility = Facility.find(facility_id) | |
ActionCable.server.broadcast "chatrooms:#{message.chatroom.id}", { | |
message_id: message.id, | |
message: render_message(message, facility, chatroom), | |
user_name: message.user.name, | |
user_id: message.user.id, | |
body: message.content.body.to_plain_text, | |
chatroom_id: message.chatroom.id | |
} | |
end | |
def render_message(message, facility, chatroom) | |
ApplicationController.renderer.render( | |
partial: 'admin/facilities/chatrooms/chatroom/messages/message', | |
locals: { message: message, facility: facility, chatroom: chatroom } | |
) | |
end | |
end | |
# chatroom/message.rb | |
class Chatroom::Message < ApplicationRecord | |
has_rich_text :content | |
belongs_to :account | |
belongs_to :source, polymorphic: true | |
belongs_to :chatroom | |
belongs_to :user | |
validates :content, | |
presence: true | |
end | |
# chatrooms_controller.js | |
import { Controller } from "stimulus" | |
import Rails from "@rails/ujs" | |
import consumer from '../../channels/consumer' | |
export default class extends Controller { | |
static targets = ['input', 'form', 'status'] | |
connect() { | |
this.initChannel() | |
this.onKeydown = this.onKeydown.bind(this) | |
this.inputTarget.addEventListener('keydown', this.onKeydown) | |
} | |
disconnect() { | |
this.inputTarget.removeEventListener('keydown', this.onKeydown) | |
} | |
initChannel() { | |
this.lastReadChannel = consumer.subscriptions.create('LastReadChannel') | |
this.channel = consumer.subscriptions.create('ChatroomsChannel', { | |
connected: this._cableConnected.bind(this), | |
disconnected: this._cableDisconnected.bind(this), | |
received: this._cableReceived.bind(this), | |
}); | |
this.typingHandler = this.typing.bind(this) | |
this.inputTarget.addEventListener('keydown', this.typingHandler) | |
this.stoppedTyping = this.stoppedTyping.bind(this) | |
this.inputTarget.addEventListener('blur', this.stoppedTyping) | |
} | |
_cableConnected() { | |
// Called when the subscription is ready for use on the server | |
} | |
_cableDisconnected() { | |
// Called when the subscription has been terminated by the server | |
} | |
_cableReceived(data) { | |
// Called when there's incoming data on the websocket for this channel | |
if (data.typing && data.typing == "started") { | |
this.statusTarget.classList.add("active") | |
this.statusTarget.innerHTML = `${data.user_name} is typing...` | |
} else { | |
this.statusTarget.classList.remove("active") | |
this.statusTarget.innerHTML = "" | |
} | |
if (!data.typing) { | |
const active_chatroom = document.querySelectorAll(`[data-behavior='messages'][data-chatroom-id='${data.chatroom_id}'`) | |
if (active_chatroom.length > 0) { | |
// Check to see if the user has the chatroom open | |
// With tabbed browsing, there is a reasonable chance that any given webpage is in the background and thus not visible to the user. | |
if (document.hidden) { | |
// 1. Check to see if there is a divider on the page | |
const strike = document.querySelectorAll(".strike") | |
if (strike.length > 0) { | |
// 2. If there is no divider, insert one | |
active_chatroom.append("<div class='strike'><span>Unread Messages</span></div>") | |
} | |
// 3. Send Web Notification | |
// https://developer.mozilla.org/en-US/docs/Web/API/notification | |
if (Notification.permission === "granted") { | |
new Notification(data.user_name, { | |
body: data.body | |
}) | |
} | |
} else { | |
// 4. Update last_read_at stamp | |
this.lastReadChannel.perform("update", { chatroom_id: data.chatroom_id }) | |
} | |
// 5. Insert the message | |
active_chatroom[0].insertAdjacentHTML('beforeend', data.message) | |
this.scrollToBottom() | |
} else { | |
// 6. Highlight the channel in the sidebar | |
document.querySelector(`[data-behavior='chatroom-link'][data-chatroom-id='${data.chatroom_id}']`).classList.add("font-bold") | |
} | |
} | |
} | |
typing() { | |
// Don't broadcast if we're already typing | |
if(!this.isTyping) { | |
this.isTyping = true | |
const chatroom_id = document.querySelector("[data-behavior='messages'][data-chatroom-id]").dataset.chatroomId | |
this.channel.perform('typing', { chatroom_id: chatroom_id, typing: 'started' } ) | |
} | |
// Do this no matter what so it resets the timer | |
this.startTypingTimer() | |
} | |
stoppedTyping() { | |
this.isTyping = false | |
this.stopTypingTimer() | |
const chatroom_id = document.querySelector("[data-behavior='messages'][data-chatroom-id]").dataset.chatroomId | |
this.channel.perform('typing', { chatroom_id: chatroom_id, typing: 'stopped' } ) | |
} | |
startTypingTimer() { | |
// Clear the old timer or it'll still fire after 10 seconds. We're effectively resetting the timer. | |
this.stopTypingTimer() | |
// No need to save a reference to bound function since we don't need to reference it to stop the timer. | |
// After 10 seconds of not typing, don't consider the user to be typing | |
this.typingTimeoutID = setTimeout(this.stoppedTyping.bind(this), 10000) | |
} | |
stopTypingTimer() { | |
if(this.typingTimeoutID) { | |
clearTimeout(this.typingTimeoutID) | |
} | |
} | |
send_message(chatroom_id, message) { | |
const facility_id = document.querySelector(`meta[name="current-facility-id"]`).getAttribute('content') | |
this.channel.perform('send_message', { | |
chatroom_id: chatroom_id, | |
message: message, | |
facility_id: facility_id | |
}) | |
} | |
onKeydown(e) { | |
if (e.keyCode == 13 && !e.shiftKey) { | |
if (this.inputTarget.innerHTML.length < 1) { | |
this.inputTarget.classList.add('opps-shake-it') | |
// Remove the class after the animation completes | |
setTimeout(() => { | |
this.inputTarget.classList.remove('opps-shake-it'); | |
}, 300) | |
e.preventDefault() | |
} else { | |
//Submit the form | |
this.submit(e) | |
return false | |
} | |
} | |
} | |
submit(e) { | |
e.preventDefault() | |
const chatroom_id = document.querySelector("[data-behavior='messages'][data-chatroom-id]").dataset.chatroomId | |
const message = this.inputTarget.value | |
this.send_message(chatroom_id, message) | |
this.inputTarget.value = "" | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Assuming
this.inputTarget
is a<trix-editor>
, here's a better way to check for "blank" using Trix's document model: