chat 0.2.0 → 0.9.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 (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