livechat 0.1.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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +11 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +160 -0
  5. data/Rakefile +11 -0
  6. data/app/controllers/livechat/application_controller.rb +76 -0
  7. data/app/controllers/livechat/conversations_controller.rb +57 -0
  8. data/app/controllers/livechat/messages_controller.rb +21 -0
  9. data/app/controllers/livechat/visitor_controller.rb +108 -0
  10. data/app/controllers/livechat/widgets_controller.rb +36 -0
  11. data/app/helpers/livechat/widget_helper.rb +37 -0
  12. data/app/mailers/livechat/mailer.rb +51 -0
  13. data/app/models/livechat/application_record.rb +7 -0
  14. data/app/models/livechat/conversation.rb +108 -0
  15. data/app/models/livechat/message.rb +53 -0
  16. data/app/views/layouts/livechat/application.html.erb +97 -0
  17. data/app/views/livechat/conversations/index.html.erb +62 -0
  18. data/app/views/livechat/conversations/show.html.erb +73 -0
  19. data/app/views/livechat/mailer/new_agent_reply.text.erb +7 -0
  20. data/app/views/livechat/mailer/new_visitor_message.text.erb +7 -0
  21. data/config/locales/livechat.ar.yml +44 -0
  22. data/config/locales/livechat.bg.yml +44 -0
  23. data/config/locales/livechat.bn.yml +44 -0
  24. data/config/locales/livechat.de.yml +44 -0
  25. data/config/locales/livechat.el.yml +44 -0
  26. data/config/locales/livechat.en.yml +44 -0
  27. data/config/locales/livechat.es.yml +44 -0
  28. data/config/locales/livechat.fr.yml +44 -0
  29. data/config/locales/livechat.hi.yml +44 -0
  30. data/config/locales/livechat.hr.yml +44 -0
  31. data/config/locales/livechat.id.yml +44 -0
  32. data/config/locales/livechat.it.yml +44 -0
  33. data/config/locales/livechat.ja.yml +44 -0
  34. data/config/locales/livechat.ko.yml +44 -0
  35. data/config/locales/livechat.lb.yml +44 -0
  36. data/config/locales/livechat.nl.yml +44 -0
  37. data/config/locales/livechat.pl.yml +44 -0
  38. data/config/locales/livechat.pt.yml +44 -0
  39. data/config/locales/livechat.ro.yml +44 -0
  40. data/config/locales/livechat.ru.yml +44 -0
  41. data/config/locales/livechat.th.yml +44 -0
  42. data/config/locales/livechat.tr.yml +44 -0
  43. data/config/locales/livechat.uk.yml +44 -0
  44. data/config/locales/livechat.ur.yml +44 -0
  45. data/config/locales/livechat.vi.yml +44 -0
  46. data/config/locales/livechat.zh-CN.yml +44 -0
  47. data/config/routes.rb +27 -0
  48. data/lib/generators/livechat/install/install_generator.rb +42 -0
  49. data/lib/generators/livechat/install/templates/create_livechat_tables.rb.tt +36 -0
  50. data/lib/generators/livechat/install/templates/initializer.rb +59 -0
  51. data/lib/livechat/configuration.rb +103 -0
  52. data/lib/livechat/dashboard.js +58 -0
  53. data/lib/livechat/engine.rb +13 -0
  54. data/lib/livechat/notifications.rb +41 -0
  55. data/lib/livechat/version.rb +5 -0
  56. data/lib/livechat/widget.js +563 -0
  57. data/lib/livechat/widget.rb +115 -0
  58. data/lib/livechat.rb +56 -0
  59. metadata +125 -0
