livechat 0.4.6 → 0.4.8
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 +16 -0
- data/app/views/layouts/livechat/application.html.erb +19 -14
- data/app/views/livechat/conversations/show.html.erb +18 -15
- data/lib/livechat/dashboard.js +29 -5
- data/lib/livechat/version.rb +1 -1
- data/lib/livechat/widget.js +3 -0
- 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: 7f0dd913b9f9ac88756a298d2ad57cf69c78d3f8bf31c08941171a6d13626a16
|
|
4
|
+
data.tar.gz: 1148803aaf1b0d465afd5526ee6196d0814f23f0a3c75d4f9a7cfbd3ba96f6ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8eff7b6fff9df34b825dd647d41eabc4e415572bd5dd282465f52de3e2ac4fa3ee648a6123b426dc67615f064d82e3354762b0cc9103ea9d724d251de5e769c8
|
|
7
|
+
data.tar.gz: 57905952ca50aaa60b2103577ab54f6ee433f0729329ee5ea999e7a4bade5100a9e585ce9702842857962209f3b63369db9dde0b03358a723d4a9bb960e48251
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.8
|
|
4
|
+
|
|
5
|
+
- A conversation with image attachments now opens scrolled to the bottom, not
|
|
6
|
+
mid-thread. Images load after the initial scroll and grow the thread, so both
|
|
7
|
+
the inbox and the widget now re-pin to the bottom as each image loads (and
|
|
8
|
+
the inbox keeps you pinned until you scroll up). Fixes threads with
|
|
9
|
+
screenshots opening somewhere in the middle.
|
|
10
|
+
|
|
11
|
+
## 0.4.7
|
|
12
|
+
|
|
13
|
+
- The inbox reply composer now matches the visitor widget: one bordered box
|
|
14
|
+
with the reply on top and a toolbar row (paperclip left, circular send
|
|
15
|
+
right) inside it, lit on focus — instead of a textarea flanked by loose
|
|
16
|
+
buttons. The reply field auto-grows as you type, and file chips ride inside
|
|
17
|
+
the box.
|
|
18
|
+
|
|
3
19
|
## 0.4.6
|
|
4
20
|
|
|
5
21
|
- Inbox conversation page no longer scrolls the whole page. It now fills the
|
|
@@ -93,28 +93,33 @@
|
|
|
93
93
|
.thread .msg.agent .att-file { background: rgba(255,255,255,.2); }
|
|
94
94
|
#new-messages { margin: 12px 0 0; }
|
|
95
95
|
#new-messages a { font-weight: 600; }
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
/* Unified composer box (matches the visitor widget): the reply on top, a
|
|
97
|
+
toolbar row below (tools left, send right), all inside one bordered box
|
|
98
|
+
that lights up on focus. */
|
|
99
|
+
.reply { display: flex; flex-direction: column; gap: 6px; margin: 10px 12px; padding: 8px 10px;
|
|
100
|
+
border: 1px solid var(--border); border-radius: 14px; background: var(--bg); }
|
|
101
|
+
.reply:focus-within { border-color: var(--accent); }
|
|
102
|
+
.reply textarea { -webkit-appearance: none; appearance: none; border: none; background: none;
|
|
103
|
+
resize: none; outline: none; font: inherit; color: var(--text);
|
|
104
|
+
padding: 4px 4px 0; min-height: 40px; max-height: 160px; overflow-y: auto; }
|
|
101
105
|
.reply input[type=file].reply-file { font-size: 12px; color: var(--muted); }
|
|
102
|
-
.reply
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
.reply-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
107
|
+
.reply-tools { display: flex; align-items: center; gap: 2px; }
|
|
108
|
+
.reply .attach { width: 32px; height: 32px; padding: 0; border: none; background: none;
|
|
109
|
+
color: var(--muted); border-radius: 8px; cursor: pointer;
|
|
110
|
+
display: flex; align-items: center; justify-content: center; }
|
|
111
|
+
.reply .attach:hover { background: var(--border); color: var(--text); }
|
|
106
112
|
.reply .chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
107
113
|
.reply .chip { display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
|
|
108
114
|
padding: 4px 6px 4px 10px; border: 1px solid var(--border); border-radius: 999px;
|
|
109
|
-
background: var(--
|
|
115
|
+
background: var(--surface); font-size: 12px; }
|
|
110
116
|
.reply .chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }
|
|
111
117
|
.reply .chip-x { padding: 0 2px; border: none; background: none; color: var(--muted);
|
|
112
118
|
font-size: 16px; line-height: 1; cursor: pointer; }
|
|
113
119
|
.reply .chip-x:hover { color: var(--text); }
|
|
114
|
-
.reply
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
border-radius: 10px; }
|
|
120
|
+
.reply .send { width: 32px; min-width: 32px; height: 32px; padding: 0; border: none; border-radius: 50%;
|
|
121
|
+
background: var(--accent); color: var(--accent-text); cursor: pointer;
|
|
122
|
+
display: flex; align-items: center; justify-content: center; }
|
|
118
123
|
/* Conversation page: fill the viewport and scroll the THREAD, not the
|
|
119
124
|
page — so a reply never jumps the page, and the composer stays put. */
|
|
120
125
|
.lvc-convo, .lvc-convo .container { height: 100vh; height: 100dvh; }
|
|
@@ -63,23 +63,26 @@
|
|
|
63
63
|
<% end %>
|
|
64
64
|
</div>
|
|
65
65
|
|
|
66
|
+
<%# One bordered composer box (matches the visitor widget): the reply on top,
|
|
67
|
+
a toolbar row below with tools on the left and send on the right. %>
|
|
66
68
|
<%= form_with url: conversation_messages_path(@conversation), method: :post,
|
|
67
69
|
multipart: true, class: 'reply' do |form| %>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
<%= form.text_area :body, rows: 2, autofocus: true, required: !Livechat.attachments_enabled?,
|
|
71
|
+
placeholder: t('livechat.dashboard.reply_placeholder', default: 'Write your reply…') %>
|
|
72
|
+
<%# dashboard.js hides this and swaps in a paperclip button (into .reply-tools)
|
|
73
|
+
plus file chips at the top of the box. Left plain as a no-JS fallback. %>
|
|
74
|
+
<% if Livechat.attachments_enabled? %>
|
|
75
|
+
<%= form.file_field :files, multiple: true, class: 'reply-file',
|
|
76
|
+
aria: { label: t('livechat.dashboard.attach', default: 'Attach files') } %>
|
|
77
|
+
<% end %>
|
|
78
|
+
<div class="reply-toolbar">
|
|
79
|
+
<div class="reply-tools"></div>
|
|
80
|
+
<button type="submit" class="send"
|
|
81
|
+
aria-label="<%= t('livechat.dashboard.send', default: 'Send') %>"
|
|
82
|
+
title="<%= t('livechat.dashboard.send', default: 'Send') %>">
|
|
83
|
+
<%# Paper airplane (Heroicons, MIT) — same icon as the widget. %>
|
|
84
|
+
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z"/></svg>
|
|
85
|
+
</button>
|
|
77
86
|
</div>
|
|
78
|
-
<button type="submit" class="primary send"
|
|
79
|
-
aria-label="<%= t('livechat.dashboard.send', default: 'Send') %>"
|
|
80
|
-
title="<%= t('livechat.dashboard.send', default: 'Send') %>">
|
|
81
|
-
<%# Paper airplane (Heroicons, MIT) — same icon as the widget. %>
|
|
82
|
-
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z"/></svg>
|
|
83
|
-
</button>
|
|
84
87
|
<% end %>
|
|
85
88
|
</div>
|
data/lib/livechat/dashboard.js
CHANGED
|
@@ -46,18 +46,19 @@
|
|
|
46
46
|
button.title = label;
|
|
47
47
|
// Paperclip (Heroicons, MIT) — same icon as the widget.
|
|
48
48
|
button.innerHTML =
|
|
49
|
-
'<svg viewBox="0 0 24 24" width="
|
|
49
|
+
'<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">' +
|
|
50
50
|
'<path fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" ' +
|
|
51
51
|
'stroke-linejoin="round" d="M18.4 8.6 9.9 17a3.5 3.5 0 0 1-5-5l8.5-8.4a2.3 2.3 0 0 1 ' +
|
|
52
52
|
'3.3 3.3l-8.5 8.4a1.1 1.1 0 0 1-1.6-1.6l7.8-7.8"/></svg>';
|
|
53
53
|
button.addEventListener("click", function () { input.click(); });
|
|
54
|
-
|
|
54
|
+
// The paperclip lives in the toolbar (left of send); chips sit at the top
|
|
55
|
+
// of the box — so it reads as one composer, like the visitor widget.
|
|
56
|
+
(form.querySelector(".reply-tools") || form).appendChild(button);
|
|
55
57
|
|
|
56
58
|
var chips = document.createElement("div");
|
|
57
59
|
chips.className = "chips";
|
|
58
60
|
chips.hidden = true;
|
|
59
|
-
|
|
60
|
-
compose.insertBefore(chips, compose.firstChild);
|
|
61
|
+
form.insertBefore(chips, form.firstChild);
|
|
61
62
|
|
|
62
63
|
input.addEventListener("change", render);
|
|
63
64
|
|
|
@@ -129,7 +130,21 @@
|
|
|
129
130
|
var thread = document.getElementById("thread");
|
|
130
131
|
if (!thread) return;
|
|
131
132
|
|
|
132
|
-
|
|
133
|
+
// Stick to the bottom until the agent scrolls up. Images (screenshots and
|
|
134
|
+
// attachments) finish loading after DOMContentLoaded and grow the thread,
|
|
135
|
+
// so a single scroll-to-bottom now would land mid-thread once they expand
|
|
136
|
+
// — re-pin as each image loads and once the page has fully loaded.
|
|
137
|
+
var stick = true;
|
|
138
|
+
var keepBottom = function () { if (stick) thread.scrollTop = thread.scrollHeight; };
|
|
139
|
+
thread.addEventListener("scroll", function () {
|
|
140
|
+
stick = thread.scrollHeight - thread.scrollTop - thread.clientHeight < 80;
|
|
141
|
+
});
|
|
142
|
+
keepBottom();
|
|
143
|
+
var images = thread.querySelectorAll("img");
|
|
144
|
+
for (var i = 0; i < images.length; i++) {
|
|
145
|
+
if (!images[i].complete) images[i].addEventListener("load", keepBottom);
|
|
146
|
+
}
|
|
147
|
+
window.addEventListener("load", keepBottom);
|
|
133
148
|
|
|
134
149
|
var replyBox = document.querySelector(".reply textarea");
|
|
135
150
|
var latest = parseInt(thread.getAttribute("data-latest"), 10) || 0;
|
|
@@ -143,6 +158,14 @@
|
|
|
143
158
|
replyBox.form.requestSubmit();
|
|
144
159
|
}
|
|
145
160
|
});
|
|
161
|
+
// Grow the box with the reply, up to the CSS max-height — matching the
|
|
162
|
+
// widget composer instead of a fixed two-row field.
|
|
163
|
+
var autogrow = function () {
|
|
164
|
+
replyBox.style.height = "auto";
|
|
165
|
+
replyBox.style.height = Math.min(replyBox.scrollHeight, 160) + "px";
|
|
166
|
+
};
|
|
167
|
+
replyBox.addEventListener("input", autogrow);
|
|
168
|
+
autogrow();
|
|
146
169
|
}
|
|
147
170
|
|
|
148
171
|
// New messages are appended in place — never a reload — so a half-written
|
|
@@ -195,6 +218,7 @@
|
|
|
195
218
|
img.src = att.url;
|
|
196
219
|
img.alt = att.name;
|
|
197
220
|
img.loading = "lazy";
|
|
221
|
+
img.addEventListener("load", keepBottom); // a live image grows the thread
|
|
198
222
|
link.appendChild(img);
|
|
199
223
|
} else {
|
|
200
224
|
link.className = "att-file";
|
data/lib/livechat/version.rb
CHANGED
data/lib/livechat/widget.js
CHANGED
|
@@ -681,6 +681,9 @@
|
|
|
681
681
|
img.src = att.url;
|
|
682
682
|
img.alt = att.name;
|
|
683
683
|
img.loading = "lazy";
|
|
684
|
+
// The image loads after its bubble is placed and grows the panel —
|
|
685
|
+
// re-pin to the bottom so a just-arrived image stays in view.
|
|
686
|
+
img.addEventListener("load", scrollToBottom);
|
|
684
687
|
link.appendChild(img);
|
|
685
688
|
} else {
|
|
686
689
|
link.className = "lvc-att-file";
|