chat 0.2.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -8
  3. data/Rakefile +2 -0
  4. data/app/assets/javascripts/chat/channels/message.coffee +33 -43
  5. data/app/assets/javascripts/chat/channels/status.coffee +2 -2
  6. data/app/assets/javascripts/chat/messages.coffee +12 -11
  7. data/app/assets/stylesheets/chat/avatar_grid.sass +40 -0
  8. data/app/assets/stylesheets/chat/chat.sass +9 -4
  9. data/app/assets/stylesheets/chat/checkbox.sass +3 -3
  10. data/app/assets/stylesheets/chat/emojionearea.min.css +1 -1
  11. data/app/assets/stylesheets/chat/header.sass +25 -8
  12. data/app/assets/stylesheets/chat/launch.sass +39 -3
  13. data/app/assets/stylesheets/chat/list.sass +56 -23
  14. data/app/assets/stylesheets/chat/message_form.sass +7 -4
  15. data/app/assets/stylesheets/chat/new.sass +27 -22
  16. data/app/assets/stylesheets/chat/transcript.sass +60 -64
  17. data/app/channels/chat/messages_channel.rb +3 -2
  18. data/app/channels/chat/notification_channel.rb +2 -0
  19. data/app/channels/chat/status_channel.rb +12 -10
  20. data/app/controllers/chat/application_controller.rb +5 -1
  21. data/app/controllers/chat/conversations_controller.rb +24 -2
  22. data/app/controllers/chat/messages_controller.rb +8 -2
  23. data/app/helpers/chat/application_helper.rb +35 -24
  24. data/app/jobs/chat/application_job.rb +9 -1
  25. data/app/jobs/chat/message_relay_job.rb +5 -13
  26. data/app/jobs/chat/notification_relay_job.rb +10 -4
  27. data/app/jobs/chat/status_relay_job.rb +3 -3
  28. data/app/mailers/chat/application_mailer.rb +2 -0
  29. data/app/models/chat/application_record.rb +2 -0
  30. data/app/models/chat/conversation.rb +12 -0
  31. data/app/models/chat/dot_command.rb +2 -0
  32. data/app/models/chat/dot_command/gif.rb +2 -0
  33. data/app/models/chat/dot_command/shrug.rb +3 -1
  34. data/app/models/chat/dot_command/validator.rb +3 -1
  35. data/app/models/chat/message.rb +10 -2
  36. data/app/models/chat/session.rb +3 -3
  37. data/app/views/chat/_chat.html.haml +10 -6
  38. data/app/views/chat/conversations/_conversation.html.haml +23 -0
  39. data/app/views/chat/conversations/_index.html.haml +1 -6
  40. data/app/views/chat/conversations/_new.html.haml +2 -4
  41. data/app/views/chat/conversations/_show.html.haml +8 -7
  42. data/app/views/chat/conversations/create.js.erb +14 -10
  43. data/app/views/chat/conversations/show.js.erb +8 -5
  44. data/app/views/chat/messages/_message.haml +9 -12
  45. data/config/routes.rb +4 -2
  46. data/lib/chat.rb +5 -1
  47. data/lib/chat/engine.rb +2 -0
  48. data/lib/chat/user.rb +7 -12
  49. data/lib/chat/version.rb +3 -1
  50. data/lib/generators/chat/install/install_generator.rb +2 -0
  51. data/lib/generators/chat/install/templates/add_chat_to_users.rb +3 -1
  52. data/lib/generators/chat/install/templates/chat.rb +2 -0
  53. data/lib/generators/chat/install/templates/create_chat.rb +19 -7
  54. data/lib/tasks/chat_tasks.rake +1 -0
  55. metadata +15 -48
  56. data/app/models/chat/dot_command/leave.rb +0 -16
@@ -1,5 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
- class ApplicationJob < ActiveJob::Base
4
+ class ApplicationJob < ::ApplicationJob
3
5
  queue_as :default
6
+
7
+ private
8
+
9
+ def broadcast(channel, payload)
10
+ ActionCable.server.broadcast(channel, payload)
11
+ end
4
12
  end
5
13
  end
@@ -1,31 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
4
  class MessageRelayJob < ApplicationJob
3
5
  def perform(message_id)
4
6
  message = Chat::Message.find(message_id)