@@ -0,0 +1,44 @@
1
+ ru:
2
+ livechat:
3
+ launcher: "Напишите нам"
4
+ greeting: "Здравствуйте! Чем можем помочь?"
5
+ reply_time: "Обычно мы отвечаем в течение нескольких часов."
6
+ placeholder: "Напишите сообщение…"
7
+ send: "Отправить"
8
+ close: "Закрыть"
9
+ you: "Вы"
10
+ team: "Поддержка"
11
+ email_prompt: "Получите копию нашего ответа по электронной почте:"
12
+ email_placeholder: "you@example.com"
13
+ email_save: "Сохранить"
14
+ email_saved: "Мы также ответим по электронной почте."
15
+ event_resolved: "Диалог завершён"
16
+ event_reopened: "Диалог возобновлён"
17
+ error_send: "Не удалось отправить. Пожалуйста, попробуйте ещё раз."
18
+ error_rate_limited: "Слишком много сообщений. Подождите немного и попробуйте ещё раз."
19
+ unread_aria: "непрочитанные сообщения"
20
+ statuses:
21
+ open: "Открыт"
22
+ resolved: "Завершён"
23
+ events:
24
+ resolved: "%{agent} завершил(а) диалог"
25
+ reopened: "%{agent} возобновил(а) диалог"
26
+ dashboard:
27
+ title: "Чат"
28
+ empty: "Пока здесь пусто. Когда посетитель напишет, диалог появится на этой странице."
29
+ visitor: "Посетитель"
30
+ last_message: "Последнее сообщение"
31
+ updated: "Обновлено"
32
+ back: "Все диалоги"
33
+ resolve: "Завершить"
34
+ reopen: "Возобновить"
35
+ reply_placeholder: "Напишите ответ…"
36
+ send: "Отправить"
37
+ new_messages: "Новые сообщения — обновите"
38
+ newer: "Новее"
39
+ older: "Старше"
40
+ mail:
41
+ visitor_subject: "%{name} написал(а) вам в %{app}"
42
+ reply_subject: "%{name} ответил(а) вам в %{app}"
43
+ answer: "Ответить"
44
+ continue: "Продолжить диалог"
@@ -0,0 +1,44 @@
1
+ th:
2
+ livechat:
3
+ launcher: "แชทกับเรา"
4
+ greeting: "สวัสดี! เราช่วยอะไรคุณได้บ้าง"
5
+ reply_time: "โดยปกติเราจะตอบกลับภายในไม่กี่ชั่วโมง"
6
+ placeholder: "เขียนข้อความ…"
7
+ send: "ส่ง"
8
+ close: "ปิด"
9
+ you: "คุณ"
10
+ team: "ฝ่ายสนับสนุน"
11
+ email_prompt: "รับสำเนาคำตอบของเราทางอีเมล:"
12
+ email_placeholder: "you@example.com"
13
+ email_save: "บันทึก"
14
+ email_saved: "เราจะตอบกลับทางอีเมลด้วย"
15
+ event_resolved: "การสนทนาได้รับการแก้ไขแล้ว"
16
+ event_reopened: "เปิดการสนทนาอีกครั้งแล้ว"
17
+ error_send: "ส่งไม่สำเร็จ โปรดลองอีกครั้ง"
18
+ error_rate_limited: "ข้อความมากเกินไป โปรดรอสักครู่แล้วลองอีกครั้ง"
19
+ unread_aria: "ข้อความที่ยังไม่ได้อ่าน"
20
+ statuses:
21
+ open: "เปิดอยู่"
22
+ resolved: "แก้ไขแล้ว"
23
+ events:
24
+ resolved: "%{agent} แก้ไขการสนทนาแล้ว"
25
+ reopened: "%{agent} เปิดการสนทนาอีกครั้ง"
26
+ dashboard:
27
+ title: "แชท"
28
+ empty: "ยังไม่มีอะไรที่นี่ เมื่อผู้เยี่ยมชมส่งข้อความ การสนทนาจะปรากฏบนหน้านี้"
29
+ visitor: "ผู้เยี่ยมชม"
30
+ last_message: "ข้อความล่าสุด"
31
+ updated: "อัปเดตเมื่อ"
32
+ back: "การสนทนาทั้งหมด"
33
+ resolve: "แก้ไขเสร็จสิ้น"
34
+ reopen: "เปิดอีกครั้ง"
35
+ reply_placeholder: "เขียนคำตอบของคุณ…"
36
+ send: "ส่ง"
37
+ new_messages: "มีข้อความใหม่ — รีเฟรช"
38
+ newer: "ใหม่กว่า"
39
+ older: "เก่ากว่า"
40
+ mail:
41
+ visitor_subject: "%{name} เขียนถึงคุณบน %{app}"
42
+ reply_subject: "%{name} ตอบกลับคุณบน %{app}"
43
+ answer: "ตอบกลับ"
44
+ continue: "สนทนาต่อ"
@@ -0,0 +1,44 @@
1
+ tr:
2
+ livechat:
3
+ launcher: "Bizimle sohbet edin"
4
+ greeting: "Merhaba! Size nasıl yardımcı olabiliriz?"
5
+ reply_time: "Genellikle birkaç saat içinde yanıtlıyoruz."
6
+ placeholder: "Bir mesaj yazın…"
7
+ send: "Gönder"
8
+ close: "Kapat"
9
+ you: "Siz"
10
+ team: "Destek"
11
+ email_prompt: "Yanıtımızın bir kopyasını e-postayla alın:"
12
+ email_placeholder: "siz@ornek.com"
13
+ email_save: "Kaydet"
14
+ email_saved: "Ayrıca e-postayla da yanıtlayacağız."
15
+ event_resolved: "Görüşme çözüldü"
16
+ event_reopened: "Görüşme yeniden açıldı"
17
+ error_send: "Gönderilemedi. Lütfen tekrar deneyin."
18
+ error_rate_limited: "Çok fazla mesaj. Lütfen biraz bekleyip tekrar deneyin."
19
+ unread_aria: "okunmamış mesaj"
20
+ statuses:
21
+ open: "Açık"
22
+ resolved: "Çözüldü"
23
+ events:
24
+ resolved: "%{agent} görüşmeyi çözdü"
25
+ reopened: "%{agent} görüşmeyi yeniden açtı"
26
+ dashboard:
27
+ title: "Sohbet"
28
+ empty: "Henüz burada bir şey yok. Bir ziyaretçi yazdığında görüşme bu sayfada görünür."
29
+ visitor: "Ziyaretçi"
30
+ last_message: "Son mesaj"
31
+ updated: "Güncellendi"
32
+ back: "Tüm görüşmeler"
33
+ resolve: "Çöz"
34
+ reopen: "Yeniden aç"
35
+ reply_placeholder: "Yanıtınızı yazın…"
36
+ send: "Gönder"
37
+ new_messages: "Yeni mesajlar — yenileyin"
38
+ newer: "Daha yeni"
39
+ older: "Daha eski"
40
+ mail:
41
+ visitor_subject: "%{name}, %{app} üzerinden size yazdı"
42
+ reply_subject: "%{name}, %{app} üzerinden size yanıt verdi"
43
+ answer: "Yanıtla"
44
+ continue: "Görüşmeye devam et"
@@ -0,0 +1,44 @@
1
+ uk:
2
+ livechat:
3
+ launcher: "Напишіть нам"
4
+ greeting: "Вітаємо! Чим можемо допомогти?"
5
+ reply_time: "Зазвичай ми відповідаємо протягом кількох годин."
6
+ placeholder: "Напишіть повідомлення…"
7
+ send: "Надіслати"
8
+ close: "Закрити"
9
+ you: "Ви"
10
+ team: "Підтримка"
11
+ email_prompt: "Отримайте копію нашої відповіді електронною поштою:"
12
+ email_placeholder: "you@example.com"
13
+ email_save: "Зберегти"
14
+ email_saved: "Ми також відповімо електронною поштою."
15
+ event_resolved: "Розмову завершено"
16
+ event_reopened: "Розмову відновлено"
17
+ error_send: "Не вдалося надіслати. Будь ласка, спробуйте ще раз."
18
+ error_rate_limited: "Забагато повідомлень. Зачекайте трохи та спробуйте ще раз."
19
+ unread_aria: "непрочитані повідомлення"
20
+ statuses:
21
+ open: "Відкрита"
22
+ resolved: "Завершена"
23
+ events:
24
+ resolved: "%{agent} завершив(ла) розмову"
25
+ reopened: "%{agent} відновив(ла) розмову"
26
+ dashboard:
27
+ title: "Чат"
28
+ empty: "Поки що тут порожньо. Коли відвідувач напише, розмова з'явиться на цій сторінці."
29
+ visitor: "Відвідувач"
30
+ last_message: "Останнє повідомлення"
31
+ updated: "Оновлено"
32
+ back: "Усі розмови"
33
+ resolve: "Завершити"
34
+ reopen: "Відновити"
35
+ reply_placeholder: "Напишіть відповідь…"
36
+ send: "Надіслати"
37
+ new_messages: "Нові повідомлення — оновіть"
38
+ newer: "Новіші"
39
+ older: "Старіші"
40
+ mail:
41
+ visitor_subject: "%{name} написав(ла) вам у %{app}"
42
+ reply_subject: "%{name} відповів(ла) вам у %{app}"
43
+ answer: "Відповісти"
44
+ continue: "Продовжити розмову"
@@ -0,0 +1,44 @@
1
+ ur:
2
+ livechat:
3
+ launcher: "ہم سے چیٹ کریں"
4
+ greeting: "ہیلو! ہم آپ کی کیا مدد کر سکتے ہیں؟"
5
+ reply_time: "ہم عام طور پر چند گھنٹوں میں جواب دیتے ہیں۔"
6
+ placeholder: "پیغام لکھیں…"
7
+ send: "بھیجیں"
8
+ close: "بند کریں"
9
+ you: "آپ"
10
+ team: "سپورٹ"
11
+ email_prompt: "ہمارے جواب کی ایک نقل ای میل پر حاصل کریں:"
12
+ email_placeholder: "you@example.com"
13
+ email_save: "محفوظ کریں"
14
+ email_saved: "ہم ای میل پر بھی جواب دیں گے۔"
15
+ event_resolved: "گفتگو حل ہو گئی"
16
+ event_reopened: "گفتگو دوبارہ کھول دی گئی"
17
+ error_send: "بھیجا نہیں جا سکا۔ براہ کرم دوبارہ کوشش کریں۔"
18
+ error_rate_limited: "بہت زیادہ پیغامات۔ براہ کرم تھوڑا انتظار کریں اور دوبارہ کوشش کریں۔"
19
+ unread_aria: "غیر پڑھے گئے پیغامات"
20
+ statuses:
21
+ open: "کھلی"
22
+ resolved: "حل شدہ"
23
+ events:
24
+ resolved: "%{agent} نے گفتگو حل کر دی"
25
+ reopened: "%{agent} نے گفتگو دوبارہ کھول دی"
26
+ dashboard:
27
+ title: "چیٹ"
28
+ empty: "یہاں ابھی کچھ نہیں ہے۔ جب کوئی وزیٹر لکھے گا تو گفتگو اس صفحے پر نظر آئے گی۔"
29
+ visitor: "وزیٹر"
30
+ last_message: "آخری پیغام"
31
+ updated: "اپ ڈیٹ"
32
+ back: "تمام گفتگوئیں"
33
+ resolve: "حل کریں"
34
+ reopen: "دوبارہ کھولیں"
35
+ reply_placeholder: "اپنا جواب لکھیں…"
36
+ send: "بھیجیں"
37
+ new_messages: "نئے پیغامات — ریفریش کریں"
38
+ newer: "نئے"
39
+ older: "پرانے"
40
+ mail:
41
+ visitor_subject: "%{name} نے آپ کو %{app} پر لکھا"
42
+ reply_subject: "%{name} نے آپ کو %{app} پر جواب دیا"
43
+ answer: "جواب دیں"
44
+ continue: "گفتگو جاری رکھیں"
@@ -0,0 +1,44 @@
1
+ vi:
2
+ livechat:
3
+ launcher: "Trò chuyện với chúng tôi"
4
+ greeting: "Xin chào! Chúng tôi có thể giúp gì cho bạn?"
5
+ reply_time: "Chúng tôi thường trả lời trong vòng vài giờ."
6
+ placeholder: "Viết tin nhắn…"
7
+ send: "Gửi"
8
+ close: "Đóng"
9
+ you: "Bạn"
10
+ team: "Hỗ trợ"
11
+ email_prompt: "Nhận bản sao câu trả lời của chúng tôi qua email:"
12
+ email_placeholder: "you@example.com"
13
+ email_save: "Lưu"
14
+ email_saved: "Chúng tôi cũng sẽ trả lời qua email."
15
+ event_resolved: "Cuộc trò chuyện đã được giải quyết"
16
+ event_reopened: "Cuộc trò chuyện đã được mở lại"
17
+ error_send: "Không thể gửi. Vui lòng thử lại."
18
+ error_rate_limited: "Quá nhiều tin nhắn. Vui lòng đợi một lát rồi thử lại."
19
+ unread_aria: "tin nhắn chưa đọc"
20
+ statuses:
21
+ open: "Đang mở"
22
+ resolved: "Đã giải quyết"
23
+ events:
24
+ resolved: "%{agent} đã giải quyết cuộc trò chuyện"
25
+ reopened: "%{agent} đã mở lại cuộc trò chuyện"
26
+ dashboard:
27
+ title: "Trò chuyện"
28
+ empty: "Chưa có gì ở đây. Khi khách truy cập nhắn tin, cuộc trò chuyện sẽ xuất hiện trên trang này."
29
+ visitor: "Khách truy cập"
30
+ last_message: "Tin nhắn cuối"
31
+ updated: "Đã cập nhật"
32
+ back: "Tất cả cuộc trò chuyện"
33
+ resolve: "Giải quyết"
34
+ reopen: "Mở lại"
35
+ reply_placeholder: "Viết câu trả lời của bạn…"
36
+ send: "Gửi"
37
+ new_messages: "Tin nhắn mới — làm mới"
38
+ newer: "Mới hơn"
39
+ older: "Cũ hơn"
40
+ mail:
41
+ visitor_subject: "%{name} đã nhắn tin cho bạn trên %{app}"
42
+ reply_subject: "%{name} đã trả lời bạn trên %{app}"
43
+ answer: "Trả lời"
44
+ continue: "Tiếp tục cuộc trò chuyện"
@@ -0,0 +1,44 @@
1
+ "zh-CN":
2
+ livechat:
3
+ launcher: "与我们聊天"
4
+ greeting: "您好!有什么可以帮您?"
5
+ reply_time: "我们通常会在几小时内回复。"
6
+ placeholder: "输入消息…"
7
+ send: "发送"
8
+ close: "关闭"
9
+ you: "您"
10
+ team: "客服"
11
+ email_prompt: "通过邮箱接收我们回复的副本:"
12
+ email_placeholder: "you@example.com"
13
+ email_save: "保存"
14
+ email_saved: "我们也会通过邮件回复您。"
15
+ event_resolved: "对话已解决"
16
+ event_reopened: "对话已重新打开"
17
+ error_send: "发送失败,请重试。"
18
+ error_rate_limited: "消息过多,请稍等片刻再重试。"
19
+ unread_aria: "条未读消息"
20
+ statuses:
21
+ open: "进行中"
22
+ resolved: "已解决"
23
+ events:
24
+ resolved: "%{agent} 已解决该对话"
25
+ reopened: "%{agent} 重新打开了该对话"
26
+ dashboard:
27
+ title: "聊天"
28
+ empty: "这里还没有内容。当访客发来消息时,对话会显示在此页面。"
29
+ visitor: "访客"
30
+ last_message: "最后一条消息"
31
+ updated: "更新时间"
32
+ back: "所有对话"
33
+ resolve: "解决"
34
+ reopen: "重新打开"
35
+ reply_placeholder: "输入您的回复…"
36
+ send: "发送"
37
+ new_messages: "有新消息 — 请刷新"
38
+ newer: "较新"
39
+ older: "较旧"
40
+ mail:
41
+ visitor_subject: "%{name} 在 %{app} 上给您留言"
42
+ reply_subject: "%{name} 在 %{app} 上回复了您"
43
+ answer: "回复"
44
+ continue: "继续对话"
data/config/routes.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ Livechat::Engine.routes.draw do
4
+ get 'widget.js', to: 'widgets#show', as: :widget
5
+ get 'dashboard.js', to: 'widgets#dashboard', as: :dashboard_script
6
+
7
+ # The widget's API. Everything is scoped to the requesting visitor —
8
+ # signed-in id or guest cookie — never to an enumerable conversation id.
9
+ scope :widget, as: :widget do
10
+ get 'conversation', to: 'visitor#show'
11
+ post 'messages', to: 'visitor#create'
12
+ post 'email', to: 'visitor#email'
13
+ post 'read', to: 'visitor#read'
14
+ end
15
+
16
+ # The inbox. Flat, human URLs: the mount path IS the conversation list.
17
+ resources :conversations, path: '', only: %i[index show], constraints: { id: /\d+/ } do
18
+ member do
19
+ get :poll
20
+ post :resolve
21
+ post :reopen
22
+ end
23
+ resources :messages, only: :create
24
+ end
25
+
26
+ root to: 'conversations#index'
27
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
5
+
6
+ module Livechat
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ source_root File.expand_path('templates', __dir__)
12
+
13
+ desc 'Installs livechat: config initializer, migration, and engine mount.'
14
+
15
+ def create_initializer
16
+ copy_file 'initializer.rb', 'config/initializers/livechat.rb'
17
+ end
18
+
19
+ def create_migration_file
20
+ migration_template 'create_livechat_tables.rb.tt',
21
+ 'db/migrate/create_livechat_tables.rb'
22
+ end
23
+
24
+ def mount_engine
25
+ route %(mount Livechat::Engine => "/livechat")
26
+ end
27
+
28
+ def post_install
29
+ say "\nlivechat installed. Run `rails db:migrate`, then add", :green
30
+ say '`<%= livechat_tag %>` before </body> in your layout.'
31
+ say 'Answer visitors at /livechat (development only until you set config.authorize_agent).'
32
+ say "Set config.mailer_from + config.agent_emails to hear about new messages by email.\n"
33
+ end
34
+
35
+ private
36
+
37
+ def migration_version
38
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateLivechatTables < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :livechat_conversations do |t|
6
+ t.string :visitor_token
7
+ t.string :visitor_id
8
+ t.string :visitor_label
9
+ t.string :visitor_email
10
+ t.string :status, null: false, default: 'open'
11
+ t.string :last_message_preview
12
+ t.datetime :last_activity_at
13
+ t.string :page_url
14
+ t.string :locale
15
+
16
+ t.timestamps
17
+ end
18
+ add_index :livechat_conversations, :visitor_token
19
+ add_index :livechat_conversations, :visitor_id
20
+ add_index :livechat_conversations, :status
21
+ add_index :livechat_conversations, :last_activity_at
22
+
23
+ create_table :livechat_messages do |t|
24
+ t.references :conversation, null: false
25
+ t.string :author_type, null: false
26
+ t.string :agent_id
27
+ t.string :agent_label
28
+ t.text :body
29
+ t.string :event
30
+ t.datetime :read_at
31
+
32
+ t.timestamps
33
+ end
34
+ add_index :livechat_messages, %i[conversation_id id]
35
+ end
36
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ Livechat.configure do |config|
4
+ # Shown in the widget header and in notification emails. Defaults to your
5
+ # Rails application name.
6
+ # config.app_name = "My App"
7
+
8
+ # Who sees the widget and can write. Return false to hide and reject for
9
+ # this request. Defaults to everyone.
10
+ # config.enabled = ->(request) { true }
11
+
12
+ # Who can answer at the mount path. Defaults to development only — override
13
+ # before deploying.
14
+ # config.authorize_agent = ->(request) { request.env["warden"]&.user&.admin? }
15
+
16
+ # Resolve the current user (optional). Return an object responding to #id,
17
+ # or nil. Signed-in visitors keep one conversation across devices; guests
18
+ # are tracked with a cookie. The same user is the agent when replying.
19
+ # config.current_user = ->(request) { request.env["warden"]&.user }
20
+
21
+ # How visitors appear in the inbox.
22
+ # config.visitor_label = ->(user) { user.name.presence || user.email }
23
+
24
+ # How agents are attributed on their replies.
25
+ # config.agent_label = ->(user) { user.name.presence || user.email }
26
+
27
+ # What visitors see as the sender. Default: the agent_label unchanged.
28
+ # Return a first name, or a constant to keep the team anonymous.
29
+ # config.agent_display_name = ->(label) { label.split.first }
30
+
31
+ # Widget copy. nil = localized defaults.
32
+ # config.greeting = "Hi! How can we help?"
33
+ # config.reply_time_text = "We usually reply within a few hours."
34
+ # config.launcher_label = "Chat with us"
35
+
36
+ # Brand color (hex) for the launcher, header, bubbles and send button.
37
+ # Text flips black/white automatically for contrast. nil = built-in blue.
38
+ # config.accent_color = "#7c3aed"
39
+
40
+ # The floating bubble. Set false and open the widget from your own
41
+ # elements carrying data-livechat-open, or window.Livechat.open().
42
+ # config.show_launcher = true
43
+
44
+ # Email the team when a visitor writes and nobody has read it — one email
45
+ # per unread stretch, not one per message. Both settings are required.
46
+ # config.mailer_from = "chat@example.com"
47
+ # config.agent_emails = ["support@example.com"]
48
+ # config.agent_emails = -> { User.where(admin: true).pluck(:email) }
49
+
50
+ # Called with each saved Livechat::Message — notify Slack, push…
51
+ # config.on_visitor_message = ->(message) {}
52
+ # config.on_agent_message = ->(message) {}
53
+
54
+ # Per-IP throttle for the widget endpoints (Rails 7.2+; ignored on 7.1).
55
+ # config.rate_limit = { to: 30, within: 1.minute }
56
+
57
+ # Keep in sync with the `mount` in config/routes.rb.
58
+ # config.mount_path = "/livechat"
59
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Livechat
4
+ # Host-tunable settings. Everything has a safe default, so a fresh install
5
+ # works with zero configuration; the hooks below let an app decide who can
6
+ # chat, who answers, and how everyone is named.
7
+ class Configuration
8
+ # Shown in the widget header and in notification emails. nil resolves to
9
+ # the Rails application name.
10
+ attr_accessor :app_name
11
+
12
+ # Per-request gate for the widget and its endpoints. Return false to hide
13
+ # the widget and reject writes for this request.
14
+ attr_accessor :enabled
15
+
16
+ # Per-request gate for the inbox. Defaults to development only — override
17
+ # it before deploying, e.g. with an admin check. Independent of `enabled`,
18
+ # so your team can answer from production even where the widget is off.
19
+ attr_accessor :authorize_agent
20
+
21
+ # Resolve the current user (optional). Return an object responding to
22
+ # #id, or nil. Receives the request. Signed-in users keep one conversation
23
+ # across devices; guests are tracked with a cookie.
24
+ attr_accessor :current_user
25
+
26
+ # Turn a resolved user into the visitor name shown in the inbox.
27
+ attr_accessor :visitor_label
28
+
29
+ # Turn a resolved user into the attribution stored on an agent's message.
30
+ # Every reply carries this, so a thread with several teammates stays
31
+ # legible.
32
+ attr_accessor :agent_label
33
+
34
+ # What visitors see as the sender of an agent message. Receives the
35
+ # stored agent_label; return it unchanged (default), a first name, or a
36
+ # constant like "Support team" to keep agents anonymous.
37
+ attr_accessor :agent_display_name
38
+
39
+ # Widget copy. nil uses the localized defaults. reply_time_text is the
40
+ # honest line under the greeting — "We usually reply within a few hours."
41
+ attr_accessor :greeting, :reply_time_text, :launcher_label
42
+
43
+ # Brand color for the widget (launcher, header, visitor bubbles, send
44
+ # button) as a hex value, e.g. "#7c3aed". The widget picks black or white
45
+ # text automatically for contrast. nil keeps the built-in blue.
46
+ attr_accessor :accent_color
47
+
48
+ # The floating launcher bubble. Set false to open the widget only from
49
+ # your own elements carrying `data-livechat-open`.
50
+ attr_accessor :show_launcher
51
+
52
+ # Email addresses to notify when a visitor writes and nobody has read it —
53
+ # an array, or a callable returning one. nil disables the built-in email.
54
+ attr_accessor :agent_emails
55
+
56
+ # From-address for the built-in notification emails. Required for any
57
+ # email to be sent.
58
+ attr_accessor :mailer_from
59
+
60
+ # Called with each saved Livechat::Message — wire up Slack, Noticed,
61
+ # push… Runs inline after save; keep it fast or hand off to a job.
62
+ attr_accessor :on_visitor_message, :on_agent_message
63
+
64
+ # Per-IP throttle for the public endpoints, as keyword arguments for
65
+ # Rails' rate limiter (Rails 7.2+; ignored on 7.1). nil disables it.
66
+ attr_accessor :rate_limit
67
+
68
+ # Where the engine is mounted. The widget calls paths under it, so keep
69
+ # this in sync with the `mount` line in your routes.
70
+ attr_accessor :mount_path
71
+
72
+ def initialize
73
+ @app_name = nil
74
+ @enabled = ->(_request) { true }
75
+ @authorize_agent = ->(_request) { Rails.env.development? }
76
+ @current_user = ->(_request) {}
77
+ @visitor_label = ->(user) { user.try(:name).presence || user.try(:email).presence || user.to_s }
78
+ @agent_label = ->(user) { user.try(:name).presence || user.try(:email).presence || user.to_s }
79
+ @agent_display_name = ->(label) { label }
80
+ @greeting = nil
81
+ @reply_time_text = nil
82
+ @launcher_label = nil
83
+ @accent_color = nil
84
+ @show_launcher = true
85
+ @agent_emails = nil
86
+ @mailer_from = nil
87
+ @on_visitor_message = ->(_message) {}
88
+ @on_agent_message = ->(_message) {}
89
+ @rate_limit = { to: 30, within: 60 }
90
+ @mount_path = '/livechat'
91
+ end
92
+
93
+ def widget_endpoint = "#{mount_path.chomp('/')}/widget"
94
+
95
+ def agent_email_list
96
+ list = agent_emails
97
+ list = list.call if list.respond_to?(:call)
98
+ Array(list).compact_blank
99
+ end
100
+
101
+ def emails_enabled? = mailer_from.present?
102
+ end
103
+ end
@@ -0,0 +1,58 @@
1
+ /*
2
+ * livechat dashboard — the little JavaScript the inbox needs, served as a
3
+ * same-origin script so it works under strict CSPs (no inline handlers).
4
+ *
5
+ * On a conversation page it polls for new messages every few seconds. If the
6
+ * reply box is empty the page just reloads to show them; if the agent is
7
+ * mid-sentence it shows a "New messages" link instead — a reload must never
8
+ * eat a half-written reply. Cmd/Ctrl+Enter sends.
9
+ */
10
+ (function () {
11
+ "use strict";
12
+
13
+ var POLL_MS = 5000;
14
+
15
+ function ready(fn) {
16
+ if (document.readyState === "loading") {
17
+ document.addEventListener("DOMContentLoaded", fn);
18
+ } else {
19
+ fn();
20
+ }
21
+ }
22
+
23
+ ready(function () {
24
+ var thread = document.getElementById("thread");
25
+ if (!thread) return;
26
+
27
+ thread.scrollTop = thread.scrollHeight;
28
+
29
+ var replyBox = document.querySelector(".reply textarea");
30
+ var banner = document.getElementById("new-messages");
31
+ var latest = parseInt(thread.getAttribute("data-latest"), 10) || 0;
32
+ var url = thread.getAttribute("data-poll-url");
33
+
34
+ if (replyBox) {
35
+ replyBox.addEventListener("keydown", function (event) {
36
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
37
+ event.preventDefault();
38
+ replyBox.form.requestSubmit();
39
+ }
40
+ });
41
+ }
42
+
43
+ setInterval(function () {
44
+ if (document.hidden) return;
45
+ fetch(url, { headers: { Accept: "application/json" }, credentials: "same-origin" })
46
+ .then(function (response) { return response.ok ? response.json() : null; })
47
+ .then(function (data) {
48
+ if (!data || data.latest <= latest) return;
49
+ if (!replyBox || replyBox.value.trim() === "") {
50
+ window.location.reload();
51
+ } else if (banner) {
52
+ banner.hidden = false;
53
+ }
54
+ })
55
+ .catch(function () { /* transient network error — next tick retries */ });
56
+ }, POLL_MS);
57
+ });
58
+ })();
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Livechat
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Livechat
6
+
7
+ initializer 'livechat.helpers' do
8
+ ActiveSupport.on_load(:action_view) do
9
+ include Livechat::WidgetHelper
10
+ end
11
+ end
12
+ end
13
+ end