livechat 0.5.2 → 0.5.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69c3cd167112ced4976fb8b759c45586255c4f9c94f8d89aedfc481efd138e09
4
- data.tar.gz: c1762cc48eb85e28d203c4ac272137f12ced552a1dc96555be8c8c9af14d7161
3
+ metadata.gz: 4f04a8c3aee0c2e7c9f4f3eff556bf450f4f81844750d8ebfb80d058ac49e180
4
+ data.tar.gz: 0f62d39bd16d1e02836fe8dbd8a777652e466db06406c60108a3ef75c3321161
5
5
  SHA512:
6
- metadata.gz: b84e5860f58d5cffc5da5eb0694f600597e5c7932805ab7a376a12997fbfb8e0ba3e380eecfe1462bdf7f167f46c79cdb46d52f4c5a63ebfe2c4458bb8165263
7
- data.tar.gz: 0dc49e26bf29a41797a96317a5c4cbaeca3c3ec5edbe6a0b253c451ad1ab08cbec4c60c1f66faf14de0916a118d6f4ede433b6c3db6ca42d5dfe434bc2e5ddab
6
+ metadata.gz: 2c3cbfefe5b627c5e919b9306ea72509026e107d28e5a69c5e83923286d6bf7e360ee5c8bfb5b0481883267dbf61630e1cfae40819d31760e0121463602e2cc6
7
+ data.tar.gz: b1c8c7782956dd04291838094131f904970011cfb92ed08473a359ad02046af18c88fc04b38d3c0e917361db5e7de3b79871fcb15c49a13d1d666d94a751a248
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.4
4
+
5
+ - Balanced the recording controls visually: the stop/finish square now uses
6
+ the same icon box as the cancel control, so the actions read with equal
7
+ weight in the composer.
8
+
9
+ ## 0.5.3
10
+
11
+ - Refined the audio recording controls. While recording, the composer now
12
+ shows a compact REC pill with a stable timer, an icon-only discard control,
13
+ and an icon-only stop control. Sending is disabled until recording is
14
+ stopped or cancelled.
15
+
3
16
  ## 0.5.2
4
17
 
5
18
  - Added audio messages. Visitors can record a voice note from the widget, send
@@ -111,6 +111,7 @@
111
111
  display: flex; align-items: center; justify-content: center; }
112
112
  .reply .attach:hover { background: var(--border); color: var(--text); }
113
113
  .reply .attach.recording-on { color: var(--danger); }
114
+ .reply.is-recording .attach { display: none; }
114
115
  .reply .chips { display: flex; flex-wrap: wrap; gap: 6px; }
115
116
  .reply .chip { display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
116
117
  padding: 4px 6px 4px 10px; border: 1px solid var(--border); border-radius: 999px;
@@ -119,14 +120,23 @@
119
120
  .reply .chip-x { padding: 0 2px; border: none; background: none; color: var(--muted);
120
121
  font-size: 16px; line-height: 1; cursor: pointer; }
121
122
  .reply .chip-x:hover { color: var(--text); }
122
- .reply .recording { display: flex; align-items: center; justify-content: space-between; gap: 8px;
123
- padding: 6px 4px 0; color: var(--muted); font-size: 12px; }
123
+ .reply .recording { display: inline-flex; align-items: center; gap: 8px; height: 32px;
124
+ padding: 0 4px 0 8px; border: 1px solid var(--border); border-radius: 999px;
125
+ background: var(--surface); color: var(--muted); font-size: 12px;
126
+ white-space: nowrap; }
124
127
  .reply .recording[hidden] { display: none; }
125
- .reply .recording-status { display: inline-flex; align-items: center; gap: 6px; }
126
- .reply .recording-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--danger); }
128
+ .reply .recording-status { display: inline-grid; grid-template-columns: 8px 24px 34px;
129
+ align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
130
+ .reply .recording-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--danger);
131
+ box-shadow: 0 0 0 3px rgba(220,38,38,.12); }
132
+ .reply .recording-rec { font-size: 10px; font-weight: 700; letter-spacing: .04em; color: var(--danger); }
127
133
  .reply .recording-actions { display: flex; align-items: center; gap: 4px; }
