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.
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