livechat 0.3.4 → 0.3.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/app/controllers/livechat/conversations_controller.rb +29 -3
- data/app/views/livechat/conversations/show.html.erb +3 -5
- data/config/locales/livechat.ar.yml +0 -1
- data/config/locales/livechat.bg.yml +0 -1
- data/config/locales/livechat.bn.yml +0 -1
- data/config/locales/livechat.de.yml +0 -1
- data/config/locales/livechat.el.yml +0 -1
- data/config/locales/livechat.en.yml +0 -1
- data/config/locales/livechat.es.yml +0 -1
- data/config/locales/livechat.fr.yml +0 -1
- data/config/locales/livechat.hi.yml +0 -1
- data/config/locales/livechat.hr.yml +0 -1
- data/config/locales/livechat.id.yml +0 -1
- data/config/locales/livechat.it.yml +0 -1
- data/config/locales/livechat.ja.yml +0 -1
- data/config/locales/livechat.ko.yml +0 -1
- data/config/locales/livechat.lb.yml +0 -1
- data/config/locales/livechat.nl.yml +0 -1
- data/config/locales/livechat.pl.yml +0 -1
- data/config/locales/livechat.pt.yml +0 -1
- data/config/locales/livechat.ro.yml +0 -1
- data/config/locales/livechat.ru.yml +0 -1
- data/config/locales/livechat.th.yml +0 -1
- data/config/locales/livechat.tr.yml +0 -1
- data/config/locales/livechat.uk.yml +0 -1
- data/config/locales/livechat.ur.yml +0 -1
- data/config/locales/livechat.vi.yml +0 -1
- data/config/locales/livechat.zh-CN.yml +0 -1
- data/lib/livechat/dashboard.js +40 -8
- data/lib/livechat/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af07804137de8734d4851efdea56a8715af2ab199cdd99694f896abf42f908c9
|
|
4
|
+
data.tar.gz: 4272cb7b782303cd4ff21de16b16f849b9a32c3b4b3e91a057fe70fcc23aa86a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a0064196bc9737e6f008aa3f558d364ad2c29d09701774e629bc4bbb25a23074088597ec8e9a55c228b1dd20d7cbc8e6c5186fc35ac3e2a34445a710a793638
|
|
7
|
+
data.tar.gz: b41ce44b50df38aa11228ab5082f755ce4424d0dd3955a5d1e41af53829f1210492d13fa02e716d2b146e08bb54125f05b336396b3944c25953645c2e7a972b7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.5
|
|
4
|
+
|
|
5
|
+
- The inbox thread now appends new messages live instead of showing a
|
|
6
|
+
"New messages — refresh" link — no page reload, so an agent's half-written
|
|
7
|
+
reply is never lost, and scroll position is kept unless you're at the
|
|
8
|
+
bottom. Visitor messages are marked read as they stream in. Still polling
|
|
9
|
+
(no Action Cable), still draft-safe.
|
|
10
|
+
|
|
3
11
|
## 0.3.4
|
|
4
12
|
|
|
5
13
|
- Docs: show how to wire `current_user` with Rails 8's built-in authentication
|
|
@@ -46,10 +46,20 @@ module Livechat
|
|
|
46
46
|
@conversation.mark_read_for_agent!
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
# Polled by dashboard.js
|
|
49
|
+
# Polled by dashboard.js on the open thread: returns messages newer than
|
|
50
|
+
# ?after=<id> so they can be appended live — no reload, so an agent's
|
|
51
|
+
# half-written reply is never lost. Marks the visitor's messages read,
|
|
52
|
+
# since the agent is looking right at them.
|
|
50
53
|
def poll
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
messages = @conversation.messages.chronological
|
|
55
|
+
messages = messages.after_id(params[:after]) if params[:after].present?
|
|
56
|
+
messages = messages.to_a
|
|
57
|
+
@conversation.mark_read_for_agent! if messages.any?(&:visitor?)
|
|
58
|
+
|
|
59
|
+
render json: {
|
|
60
|
+
status: @conversation.status,
|
|
61
|
+
messages: messages.map { |message| thread_message_json(message) }
|
|
62
|
+
}
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
def resolve
|
|
@@ -68,6 +78,22 @@ module Livechat
|
|
|
68
78
|
@conversation = Conversation.find(params[:id])
|
|
69
79
|
end
|
|
70
80
|
|
|
81
|
+
# The fields dashboard.js needs to render a message bubble, matching the
|
|
82
|
+
# thread partial: system events carry a ready localized line; visitor and
|
|
83
|
+
# agent messages carry a display name (for the author header) and body.
|
|
84
|
+
def thread_message_json(message)
|
|
85
|
+
if message.system?
|
|
86
|
+
{ id: message.id, author: 'system',
|
|
87
|
+
text: t("livechat.events.#{message.event}", agent: message.agent_label,
|
|
88
|
+
default: "%{agent} #{message.event} the conversation"),
|
|
89
|
+
at: message.created_at.to_fs(:short) }
|
|
90
|
+
else
|
|
91
|
+
{ id: message.id, author: message.author_type,
|
|
92
|
+
name: message.agent? ? message.agent_label : @conversation.display_name,
|
|
93
|
+
body: message.body, at: message.created_at.to_fs(:short) }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
71
97
|
# Case-insensitive match on who the visitor is or anything anyone wrote.
|
|
72
98
|
# LOWER(...) LIKE is deliberately plain SQL — portable across SQLite,
|
|
73
99
|
# PostgreSQL and MySQL alike (feedback_engine's proven approach).
|
|
@@ -30,9 +30,11 @@
|
|
|
30
30
|
</p>
|
|
31
31
|
|
|
32
32
|
<div class="card">
|
|
33
|
+
<% last = @messages.last %>
|
|
33
34
|
<div class="thread" id="thread"
|
|
34
35
|
data-poll-url="<%= poll_conversation_path(@conversation) %>"
|
|
35
|
-
data-latest="<%=
|
|
36
|
+
data-latest="<%= last&.id.to_i %>"
|
|
37
|
+
data-last-author="<%= (last && !last.system?) ? "#{last.author_type}:#{last.agent_label}" : '' %>">
|
|
36
38
|
<% previous_author = nil %>
|
|
37
39
|
<% @messages.each do |message| %>
|
|
38
40
|
<% if message.system? %>
|
|
@@ -56,10 +58,6 @@
|
|
|
56
58
|
<% end %>
|
|
57
59
|
</div>
|
|
58
60
|
|
|
59
|
-
<p id="new-messages" class="system" hidden>
|
|
60
|
-
<a href=""><%= t('livechat.dashboard.new_messages', default: 'New messages — refresh') %></a>
|
|
61
|
-
</p>
|
|
62
|
-
|
|
63
61
|
<%= form_with url: conversation_messages_path(@conversation), method: :post, class: 'reply' do |form| %>
|
|
64
62
|
<%= form.text_area :body, rows: 2, autofocus: true, required: true,
|
|
65
63
|
placeholder: t('livechat.dashboard.reply_placeholder', default: 'Write your reply…') %>
|
data/lib/livechat/dashboard.js
CHANGED
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
thread.scrollTop = thread.scrollHeight;
|
|
57
57
|
|
|
58
58
|
var replyBox = document.querySelector(".reply textarea");
|
|
59
|
-
var banner = document.getElementById("new-messages");
|
|
60
59
|
var latest = parseInt(thread.getAttribute("data-latest"), 10) || 0;
|
|
60
|
+
var lastAuthorKey = thread.getAttribute("data-last-author") || null;
|
|
61
61
|
var url = thread.getAttribute("data-poll-url");
|
|
62
62
|
|
|
63
63
|
if (replyBox) {
|
|
@@ -69,17 +69,49 @@
|
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// New messages are appended in place — never a reload — so a half-written
|
|
73
|
+
// reply is never lost and scroll position is kept unless you're at the
|
|
74
|
+
// bottom. Author headers group exactly like the server-rendered thread.
|
|
75
|
+
function append(message) {
|
|
76
|
+
if (message.id <= latest) return;
|
|
77
|
+
latest = message.id;
|
|
78
|
+
|
|
79
|
+
if (message.author === "system") {
|
|
80
|
+
var line = document.createElement("p");
|
|
81
|
+
line.className = "system";
|
|
82
|
+
line.id = "message-" + message.id;
|
|
83
|
+
line.textContent = message.text + " · " + message.at;
|
|
84
|
+
thread.appendChild(line);
|
|
85
|
+
lastAuthorKey = null;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
var key = message.author + ":" + (message.name || "");
|
|
90
|
+
if (key !== lastAuthorKey) {
|
|
91
|
+
var who = document.createElement("p");
|
|
92
|
+
who.className = "who " + message.author;
|
|
93
|
+
who.textContent = message.name + " · " + message.at;
|
|
94
|
+
thread.appendChild(who);
|
|
95
|
+
lastAuthorKey = key;
|
|
96
|
+
}
|
|
97
|
+
var bubble = document.createElement("div");
|
|
98
|
+
bubble.className = "msg " + message.author;
|
|
99
|
+
bubble.id = "message-" + message.id;
|
|
100
|
+
bubble.textContent = message.body;
|
|
101
|
+
thread.appendChild(bubble);
|
|
102
|
+
}
|
|
103
|
+
|
|
72
104
|
setInterval(function () {
|
|
73
105
|
if (document.hidden) return;
|
|
74
|
-
|
|
106
|
+
// Only auto-scroll if the agent is already reading the latest — don't
|
|
107
|
+
// yank them down while they've scrolled up through history.
|
|
108
|
+
var atBottom = thread.scrollHeight - thread.scrollTop - thread.clientHeight < 60;
|
|
109
|
+
fetch(url + "?after=" + latest, { headers: { Accept: "application/json" }, credentials: "same-origin" })
|
|
75
110
|
.then(function (response) { return response.ok ? response.json() : null; })
|
|
76
111
|
.then(function (data) {
|
|
77
|
-
if (!data || data.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
} else if (banner) {
|
|
81
|
-
banner.hidden = false;
|
|
82
|
-
}
|
|
112
|
+
if (!data || !data.messages || !data.messages.length) return;
|
|
113
|
+
data.messages.forEach(append);
|
|
114
|
+
if (atBottom) thread.scrollTop = thread.scrollHeight;
|
|
83
115
|
})
|
|
84
116
|
.catch(function () { /* transient network error — next tick retries */ });
|
|
85
117
|
}, POLL_MS);
|
data/lib/livechat/version.rb
CHANGED