chat 0.0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +20 -0
- data/README.md +47 -22
- data/Rakefile +27 -1
- data/app/assets/config/chat_manifest.js +2 -0
- data/app/assets/javascripts/chat.js +16 -0
- data/app/assets/javascripts/chat/cable.js +13 -0
- data/app/assets/javascripts/chat/channels/message.coffee +75 -0
- data/app/assets/javascripts/chat/channels/notification.coffee +44 -0
- data/app/assets/javascripts/chat/channels/status.coffee +13 -0
- data/app/assets/javascripts/chat/emojionearea.min.js +1 -0
- data/app/assets/javascripts/chat/messages.coffee +44 -0
- data/app/assets/stylesheets/chat.css +15 -0
- data/app/assets/stylesheets/chat/chat.sass +25 -0
- data/app/assets/stylesheets/chat/checkbox.sass +80 -0
- data/app/assets/stylesheets/chat/emojionearea.min.css +1 -0
- data/app/assets/stylesheets/chat/header.sass +50 -0
- data/app/assets/stylesheets/chat/launch.sass +26 -0
- data/app/assets/stylesheets/chat/list.sass +45 -0
- data/app/assets/stylesheets/chat/message_form.sass +50 -0
- data/app/assets/stylesheets/chat/new.sass +53 -0
- data/app/assets/stylesheets/chat/transcript.sass +76 -0
- data/app/channels/chat/messages_channel.rb +11 -0
- data/app/channels/chat/notification_channel.rb +10 -0
- data/app/channels/chat/status_channel.rb +12 -0
- data/app/controllers/chat/application_controller.rb +5 -0
- data/app/controllers/chat/conversations_controller.rb +30 -0
- data/app/controllers/chat/messages_controller.rb +28 -0
- data/app/helpers/chat/application_helper.rb +62 -0
- data/app/jobs/chat/application_job.rb +5 -0
- data/app/jobs/chat/message_relay_job.rb +40 -0
- data/app/jobs/chat/notification_relay_job.rb +11 -0
- data/app/jobs/chat/status_relay_job.rb +9 -0
- data/app/mailers/chat/application_mailer.rb +6 -0
- data/app/models/chat/application_record.rb +3 -0
- data/app/models/chat/conversation.rb +9 -0
- data/app/models/chat/dot_command.rb +26 -0
- data/app/models/chat/dot_command/gif.rb +17 -0
- data/app/models/chat/dot_command/leave.rb +16 -0
- data/app/models/chat/dot_command/shrug.rb +27 -0
- data/app/models/chat/dot_command/validator.rb +23 -0
- data/app/models/chat/message.rb +29 -0
- data/app/models/chat/session.rb +10 -0
- data/app/views/chat/_chat.html.haml +14 -0
- data/app/views/chat/conversations/_index.html.haml +11 -0
- data/app/views/chat/conversations/_new.html.haml +10 -0
- data/app/views/chat/conversations/_show.html.haml +9 -0
- data/app/views/chat/conversations/create.js.erb +21 -0
- data/app/views/chat/conversations/show.js.erb +12 -0
- data/app/views/chat/messages/_message.haml +13 -0
- data/app/views/layouts/chat/application.html.erb +14 -0
- data/config/routes.rb +5 -0
- data/lib/chat.rb +29 -3
- data/lib/chat/engine.rb +5 -0
- data/lib/chat/user.rb +51 -0
- data/lib/chat/version.rb +1 -1
- data/lib/generators/chat/install/install_generator.rb +68 -0
- data/lib/generators/chat/install/templates/add_chat_to_users.rb +9 -0
- data/lib/generators/chat/install/templates/chat.rb +11 -0
- data/lib/generators/chat/install/templates/create_chat.rb +29 -0
- data/lib/tasks/chat_tasks.rake +4 -0
- metadata +296 -16
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -21
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/chat.gemspec +0 -22
@@ -0,0 +1,40 @@
|
|
1
|
+
module Chat
|
2
|
+
class MessageRelayJob < ApplicationJob
|
3
|
+
def perform(message_id)
|
4
|
+
message = Chat::Message.find(message_id)
|
5
|
+
ActionCable.server.broadcast(
|
6
|
+
"chats::#{message.conversation_id}::messages", render_message(message)
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
def render_message(message)
|
11
|
+
{
|
12
|
+
user: message.user_id,
|
13
|
+
avatar: avatar(message.user)
|
14
|
+
}.merge(content(message))
|
15
|
+
end
|
16
|
+
|
17
|
+
def avatar(user)
|
18
|
+
renderer.chat_avatar(user).to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def content(message)
|
22
|
+
if message.image?
|
23
|
+
{
|
24
|
+
message: renderer.image_tag(
|
25
|
+
message.image.url, class: "chat__message-image"
|
26
|
+
),
|
27
|
+
image: true
|
28
|
+
}
|
29
|
+
else
|
30
|
+
{ message: message.text, image: false }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def renderer
|
37
|
+
ApplicationController.helpers
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Chat
|
2
|
+
class NotificationRelayJob < ApplicationJob
|
3
|
+
def perform(message)
|
4
|
+
(message.conversation.user_ids - [message.user_id]).each do |user_id|
|
5
|
+
ActionCable.server.broadcast(
|
6
|
+
"users::#{user_id}::chats", chat_id: message.conversation_id
|
7
|
+
)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Chat
|
2
|
+
class DotCommand
|
3
|
+
attr_reader :text, :message
|
4
|
+
|
5
|
+
def initialize(message, text)
|
6
|
+
@message = message
|
7
|
+
@text = text
|
8
|
+
end
|
9
|
+
|
10
|
+
def command
|
11
|
+
@command ||= text[/^\.[a-zA-z]*/]
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
return false unless valid?
|
16
|
+
|
17
|
+
("Chat::DotCommand::" + command[1..-1].classify).constantize.new(
|
18
|
+
message, text[/[^\.[a-zA-z]*].*/]&.squish
|
19
|
+
).perform
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid?
|
23
|
+
Chat::DotCommand::Validator.new(command).perform
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Chat
|
2
|
+
class DotCommand
|
3
|
+
class Gif
|
4
|
+
attr_reader :message, :text
|
5
|
+
|
6
|
+
def initialize(message, text)
|
7
|
+
@message = message
|
8
|
+
@text = text
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform
|
12
|
+
message.image = open(::Giphy.random(text).image_url)
|
13
|
+
message.text = nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Chat
|
2
|
+
class DotCommand
|
3
|
+
class Leave
|
4
|
+
attr_reader :message, :text
|
5
|
+
|
6
|
+
def initialize(message, text)
|
7
|
+
@message = message
|
8
|
+
@text = text
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform
|
12
|
+
message.user.leave_conversation(message.conversation)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Chat
|
2
|
+
class DotCommand
|
3
|
+
class Shrug
|
4
|
+
attr_reader :message, :text
|
5
|
+
|
6
|
+
def initialize(message, text)
|
7
|
+
@message = message
|
8
|
+
@text = text
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform
|
12
|
+
message.text =
|
13
|
+
if text.blank?
|
14
|
+
shruggie
|
15
|
+
else
|
16
|
+
"#{text} #{shruggie}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def shruggie
|
23
|
+
"¯\\_(ツ)_/¯"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Chat
|
2
|
+
class DotCommand
|
3
|
+
class Validator
|
4
|
+
attr_reader :command
|
5
|
+
|
6
|
+
def initialize(command)
|
7
|
+
@command = command
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform
|
11
|
+
(commands + custom_commands).include? command
|
12
|
+
end
|
13
|
+
|
14
|
+
def commands
|
15
|
+
%w(.gif .shrug .leave)
|
16
|
+
end
|
17
|
+
|
18
|
+
def custom_commands
|
19
|
+
[]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Chat::Message < ApplicationRecord
|
2
|
+
has_attached_file :image
|
3
|
+
validates_attachment :image, content_type: {
|
4
|
+
content_type: ["image/jpg", "image/jpeg", "image/png", "image/gif"]
|
5
|
+
}
|
6
|
+
|
7
|
+
belongs_to :user, class_name: "::User"
|
8
|
+
belongs_to :conversation
|
9
|
+
|
10
|
+
delegate :name, to: :user
|
11
|
+
before_save :execute_dot_command
|
12
|
+
|
13
|
+
after_create_commit do
|
14
|
+
Chat::MessageRelayJob.perform_later(id)
|
15
|
+
Chat::NotificationRelayJob.perform_later(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def execute_dot_command
|
21
|
+
return unless dot_command.valid?
|
22
|
+
|
23
|
+
dot_command.perform
|
24
|
+
end
|
25
|
+
|
26
|
+
def dot_command
|
27
|
+
@dot_command ||= Chat::DotCommand.new(self, text)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Chat::Session < ApplicationRecord
|
2
|
+
belongs_to :conversation
|
3
|
+
belongs_to :user, class_name: "::User"
|
4
|
+
|
5
|
+
has_many :messages, foreign_key: [:user_id, :conversation_id],
|
6
|
+
primary_key: [:user_id, :conversation_id],
|
7
|
+
dependent: :destroy
|
8
|
+
|
9
|
+
validates :user_id, uniqueness: { scope: :conversation_id }
|
10
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
= launch_chat_fab(background, color)
|
2
|
+
|
3
|
+
#chat
|
4
|
+
.chat__header
|
5
|
+
= material_icon.menu.css_class('chat__settings')
|
6
|
+
.chat__flex
|
7
|
+
%h6 Chat
|
8
|
+
.chat__flex
|
9
|
+
= material_icon.close.css_class('chat__close')
|
10
|
+
|
11
|
+
= render 'chat/conversations/new'
|
12
|
+
.chat__list
|
13
|
+
= render 'chat/conversations/index'
|
14
|
+
.current_chat
|
@@ -0,0 +1,11 @@
|
|
1
|
+
- if chat_list.present?
|
2
|
+
.chat__index
|
3
|
+
- chat_list.each do |conversation|
|
4
|
+
= link_to [chat, conversation], remote: true, id: "chat__show-#{conversation.id}" do
|
5
|
+
= conversation.names.gsub(", #{current_user.name}", '').gsub("#{current_user.name}, ", '')
|
6
|
+
.chat__flex
|
7
|
+
- else
|
8
|
+
.chat__none-available
|
9
|
+
= material_icon.chat_bubble_outline
|
10
|
+
%h5 No Chats
|
11
|
+
= link_to 'Start a Chat', nil, class: 'chat__start-new'
|
@@ -0,0 +1,10 @@
|
|
1
|
+
= form_for [chat, Chat::Conversation.new], remote: true do |f|
|
2
|
+
- if chatable_users.present?
|
3
|
+
.chat__user-check-boxes
|
4
|
+
= chatable_user_check_boxes(f)
|
5
|
+
.chat__flex
|
6
|
+
= f.submit 'Start Chatting'
|
7
|
+
- else
|
8
|
+
.chat__none-available
|
9
|
+
= material_icon.people
|
10
|
+
%h5 No Chatable Users
|
@@ -0,0 +1,9 @@
|
|
1
|
+
.chat__transcript{ data: { channel: 'messages', current: { chat: @conversation.id, user: current_user.id }}}
|
2
|
+
= render @conversation.messages
|
3
|
+
|
4
|
+
.chat__flex
|
5
|
+
= form_for [chat, @conversation, Chat::Message.new], remote: true do |f|
|
6
|
+
= material_icon.insert_photo.css_class("chat__insert-image")
|
7
|
+
= f.text_area :text, placeholder: 'Send message...', autocomplete: 'off'
|
8
|
+
%span.bar
|
9
|
+
= f.file_field :image
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<% if @conversation.valid? %>
|
2
|
+
$('#chat .chat__list').html("<%= j render 'chat/conversations/index' %>");
|
3
|
+
$('#chat .chat__list').css('display', 'none');
|
4
|
+
$('.new_conversation').replaceWith("<%= j render 'chat/conversations/new' %>");
|
5
|
+
$('.current_chat').html("<%= j render 'chat/conversations/show' %>");
|
6
|
+
$('.current_chat').css('display', 'flex');
|
7
|
+
$('.chat__transcript').scrollTop($('.chat__transcript').prop('scrollHeight'));
|
8
|
+
App.chat_message.followCurrentChat();
|
9
|
+
<% elsif @conversation.errors.messages[:sessions].present? %>
|
10
|
+
error_msg =
|
11
|
+
`<div class='chat__errors'>
|
12
|
+
<%= j @conversation.errors.values.flatten.join(', ') %>
|
13
|
+
</div>`;
|
14
|
+
if($('.chat__errors').length == 0) {
|
15
|
+
$('.chat__user-check-boxes').prepend(error_msg);
|
16
|
+
}
|
17
|
+
<% else %>
|
18
|
+
<% id = Chat.joins(:chat_users).find_by(chat_users: { user_id: @conversation.user_ids }).id %>
|
19
|
+
$('.new_conversation').replaceWith("<%= j render 'chats/new' %>");
|
20
|
+
$("a[href='/chats/<%= id %>']").click();
|
21
|
+
<% end %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
$('.current_chat').html("<%= j render 'chat/conversations/show' %>");
|
2
|
+
$('.new_message textarea').emojioneArea()
|
3
|
+
$('.chat__list').css('display', 'none');
|
4
|
+
$('.new_conversation').css('display', 'none');
|
5
|
+
$('.current_chat').css('display', 'flex');
|
6
|
+
App.chat_message.followCurrentChat();
|
7
|
+
$("#new_message").fileupload();
|
8
|
+
if($("#chat__show-<%= @conversation.id %>").hasClass('chat__has_new_message')) {
|
9
|
+
App.chat_notification.decrement();
|
10
|
+
$("#chat__show-<%= @conversation.id %>").removeClass('chat__has_new_message')
|
11
|
+
}
|
12
|
+
$('.chat__transcript').scrollTop($('.chat__transcript').prop('scrollHeight'));
|
@@ -0,0 +1,13 @@
|
|
1
|
+
- if message.image?
|
2
|
+
.chat__image-message{ class: "#{'current' if message.user == current_user}" }
|
3
|
+
= image_tag message.image.url
|
4
|
+
- else
|
5
|
+
.chat__message
|
6
|
+
- if message.user == current_user
|
7
|
+
.chat__flex
|
8
|
+
.text.right= raw(message.text)
|
9
|
+
= chat_avatar(message.user)
|
10
|
+
- else
|
11
|
+
= chat_avatar(message.user)
|
12
|
+
.text.left= raw(message.text)
|
13
|
+
.chat__flex
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Chat</title>
|
5
|
+
<%= stylesheet_link_tag "chat/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "chat/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
data/lib/chat.rb
CHANGED
@@ -1,5 +1,31 @@
|
|
1
|
-
require "
|
1
|
+
require "chat/engine"
|
2
|
+
require "chat/version"
|
3
|
+
require "chat/user"
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
+
require "paperclip"
|
6
|
+
require "material_icons"
|
7
|
+
require "coffee-rails"
|
8
|
+
require "jquery-rails"
|
9
|
+
require "composite_primary_keys"
|
10
|
+
require "jquery-fileupload-rails"
|
11
|
+
require "giphy"
|
12
|
+
require "sidekiq"
|
13
|
+
|
14
|
+
module Chat
|
15
|
+
# The helper method used to distinguish if a user is logged in
|
16
|
+
mattr_accessor :signed_in
|
17
|
+
|
18
|
+
# The before_action method to validate users are logged in
|
19
|
+
mattr_accessor :logged_in_check
|
20
|
+
|
21
|
+
# The field that contains the users avatar
|
22
|
+
mattr_accessor :user_avatar
|
23
|
+
|
24
|
+
def self.table_name_prefix
|
25
|
+
"chat_"
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.setup
|
29
|
+
yield self
|
30
|
+
end
|
5
31
|
end
|
data/lib/chat/engine.rb
ADDED
data/lib/chat/user.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module Chat
|
4
|
+
module User
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
has_many :sessions, dependent: :destroy, class_name: "::Chat::Session"
|
9
|
+
has_many :conversations, through: :sessions,
|
10
|
+
class_name: "::Chat::Conversation"
|
11
|
+
|
12
|
+
validates :chat_status, inclusion: { in: %w(online offline) }
|
13
|
+
|
14
|
+
after_commit :broadcast_status,
|
15
|
+
if: proc { |u| u.previous_changes.key?(:chat_status) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
"#{first_name} #{last_name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def online
|
23
|
+
update(chat_status: "online")
|
24
|
+
end
|
25
|
+
|
26
|
+
def offline
|
27
|
+
update(chat_status: "offline")
|
28
|
+
end
|
29
|
+
|
30
|
+
def leave_conversation(conversation)
|
31
|
+
session = sessions.where(conversation: conversation)
|
32
|
+
session.destroy
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
def first_name
|
37
|
+
"first_name"
|
38
|
+
end
|
39
|
+
|
40
|
+
def last_name
|
41
|
+
"last_name"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def broadcast_status
|
48
|
+
Chat::StatusRelayJob.perform_later(self)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|