chat 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +47 -22
  4. data/Rakefile +27 -1
  5. data/app/assets/config/chat_manifest.js +2 -0
  6. data/app/assets/javascripts/chat.js +16 -0
  7. data/app/assets/javascripts/chat/cable.js +13 -0
  8. data/app/assets/javascripts/chat/channels/message.coffee +75 -0
  9. data/app/assets/javascripts/chat/channels/notification.coffee +44 -0
  10. data/app/assets/javascripts/chat/channels/status.coffee +13 -0
  11. data/app/assets/javascripts/chat/emojionearea.min.js +1 -0
  12. data/app/assets/javascripts/chat/messages.coffee +44 -0
  13. data/app/assets/stylesheets/chat.css +15 -0
  14. data/app/assets/stylesheets/chat/chat.sass +25 -0
  15. data/app/assets/stylesheets/chat/checkbox.sass +80 -0
  16. data/app/assets/stylesheets/chat/emojionearea.min.css +1 -0
  17. data/app/assets/stylesheets/chat/header.sass +50 -0
  18. data/app/assets/stylesheets/chat/launch.sass +26 -0
  19. data/app/assets/stylesheets/chat/list.sass +45 -0
  20. data/app/assets/stylesheets/chat/message_form.sass +50 -0
  21. data/app/assets/stylesheets/chat/new.sass +53 -0
  22. data/app/assets/stylesheets/chat/transcript.sass +76 -0
  23. data/app/channels/chat/messages_channel.rb +11 -0
  24. data/app/channels/chat/notification_channel.rb +10 -0
  25. data/app/channels/chat/status_channel.rb +12 -0
  26. data/app/controllers/chat/application_controller.rb +5 -0
  27. data/app/controllers/chat/conversations_controller.rb +30 -0
  28. data/app/controllers/chat/messages_controller.rb +28 -0
  29. data/app/helpers/chat/application_helper.rb +62 -0
  30. data/app/jobs/chat/application_job.rb +5 -0
  31. data/app/jobs/chat/message_relay_job.rb +40 -0
  32. data/app/jobs/chat/notification_relay_job.rb +11 -0
  33. data/app/jobs/chat/status_relay_job.rb +9 -0
  34. data/app/mailers/chat/application_mailer.rb +6 -0
  35. data/app/models/chat/application_record.rb +3 -0
  36. data/app/models/chat/conversation.rb +9 -0
  37. data/app/models/chat/dot_command.rb +26 -0
  38. data/app/models/chat/dot_command/gif.rb +17 -0
  39. data/app/models/chat/dot_command/leave.rb +16 -0
  40. data/app/models/chat/dot_command/shrug.rb +27 -0
  41. data/app/models/chat/dot_command/validator.rb +23 -0
  42. data/app/models/chat/message.rb +29 -0
  43. data/app/models/chat/session.rb +10 -0
  44. data/app/views/chat/_chat.html.haml +14 -0
  45. data/app/views/chat/conversations/_index.html.haml +11 -0
  46. data/app/views/chat/conversations/_new.html.haml +10 -0
  47. data/app/views/chat/conversations/_show.html.haml +9 -0
  48. data/app/views/chat/conversations/create.js.erb +21 -0
  49. data/app/views/chat/conversations/show.js.erb +12 -0
  50. data/app/views/chat/messages/_message.haml +13 -0
  51. data/app/views/layouts/chat/application.html.erb +14 -0
  52. data/config/routes.rb +5 -0
  53. data/lib/chat.rb +29 -3
  54. data/lib/chat/engine.rb +5 -0
  55. data/lib/chat/user.rb +51 -0
  56. data/lib/chat/version.rb +1 -1
  57. data/lib/generators/chat/install/install_generator.rb +68 -0
  58. data/lib/generators/chat/install/templates/add_chat_to_users.rb +9 -0
  59. data/lib/generators/chat/install/templates/chat.rb +11 -0
  60. data/lib/generators/chat/install/templates/create_chat.rb +29 -0
  61. data/lib/tasks/chat_tasks.rake +4 -0
  62. metadata +296 -16
  63. data/.gitignore +0 -9
  64. data/Gemfile +0 -4
  65. data/LICENSE.txt +0 -21
  66. data/bin/console +0 -14
  67. data/bin/setup +0 -8
  68. data/chat.gemspec +0 -22
@@ -0,0 +1,5 @@
1
+ module Chat
2
+ class ApplicationJob < ActiveJob::Base
3
+ queue_as :default
4
+ end
5
+ end
@@ -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,9 @@
1
+ module Chat
2
+ class StatusRelayJob < ApplicationJob
3
+ def perform(user)
4
+ ActionCable.server.broadcast(
5
+ "chat::status", user_id: user.id, status: user.chat_status
6
+ )
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Chat
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ class Chat::ApplicationRecord < ActiveRecord::Base
2
+ self.abstract_class = true
3
+ end
@@ -0,0 +1,9 @@
1
+ class Chat::Conversation < ApplicationRecord
2
+ has_many :messages
3
+ has_many :sessions, dependent: :destroy
4
+ has_many :users, through: :sessions, class_name: "::User"
5
+
6
+ validates :sessions, presence: {
7
+ message: "At least one user must be selected"
8
+ }
9
+ 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>
@@ -0,0 +1,5 @@
1
+ Chat::Engine.routes.draw do
2
+ resources :conversations, only: [:show, :create] do
3
+ resources :messages, only: [:create, :destroy]
4
+ end
5
+ end
@@ -1,5 +1,31 @@
1
- require "conversation/version"
1
+ require "chat/engine"
2
+ require "chat/version"
3
+ require "chat/user"
2
4
 
3
- module Conversation
4
- # Your code goes here...
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
@@ -0,0 +1,5 @@
1
+ module Chat
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Chat
4
+ end
5
+ end
@@ -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