128
- .reply .recording-actions button { padding: 6px 8px; border-radius: 8px; font-size: 12px; line-height: 1; }
129
- .reply .recording-actions .stop-recording { border-color: var(--danger); color: var(--danger); }
134
+ .reply .recording-actions button { width: 24px; height: 24px; padding: 0; border: none;
135
+ border-radius: 50%; background: none; color: var(--muted);
136
+ display: flex; align-items: center; justify-content: center; }
137
+ .reply .recording-actions button:hover { background: var(--border); color: var(--text); }
138
+ .reply .recording-actions .stop-recording { background: var(--danger); color: #fff; }
139
+ .reply .recording-actions .stop-recording:hover { background: #b91c1c; color: #fff; }
130
140
  .reply .send { width: 32px; min-width: 32px; height: 32px; padding: 0; border: none; border-radius: 50%;
131
141
  background: var(--accent); color: var(--accent-text); cursor: pointer;
132
142
  display: flex; align-items: center; justify-content: center; }
@@ -134,25 +134,40 @@
134
134
  bar.hidden = true;
135
135
  var status = document.createElement("span");
136
136
  status.className = "recording-status";
137
+ status.setAttribute("aria-live", "polite");
137
138
  var dot = document.createElement("span");
138
139
  dot.className = "recording-dot";
140
+ var rec = document.createElement("span");
141
+ rec.className = "recording-rec";
142
+ rec.textContent = "REC";
139
143
  var time = document.createElement("span");
140
144
  status.appendChild(dot);
145
+ status.appendChild(rec);
141
146
  status.appendChild(time);
142
147
  bar.appendChild(status);
143
148
  var actions = document.createElement("div");
144
149
  actions.className = "recording-actions";
145
150
  var cancel = document.createElement("button");
146
151
  cancel.type = "button";
147
- cancel.textContent = labels.cancelRecording;
152
+ cancel.setAttribute("aria-label", labels.cancelRecording);
153
+ cancel.title = labels.cancelRecording;
154
+ cancel.innerHTML =
155
+ '<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">' +
156
+ '<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" ' +
157
+ 'd="M6 6l12 12M18 6 6 18"/></svg>';
148
158
  var stop = document.createElement("button");
149
159
  stop.type = "button";
150
160
  stop.className = "stop-recording";
151
- stop.textContent = labels.stopRecording;
161
+ stop.setAttribute("aria-label", labels.stopRecording);
162
+ stop.title = labels.stopRecording;
163
+ stop.innerHTML =
164
+ '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">' +
165
+ '<rect x="5" y="5" width="14" height="14" rx="2" fill="currentColor"/></svg>';
152
166
  actions.appendChild(cancel);
153
167
  actions.appendChild(stop);
154
168
  bar.appendChild(actions);
155
- form.appendChild(bar);
169
+ tools.appendChild(bar);
170
+ var sendButton = form.querySelector(".send");
156
171
 
157
172
  recordButton.addEventListener("click", start);
158
173
  stop.addEventListener("click", function () {
@@ -190,6 +205,8 @@
190
205
  recorder.start();
191
206
  startedAt = Date.now();
192
207
  recordButton.classList.add("recording-on");
208
+ form.classList.add("is-recording");
209
+ if (sendButton) sendButton.disabled = true;
193
210
  bar.hidden = false;
194
211
  tick();
195
212
  timer = setInterval(tick, 500);
@@ -209,6 +226,8 @@
209
226
  bar.hidden = true;
210
227
  recordButton.disabled = false;
211
228
  recordButton.classList.remove("recording-on");
229
+ form.classList.remove("is-recording");
230
+ if (sendButton) sendButton.disabled = false;
212
231
 
213
232
  if (!cancelled && chunks.length && typeof DataTransfer !== "undefined") {
214
233
  var kept = new DataTransfer();
@@ -222,7 +241,9 @@
222
241
  }
223
242
 
224
243
  function tick() {
225
- time.textContent = labels.recording.replace("%{time}", formatDuration(Date.now() - startedAt));
244
+ var elapsed = formatDuration(Date.now() - startedAt);
245
+ time.textContent = elapsed;
246
+ status.setAttribute("aria-label", labels.recording.replace("%{time}", elapsed));
226
247
  }
227
248
 
228
249
  function stopTracks() {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Livechat
4
- VERSION = '0.5.2'
4
+ VERSION = '0.5.4'
5
5
  end
@@ -349,6 +349,9 @@
349
349
  'M5 11a7 7 0 0 0 14 0M12 18v3M8.5 21h7"/></svg>';
350
350
  recordBtnEl.addEventListener("click", startRecording);
351
351
  tools.appendChild(recordBtnEl);
352
+
353
+ recordingBarEl = buildRecordingBar();
354
+ tools.appendChild(recordingBarEl);
352
355
  }
353
356
  toolbar.appendChild(tools);
354
357
 
@@ -367,10 +370,6 @@
367
370
  toolbar.appendChild(sendButton);
368
371
 
369
372
  formEl.appendChild(toolbar);
370
- if (config.attachments) {
371
- recordingBarEl = buildRecordingBar();
372
- formEl.appendChild(recordingBarEl);
373
- }
374
373
  formEl.addEventListener("submit", function (event) {
375
374
  event.preventDefault();
376
375
  send();
@@ -390,11 +389,16 @@
390
389
 
391
390
  var status = document.createElement("span");
392
391
  status.className = "lvc-recording-status";
392
+ status.setAttribute("aria-live", "polite");
393
393
  var dot = document.createElement("span");
394
394
  dot.className = "lvc-recording-dot";
395
+ var rec = document.createElement("span");
396
+ rec.className = "lvc-recording-rec";
397
+ rec.textContent = "REC";
395
398
  recordingTimeEl = document.createElement("span");
396
399
  recordingTimeEl.textContent = formatDuration(0);
397
400
  status.appendChild(dot);
401
+ status.appendChild(rec);
398
402
  status.appendChild(recordingTimeEl);
399
403
  bar.appendChild(status);
400
404
 
@@ -403,12 +407,21 @@
403
407
  var cancel = document.createElement("button");
404
408
  cancel.type = "button";
405
409
  cancel.className = "lvc-recording-cancel";
406
- cancel.textContent = config.labels.cancelRecording;
410
+ cancel.setAttribute("aria-label", config.labels.cancelRecording);
411
+ cancel.title = config.labels.cancelRecording;
412
+ cancel.innerHTML =
413
+ '<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">' +
414
+ '<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" ' +
415
+ 'd="M6 6l12 12M18 6 6 18"/></svg>';
407
416
  cancel.addEventListener("click", cancelRecording);
408
417
  var stop = document.createElement("button");
409
418
  stop.type = "button";
410
419
  stop.className = "lvc-recording-stop";
411
- stop.textContent = config.labels.stopRecording;
420
+ stop.setAttribute("aria-label", config.labels.stopRecording);
421
+ stop.title = config.labels.stopRecording;
422
+ stop.innerHTML =
423
+ '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">' +
424
+ '<rect x="5" y="5" width="14" height="14" rx="2" fill="currentColor"/></svg>';
412
425
  stop.addEventListener("click", stopRecording);
413
426
  actions.appendChild(cancel);
414
427
  actions.appendChild(stop);
@@ -505,11 +518,17 @@
505
518
  function showRecordingBar(show) {
506
519
  if (recordingBarEl) recordingBarEl.hidden = !show;
507
520
  if (recordBtnEl) recordBtnEl.classList.toggle("lvc-recording-on", show);
521
+ if (formEl) formEl.classList.toggle("lvc-is-recording", show);
522
+ var sendButton = formEl && formEl.querySelector("button[type=submit]");
523
+ if (sendButton) sendButton.disabled = show;
508
524
  }
509
525
 
510
526
  function tickRecording() {
511
527
  if (!recordingTimeEl) return;
512
- recordingTimeEl.textContent = config.labels.recording.replace("%{time}", formatDuration(Date.now() - recordingStartedAt));
528
+ var elapsed = formatDuration(Date.now() - recordingStartedAt);
529
+ recordingTimeEl.textContent = elapsed;
530
+ var status = recordingTimeEl.closest(".lvc-recording-status");
531
+ if (status) status.setAttribute("aria-label", config.labels.recording.replace("%{time}", elapsed));
513
532
  }
514
533
 
515
534
  function formatDuration(ms) {
@@ -1215,6 +1234,8 @@
1215
1234
  "display:flex;align-items:center;justify-content:center;cursor:pointer;border-radius:8px;padding:0}" +
1216
1235
  "#lvc-root .lvc-tool:hover{background:var(--lvc-border);color:var(--lvc-text)}" +
1217
1236
  "#lvc-root #lvc-record.lvc-recording-on{color:#dc2626}" +
1237
+ "#lvc-root #lvc-form.lvc-is-recording #lvc-attach,#lvc-root #lvc-form.lvc-is-recording #lvc-record" +
1238
+ "{display:none}" +
1218
1239
  "#lvc-send{border:none;border-radius:50%;background:var(--lvc-accent);color:var(--lvc-accent-text);" +
1219
1240
  "width:32px;height:32px;min-width:32px;display:flex;align-items:center;justify-content:center;" +
1220
1241
  "cursor:pointer;padding:0}" +
@@ -1230,16 +1251,22 @@
1230
1251
  "#lvc-root .lvc-chip-x{border:none;background:none;color:var(--lvc-muted);font-size:16px;" +
1231
1252
  "line-height:1;cursor:pointer;padding:0 2px}" +
1232
1253
  "#lvc-root .lvc-chip-x:hover{color:var(--lvc-text)}" +
1233
- "#lvc-recording{display:flex;align-items:center;justify-content:space-between;gap:8px;" +
1234
- "padding:6px 4px 0;color:var(--lvc-muted);font-size:12px}" +
1254
+ "#lvc-recording{display:inline-flex;align-items:center;gap:8px;height:32px;padding:0 4px 0 8px;" +
1255
+ "border:1px solid var(--lvc-border);border-radius:999px;background:var(--lvc-surface);" +
1256
+ "color:var(--lvc-muted);font-size:12px;white-space:nowrap}" +
1235
1257
  "#lvc-recording[hidden]{display:none}" +
1236
- "#lvc-root .lvc-recording-status{display:inline-flex;align-items:center;gap:6px}" +
1237
- "#lvc-root .lvc-recording-dot{width:8px;height:8px;border-radius:50%;background:#dc2626}" +
1258
+ "#lvc-root .lvc-recording-status{display:inline-grid;grid-template-columns:8px 24px 34px;" +
1259
+ "align-items:center;gap:6px;font-variant-numeric:tabular-nums}" +
1260
+ "#lvc-root .lvc-recording-dot{width:8px;height:8px;border-radius:50%;background:#dc2626;" +
1261
+ "box-shadow:0 0 0 3px rgba(220,38,38,.12)}" +
1262
+ "#lvc-root .lvc-recording-rec{font-size:10px;font-weight:700;letter-spacing:.04em;color:#dc2626}" +
1238
1263
  "#lvc-root .lvc-recording-actions{display:flex;align-items:center;gap:4px}" +
1239
- "#lvc-root .lvc-recording-actions button{border:1px solid var(--lvc-border);border-radius:8px;" +
1240
- "background:var(--lvc-surface);color:var(--lvc-text);font:inherit;font-size:12px;line-height:1;" +
1241
- "padding:6px 8px;cursor:pointer}" +
1242
- "#lvc-root .lvc-recording-stop{border-color:#dc2626;color:#dc2626}" +
1264
+ "#lvc-root .lvc-recording-actions button{width:24px;height:24px;border:none;border-radius:50%;" +
1265
+ "background:none;color:var(--lvc-muted);display:flex;align-items:center;justify-content:center;" +
1266
+ "padding:0;cursor:pointer}" +
1267
+ "#lvc-root .lvc-recording-actions button:hover{background:var(--lvc-border);color:var(--lvc-text)}" +
1268
+ "#lvc-root .lvc-recording-stop{background:#dc2626;color:#fff}" +
1269
+ "#lvc-root .lvc-recording-stop:hover{background:#b91c1c;color:#fff}" +
1243
1270
  // Attachments inside a message bubble: thumbnails and file links.
1244
1271
  "#lvc-root .lvc-atts{display:flex;flex-direction:column;gap:6px;margin-top:6px}" +
1245
1272
  "#lvc-root .lvc-att-img{display:block}" +
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: livechat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Shmarov