livechat 0.9.0 → 0.10.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.
- checksums.yaml +4 -4
- data/AGENTS.md +1 -1
- data/CHANGELOG.md +26 -0
- data/app/views/livechat/conversations/_thread_panel.html.erb +30 -24
- data/lib/livechat/dashboard.css +22 -16
- 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: 63ba0ec549bbfac094a6a507ac4e3525c01b9b8926dbbf4e7822e7db05c2472d
|
|
4
|
+
data.tar.gz: 7afeeacda8689b6335c87a06a9fe0fecb50388379fc32d7255ab0fec16afe04e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26d8a59095979df9546eaa12aee10d828b50677a2b32e199c2b25a9820cfa7d3e8a4b24f9ec53e4f19c286f49948ab8bb4ea1756bfea3818d849240b8221f080
|
|
7
|
+
data.tar.gz: e287d3e63e218974245e0c5e495603aee5ae4ddd3b3b9c91af2ebc6ca103aa2d0ffdea731f39003ad79e10c52101bf9f187afba64cb4ded94e07c17ab6bb8cd0
|
data/AGENTS.md
CHANGED
|
@@ -176,6 +176,6 @@ Conventions this codebase holds to — follow them rather than the first thing t
|
|
|
176
176
|
- **The widget is plain ES5-style JS served by the engine**, no build step, no framework, config read from a JSON block so a Turbo visit re-reads the current page's settings.
|
|
177
177
|
- **Visitor scoping is never by conversation id.** The widget's endpoints resolve the thread from the signed-in id or the guest cookie, so no id in a request can address someone else's conversation. Keep it that way.
|
|
178
178
|
- **The dummy app pins `config.active_job.queue_adapter = :test`.** Do not remove it or let it drift back to the `:async` default. Attaching a file enqueues Active Storage's analysis job, and `:async` runs it on a background thread that checks out its own connection — writes no test transaction covers, landing in the middle of whatever runs next. That is a suite that fails order-dependently in a test which never created a row, and it is miserable to trace back.
|
|
179
|
-
- **`lib/livechat/dashboard.css` is half shared.** Everything above the `GEM-SPECIFIC` banner is the design system all five gems in the family ship — the same tokens, the same `.page-head`/`.tabs`/`.filters`/`.card`/`.badge`/`button`, the same `.dashboard-shell` + `.record-row` + `.detail-panel` two-pane dashboard — identical in every repo apart from the `lvc` prefix. Diff it against a sibling before changing it, and carry the change to the other four. Anything only this gem has goes below the banner. New dashboard markup reuses the shared class names rather than inventing a domain-specific one.
|
|
179
|
+
- **`lib/livechat/dashboard.css` is half shared.** Everything above the `GEM-SPECIFIC` banner is the design system all five gems in the family ship — the same tokens, the same `.page-head`/`.tabs`/`.filters`/`.card`/`.badge`/`button`/`.status-switch`, the same `.dashboard-shell` + `.record-row` + `.detail-panel` two-pane dashboard — identical in every repo apart from the `lvc` prefix. Diff it against a sibling before changing it, and carry the change to the other four. Anything only this gem has goes below the banner. New dashboard markup reuses the shared class names rather than inventing a domain-specific one.
|
|
180
180
|
- Every user-facing change bumps `lib/livechat/version.rb` and adds a `CHANGELOG.md` entry that says what it costs, not only what it adds.
|
|
181
181
|
- Commit messages are prose that explains the tradeoff — read `git log` before writing one.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
- **The open/resolved switch is now the family's shared component.** It looks the
|
|
6
|
+
same and sits where it sat, but it is `.status-switch` in the shared core of
|
|
7
|
+
`dashboard.css` rather than this gem's own `.status-toggle`, so ideasbugs could
|
|
8
|
+
adopt it for its three statuses instead of growing a second near-identical
|
|
9
|
+
control. `.status-toggle`, `.status-toggle-form`, `.is-open` and `.is-resolved`
|
|
10
|
+
are gone — that is the breaking change if you styled them.
|
|
11
|
+
- Each half is now its own target: `Open` reopens, `Resolved` resolves, via
|
|
12
|
+
`formaction` on two submit buttons in one form. Before, the whole pill was one
|
|
13
|
+
button that flipped whichever way it was not. The half that is already current
|
|
14
|
+
is inert, so clicking it can no longer log a reopen of an open chat. The
|
|
15
|
+
sliding highlight went with the rewrite — it never animated anyway, since
|
|
16
|
+
resolving reloads the page and the pill arrives already in its new position.
|
|
17
|
+
`POST /:id/resolve` and `POST /:id/reopen` are unchanged.
|
|
18
|
+
|
|
19
|
+
## 0.9.1
|
|
20
|
+
|
|
21
|
+
- **The conversation heading reads as one block.** The visitor's email and locale
|
|
22
|
+
moved out of their own row and sit directly under the name, inside a
|
|
23
|
+
`.panel-titles` wrapper, and the open/resolved switch is pinned to the right
|
|
24
|
+
edge of the heading row with `margin-left: auto` rather than relying on the
|
|
25
|
+
row's `space-between` — a long name no longer drags the switch inward. If you
|
|
26
|
+
targeted `.context-line` as a child of `.detail-panel`, it is now a child of
|
|
27
|
+
`.panel-head`.
|
|
28
|
+
|
|
3
29
|
## 0.9.0
|
|
4
30
|
|
|
5
31
|
- **One design system across the family.** The stylesheet now opens with a
|
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
<div class="detail-panel">
|
|
2
2
|
<div class="panel-head">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
<%# The name with the visitor's context directly under it, so the status
|
|
4
|
+
toggle on the right of the row clears both. %>
|
|
5
|
+
<div class="panel-titles">
|
|
6
|
+
<h1><%= conversation.display_name %> <span class="case-id">#<%= conversation.id %></span></h1>
|
|
7
|
+
<%# One quiet line of live context: who the visitor is and the language to answer in. %>
|
|
8
|
+
<p class="muted context-line">
|
|
9
|
+
<% context = [] %>
|
|
10
|
+
<% context << conversation.visitor_email if conversation.visitor_email.present? %>
|
|
11
|
+
<% context << conversation.locale if conversation.locale.present? %>
|
|
12
|
+
<%= safe_join(context, ' · ') %>
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
15
|
+
<%# The shared status switch: the state it is in is lit and inert, the other
|
|
16
|
+
one submits to its own route via `formaction`. One form, no JS, and
|
|
17
|
+
clicking the lit half cannot log a reopen of an already-open chat. %>
|
|
18
|
+
<%= form_with url: resolve_conversation_path(conversation), method: :post, class: 'status-switch' do %>
|
|
19
|
+
<% if conversation.open? %>
|
|
20
|
+
<button type="button" class="status-open current" aria-current="true">
|
|
21
|
+
<%= t('livechat.statuses.open', default: 'Open') %>
|
|
22
|
+
</button>
|
|
23
|
+
<button formaction="<%= resolve_conversation_path(conversation) %>" class="status-resolved">
|
|
24
|
+
<%= t('livechat.statuses.resolved', default: 'Resolved') %>
|
|
25
|
+
</button>
|
|
26
|
+
<% else %>
|
|
27
|
+
<button formaction="<%= reopen_conversation_path(conversation) %>" class="status-open">
|
|
28
|
+
<%= t('livechat.statuses.open', default: 'Open') %>
|
|
29
|
+
</button>
|
|
30
|
+
<button type="button" class="status-resolved current" aria-current="true">
|
|
31
|
+
<%= t('livechat.statuses.resolved', default: 'Resolved') %>
|
|
32
|
+
</button>
|
|
19
33
|
<% end %>
|
|
20
34
|
<% end %>
|
|
21
35
|
</div>
|
|
22
36
|
|
|
23
|
-
<%# One quiet line of live context: who the visitor is and the language to answer in. %>
|
|
24
|
-
<p class="muted context-line">
|
|
25
|
-
<% context = [] %>
|
|
26
|
-
<% context << conversation.visitor_email if conversation.visitor_email.present? %>
|
|
27
|
-
<% context << conversation.locale if conversation.locale.present? %>
|
|
28
|
-
<%= safe_join(context, ' · ') %>
|
|
29
|
-
</p>
|
|
30
|
-
|
|
31
37
|
<div class="card">
|
|
32
38
|
<% last = messages.last %>
|
|
33
39
|
<% cable = livechat_cable_data("livechat:conversation:#{conversation.id}") %>
|
data/lib/livechat/dashboard.css
CHANGED
|
@@ -144,6 +144,19 @@
|
|
|
144
144
|
& .actions form { display: inline; }
|
|
145
145
|
& .form-actions { padding-top: 12px; }
|
|
146
146
|
|
|
147
|
+
/* Status switch: one pill holding every state a record can be in, the state it
|
|
148
|
+
is in now lit. Each other segment is a submit button, so moving a record
|
|
149
|
+
takes one click and no JavaScript; the current segment is a `type="button"`
|
|
150
|
+
so clicking it cannot re-file the record it is already filed under. Two
|
|
151
|
+
states or five, the pill sizes itself. A gem tints the lit segment with its
|
|
152
|
+
own status colour — see the `GEM-SPECIFIC` section. */
|
|
153
|
+
& .status-switch { display: inline-flex; flex: 0 0 auto; margin: 0; padding: 3px; border: 1px solid var(--lvc-border); border-radius: 999px; background: var(--lvc-bg); }
|
|
154
|
+
& .status-switch button { min-height: 30px; padding: 0 12px; border: 0; border-radius: 999px; background: none; color: var(--lvc-muted); font-size: 13px; font-weight: 700; white-space: nowrap; transition: background .16s ease, color .16s ease; }
|
|
155
|
+
& .status-switch button:hover { border: 0; background: color-mix(in srgb, var(--lvc-muted) 12%, transparent); color: var(--lvc-text); }
|
|
156
|
+
& .status-switch button.current { background: var(--lvc-surface); color: var(--lvc-text); box-shadow: 0 1px 2px rgba(15, 23, 42, .12); cursor: default; }
|
|
157
|
+
& .status-switch button.current:hover { background: var(--lvc-surface); }
|
|
158
|
+
& .panel-head .status-switch { margin-left: auto; }
|
|
159
|
+
|
|
147
160
|
& .card { overflow: hidden; border: 1px solid var(--lvc-border); border-radius: 12px; background: var(--lvc-surface); }
|
|
148
161
|
& .card.pad { padding: 16px; overflow: visible; }
|
|
149
162
|
& table { width: 100%; border-collapse: collapse; }
|
|
@@ -266,6 +279,7 @@
|
|
|
266
279
|
& .dashboard-detail .detail-panel { min-height: calc(100vh - 64px); }
|
|
267
280
|
& .mobile-back { display: block; flex: 0 0 auto; margin: 4px 0 10px; font-size: 13px; }
|
|
268
281
|
& .panel-head h1 { font-size: 18px; }
|
|
282
|
+
& .status-switch button { padding: 0 9px; font-size: 12px; }
|
|
269
283
|
& dl { grid-template-columns: 1fr; gap: 3px; }
|
|
270
284
|
& dd { margin-bottom: 8px; }
|
|
271
285
|
}
|
|
@@ -290,16 +304,13 @@
|
|
|
290
304
|
& .record-row.unread .record-copy { font-weight: 700; }
|
|
291
305
|
& .record-side .avatars { min-height: 26px; }
|
|
292
306
|
|
|
293
|
-
/*
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
& .
|
|
297
|
-
& .
|
|
298
|
-
|
|
299
|
-
& .status-
|
|
300
|
-
& .status-toggle.is-open span:first-child { color: var(--lvc-open); }
|
|
301
|
-
& .status-toggle.is-resolved span:last-child { color: var(--lvc-text); }
|
|
302
|
-
& .status-toggle:hover { text-decoration: none; color: var(--lvc-text); }
|
|
307
|
+
/* The panel heading: the name with the visitor's context directly under it,
|
|
308
|
+
and the shared status switch pushed to the far right of the row whatever the
|
|
309
|
+
name's length. */
|
|
310
|
+
& .panel-titles { display: flex; min-width: 0; flex-direction: column; gap: 2px; }
|
|
311
|
+
& .context-line { margin: 0; overflow-wrap: anywhere; }
|
|
312
|
+
/* An open conversation is the one that wants an answer, so say so in green. */
|
|
313
|
+
& .status-switch button.current.status-open { color: var(--lvc-open); }
|
|
303
314
|
|
|
304
315
|
& .avatars { display: inline-flex; }
|
|
305
316
|
& .avatars .avatar { width: 26px; height: 26px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; color: #fff; border: 2px solid var(--lvc-surface); cursor: default; }
|
|
@@ -312,7 +323,6 @@
|
|
|
312
323
|
& .avatar.color-5 { background: #0891b2; }
|
|
313
324
|
|
|
314
325
|
/* The thread: the visitor on the left, agents on the right. */
|
|
315
|
-
& .context-line { margin: -6px 0 16px; overflow-wrap: anywhere; }
|
|
316
326
|
& .thread { display: flex; flex-direction: column; gap: 4px; padding: 16px; }
|
|
317
327
|
& .thread .who { font-size: 12px; color: var(--lvc-muted); margin: 10px 4px 2px; }
|
|
318
328
|
& .thread .who.visitor { text-align: left; }
|
|
@@ -362,8 +372,7 @@
|
|
|
362
372
|
|
|
363
373
|
/* Inside the thread pane only the messages scroll, so a reply never jumps the
|
|
364
374
|
page and the composer stays put. */
|
|
365
|
-
& .detail-panel .panel-head
|
|
366
|
-
& .detail-panel .context-line { flex: 0 0 auto; }
|
|
375
|
+
& .detail-panel .panel-head { flex: 0 0 auto; }
|
|
367
376
|
& .detail-panel .card { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; border-radius: 8px; }
|
|
368
377
|
& .detail-panel .thread { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
|
369
378
|
& .detail-panel .reply { flex: 0 0 auto; }
|
|
@@ -376,15 +385,12 @@
|
|
|
376
385
|
.lvc-convo .container { display: flex; flex-direction: column; padding-bottom: 16px; }
|
|
377
386
|
.lvc-convo .breadcrumb,
|
|
378
387
|
.lvc-convo .panel-head,
|
|
379
|
-
.lvc-convo .context-line,
|
|
380
388
|
.lvc-convo .flash { flex: 0 0 auto; }
|
|
381
389
|
.lvc-convo .detail-panel { flex: 1 1 auto; min-height: 0; }
|
|
382
390
|
|
|
383
391
|
@media (max-width: 760px) {
|
|
384
392
|
.lvc-dashboard {
|
|
385
393
|
& .detail-panel .panel-head h1 { font-size: 18px; }
|
|
386
|
-
& .status-toggle { width: 152px; min-height: 34px; }
|
|
387
|
-
& .status-toggle span { padding: 0 7px; font-size: 12px; }
|
|
388
394
|
& .thread .msg { max-width: 88%; }
|
|
389
395
|
}
|
|
390
396
|
}
|
data/lib/livechat/version.rb
CHANGED