5
- ActionCable.server.broadcast(
7
+ broadcast(
6
8
  "chats::#{message.conversation_id}::messages", render_message(message)
7
9
  )
8
10
  end
9
11
 
10
12
  def render_message(message)
11
13
  {
12
- user: message.user_id,
13
- avatar: avatar(message.user)
14
+ user: message.user_id, avatar: renderer.chat_avatar(message.user).to_s
14
15
  }.merge(content(message))
15
16
  end
16
17
 
17
- def avatar(user)
18
- renderer.chat_avatar(user).to_s
19
- end
20
-
21
18
  def content(message)
22
19
  if message.image?
23
- {
24
- message: renderer.image_tag(
25
- message.image.url, class: "chat__message-image"
26
- ),
27
- image: true
28
- }
20
+ { message: renderer.image_tag(message.image.url), image: true }
29
21
  else
30
22
  { message: message.text, image: false }
31
23
  end
@@ -1,11 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
4
  class NotificationRelayJob < ApplicationJob
3
5
  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
- )
6
+ user_ids(message).each do |user_id|
7
+ broadcast("users::#{user_id}::chats", chat_id: message.conversation_id)
8
8
  end
9
9
  end
10
+
11
+ private
12
+
13
+ def user_ids(message)
14
+ message.conversation.users.where.not(id: message.user_id).ids
15
+ end
10
16
  end
11
17
  end
@@ -1,9 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
4
  class StatusRelayJob < ApplicationJob
3
5
  def perform(user)
4
- ActionCable.server.broadcast(
5
- "chat::status", user_id: user.id, status: user.chat_status
6
- )
6
+ broadcast("chat::status", user_id: user.id, status: user.chat_status)
7
7
  end
8
8
  end
9
9
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
4
  class ApplicationMailer < ActionMailer::Base
3
5
  default from: "from@example.com"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Chat::ApplicationRecord < ActiveRecord::Base
2
4
  self.abstract_class = true
3
5
  end
@@ -1,9 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Chat::Conversation < ApplicationRecord
2
4
  has_many :messages
3
5
  has_many :sessions, dependent: :destroy
4
6
  has_many :users, through: :sessions, class_name: "::User"
5
7
 
8
+ has_one :last_message, -> { order(created_at: :desc) },
9
+ class_name: "Chat::Message"
10
+
6
11
  validates :sessions, presence: {
7
12
  message: "At least one user must be selected"
8
13
  }
14
+ validate do
15
+ next unless Chat::Conversation.joins(:users).where.not(id: id).where(
16
+ users: { id: user_ids }
17
+ ).group(:id).having("COUNT(DISTINCT users.id) = ?", user_ids.count).exists?
18
+
19
+ errors.add(:conversation, "already taking place")
20
+ end
9
21
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
4
  class DotCommand
3
5
  attr_reader :text, :message
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
4
  class DotCommand
3
5
  class Gif
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
4
  class DotCommand
3
5
  class Shrug
@@ -20,7 +22,7 @@ module Chat
20
22
  private
21
23
 
22
24
  def shruggie
23
- "¯\\_(ツ)_/¯"
25
+ '¯\_(ツ)_/¯'
24
26
  end
25
27
  end
26
28
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
4
  class DotCommand
3
5
  class Validator
@@ -12,7 +14,7 @@ module Chat
12
14
  end
13
15
 
14
16
  def commands
15
- %w(.gif .shrug .leave)
17
+ %w(.gif .shrug)
16
18
  end
17
19
 
18
20
  def custom_commands
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Chat::Message < ApplicationRecord
2
4
  has_attached_file :image
