chat 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|