3
5
  validates_attachment :image, content_type: {
@@ -6,13 +8,15 @@ class Chat::Message < ApplicationRecord
6
8
 
7
9
  belongs_to :user, class_name: "::User"
8
10
  belongs_to :conversation
11
+ belongs_to :session
9
12
 
10
13
  delegate :name, to: :user
14
+ before_save :remove_extra_new_line
11
15
  before_save :execute_dot_command
12
16
 
13
17
  after_create_commit do
14
- Chat::MessageRelayJob.perform_later(id)
15
- Chat::NotificationRelayJob.perform_later(self)
18
+ Chat::MessageRelayJob.send(Chat.perform_method.to_sym, id)
19
+ Chat::NotificationRelayJob.send(Chat.perform_method.to_sym, self)
16
20
  end
17
21
 
18
22
  private
@@ -26,4 +30,8 @@ class Chat::Message < ApplicationRecord
26
30
  def dot_command
27
31
  @dot_command ||= Chat::DotCommand.new(self, text)
28
32
  end
33
+
34
+ def remove_extra_new_line
35
+ self.text = text[0..-16]
36
+ end
29
37
  end
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Chat::Session < ApplicationRecord
2
4
  belongs_to :conversation
3
5
  belongs_to :user, class_name: "::User"
4
6
 
5
- has_many :messages, foreign_key: [:user_id, :conversation_id],
6
- primary_key: [:user_id, :conversation_id],
7
- dependent: :destroy
7
+ has_many :messages, dependent: :destroy
8
8
 
9
9
  validates :user_id, uniqueness: { scope: :conversation_id }
10
10
  end
@@ -1,14 +1,18 @@
1
- = launch_chat_fab(background, color)
1
+ = launch_chat_fab
2
2
 
3
3
  #chat
4
+ #chat__background
4
5
  .chat__header
5
- = material_icon.menu.css_class('chat__settings')
6
+ .chat__settings
7
+ %div
8
+ %div
6
9
  .chat__flex
7
10
  %h6 Chat
8
11
  .chat__flex
9
- = material_icon.close.css_class('chat__close')
10
-
11
- = render 'chat/conversations/new'
12
+ = material_icon.close.css_class("chat__close")
13
+ = render "chat/conversations/new"
12
14
  .chat__list
13
- = render 'chat/conversations/index'
15
+ = render "chat/conversations/index"
16
+ = link_to nil, class: "chat__start-new" do
17
+ = material_icon.add
14
18
  .current_chat
@@ -0,0 +1,23 @@
1
+ = link_to [chat, conversation], remote: true, id: "chat__show-#{conversation.id}" do
2
+ .avatar_container{ class: chat_avatar_count(conversation) }
3
+ - if chat_avatar_count(conversation) == "count_default"
4
+ = chat_avatars(conversation).sample.html_safe
5
+ .chat_count
6
+ %span
7
+ = conversation_user_count(conversation)
8
+ - else
9
+ = chat_avatars(conversation).join("").html_safe
10
+ .chat__glance
11
+ .chat__glance-name-date
12
+ .name
13
+ - if conversation.users.to_a.size > 2
14
+ Group Chat
15
+ - else
16
+ = conversation.users.where.not(id: current_user.id).first.first_name
17
+ .flex
18
+ .date
19
+ - if conversation.last_message.present?
20
+ = time_ago_in_words conversation.last_message&.created_at
21
+ ago
22
+ .chat__glance-last-message
23
+ = raw conversation.last_message&.text
@@ -1,11 +1,6 @@
1
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
2
+ = render chat_list
7
3
  - else
8
4
  .chat__none-available
9
5
  = material_icon.chat_bubble_outline
10
6
  %h5 No Chats
11
- = link_to 'Start a Chat', nil, class: 'chat__start-new'
@@ -1,9 +1,7 @@
1
1
  = form_for [chat, Chat::Conversation.new], remote: true do |f|
2
2
  - if chatable_users.present?
3
- .chat__user-check-boxes
4
- = chatable_user_check_boxes(f)
5
- .chat__flex
6
- = f.submit 'Start Chatting'
3
+ = chatable_user_check_boxes(f)
4
+ .chat__submit-container= f.submit "Start Chatting"
7
5
  - else
8
6
  .chat__none-available
9
7
  = material_icon.people
@@ -1,9 +1,10 @@
1
1
  .chat__transcript{ data: { channel: 'messages', current: { chat: @conversation.id, user: current_user.id }}}
2
- = render @conversation.messages
3
-
2
+ - @conversation.messages.each_with_index do |msg, i|
3
+ = render "chat/messages/message", message: msg, previous: (i > 0 ? @conversation.messages[i-1] : nil)
4
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
5
+ %div
6
+ = form_for [chat, @conversation, Chat::Message.new], remote: true do |f|
7
+ = material_icon.insert_photo.css_class("chat__insert-image")
8
+ = f.text_area :text, placeholder: 'Send message...', autocomplete: 'off'
9
+ %span.bar
10
+ = f.file_field :image
@@ -1,21 +1,25 @@
1
- <% if @conversation.valid? %>
1
+ <% if @conversation.errors.messages[:sessions].blank? %>
2
2
  $('#chat .chat__list').html("<%= j render 'chat/conversations/index' %>");
3
- $('#chat .chat__list').css('display', 'none');
4
3
  $('.new_conversation').replaceWith("<%= j render 'chat/conversations/new' %>");
5
4
  $('.current_chat').html("<%= j render 'chat/conversations/show' %>");
6
- $('.current_chat').css('display', 'flex');
7
- $('.chat__transcript').scrollTop($('.chat__transcript').prop('scrollHeight'));
5
+ $('.new_message textarea').emojioneArea();
8
6
  App.chat_message.followCurrentChat();
9
- <% elsif @conversation.errors.messages[:sessions].present? %>
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
+ $('.current_chat').toggleClass("slide_left");
13
+ $('.chat__list').addClass("slide_left");
14
+ $('.chat__start-new').addClass("chat__disappear");
15
+ $('.current_chat').scrollTop($('.current_chat').prop('scrollHeight'));
16
+ <% else %>
10
17
  error_msg =
11
18
  `<div class='chat__errors'>
12
19
  <%= j @conversation.errors.values.flatten.join(', ') %>
13
20
  </div>`;
14
21
  if($('.chat__errors').length == 0) {
15
- $('.chat__user-check-boxes').prepend(error_msg);
22
+ $('.new_conversation').scrollTop(0);
23
+ $('.new_conversation').prepend(error_msg);
16
24
  }
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
25
  <% end %>
@@ -1,12 +1,15 @@
1
+ $('.new_conversation').replaceWith("<%= j render 'chat/conversations/new' %>");
1
2
  $('.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');
3
+ $('.new_message textarea').emojioneArea();
6
4
  App.chat_message.followCurrentChat();
7
5
  $("#new_message").fileupload();
8
6
  if($("#chat__show-<%= @conversation.id %>").hasClass('chat__has_new_message')) {
9
7
  App.chat_notification.decrement();
10
8
  $("#chat__show-<%= @conversation.id %>").removeClass('chat__has_new_message')
11
9
  }
12
- $('.chat__transcript').scrollTop($('.chat__transcript').prop('scrollHeight'));
10
+ $('.current_chat').toggleClass("slide_left");
11
+ $('.chat__list').removeClass("slide_right");
12
+ $('.chat__list').addClass("slide_left");
13
+ $('.chat__start-new').addClass("chat__disappear");
14
+ $('.current_chat').scrollTop($('.current_chat').prop('scrollHeight'));
15
+ $(".new_conversation").removeClass("slide_right");
@@ -1,13 +1,10 @@
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)
1
+ .message-container{ class: message.user == current_user ? "right" : "left" }
2
+ - if message.user == previous&.user
3
+ .transcript_placeholder_avatar
4
+ - else
5
+ = chat_avatar(message.user)
6
+ .message{ class: ("image" if message.image?).to_s }
7
+ - if message.image?
8
+ = image_tag message.image.url
10
9
  - else
11
- = chat_avatar(message.user)
12
- .text.left= raw(message.text)
13
- .chat__flex
10
+ .content= raw(message.text)
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Chat::Engine.routes.draw do
2
- resources :conversations, only: [:show, :create] do
3
- resources :messages, only: [:create, :destroy]
4
+ resources :conversations, only: %i(show create) do
5
+ resources :messages, only: %i(create destroy)
4
6
  end
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "chat/engine"
2
4
  require "chat/version"
3
5
  require "chat/user"
@@ -6,7 +8,6 @@ require "paperclip"
6
8
  require "material_icons"
7
9
  require "coffee-rails"
8
10
  require "jquery-rails"
9
- require "composite_primary_keys"
10
11
  require "jquery-fileupload-rails"
11
12
  require "giphy"
12
13
  require "sidekiq"
@@ -21,6 +22,9 @@ module Chat
21
22
  # The field that contains the users avatar
22
23
  mattr_accessor :user_avatar
23
24
 
25
+ # Can be perform_now or perform_later. Called on jobs
26
+ mattr_accessor :perform_method
27
+
24
28
  def self.table_name_prefix
25
29
  "chat_"
26
30
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chat
2
4
  class Engine < ::Rails::Engine
3
5
  isolate_namespace Chat
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/concern"
2
4
 
3
5
  module Chat
@@ -27,25 +29,18 @@ module Chat
27
29
  update(chat_status: "offline")
28
30
  end
29
31
 
30
- def leave_conversation(conversation)
31
- session = sessions.where(conversation: conversation)
32
- session.destroy
32
+ def chat_avatar
33
+ send(Chat.user_avatar)
33
34
  end
34
35
 
35
- module ClassMethods
36
- def first_name
37
- "first_name"
38
- end
39
-
40
- def last_name
41
- "last_name"
42
- end
36
+ def chat_avatar?
37
+ send("#{Chat.user_avatar}?")
43
38
  end
44
39
 
45
40
  private
46
41
 
47
42
  def broadcast_status
48
- Chat::StatusRelayJob.perform_later(self)
43
+ Chat::StatusRelayJob.send(Chat.perform_method, self)
49
44
  end
50
45
  end
51
46
  end