@lightworkai.official/debug-capture 0.19.3 → 0.19.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.
- package/README.md +8 -1
- package/dist/config.d.ts +6 -0
- package/dist/debug-capture.js +19 -19
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +24 -24
- package/dist/live.d.ts +4 -2
- package/dist/ui/chatBubble.d.ts +10 -0
- package/dist/ui/ring.d.ts +4 -2
- package/dist/ui/strings.d.ts +2 -0
- package/package.json +1 -1
- package/src/config.ts +6 -0
- package/src/index.ts +1 -1
- package/src/live.ts +6 -2
- package/src/ui/chatBubble.ts +92 -25
- package/src/ui/ring.ts +6 -3
- package/src/ui/strings.ts +4 -0
package/dist/live.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface RingingCall {
|
|
2
2
|
id: string;
|
|
3
|
-
|
|
3
|
+
/** A เรื่อง's call — or a chat's (โทรจากแชท, 0.19.4): then `chatId`, and the key "แชท". */
|
|
4
|
+
ticketId: string | null;
|
|
5
|
+
chatId?: string | null;
|
|
4
6
|
ticketKey: string;
|
|
5
7
|
ticketTitle: string;
|
|
6
8
|
status: string;
|
|
@@ -10,7 +12,7 @@ export interface RingingCall {
|
|
|
10
12
|
/** `wss://support.example/api/presence/ws?t=…` — the support host's scheme, the socket's own path. Pure, for the tests. */
|
|
11
13
|
export declare function liveSocketUrl(host: string, ticket: string): string;
|
|
12
14
|
/** Where the card sends them: their request, with the call — the support host rings it there. Pure, for the tests. */
|
|
13
|
-
export declare function answerPath(call: Pick<RingingCall, "id" | "ticketId">): string;
|
|
15
|
+
export declare function answerPath(call: Pick<RingingCall, "id" | "ticketId" | "chatId">): string;
|
|
14
16
|
/** Is this a call ringing for the person at this widget? */
|
|
15
17
|
export declare function ringsHere(call: Pick<RingingCall, "status" | "side">): boolean;
|
|
16
18
|
type LiveFrame = {
|
package/dist/ui/chatBubble.d.ts
CHANGED
|
@@ -65,6 +65,14 @@ export declare function bubbleLabel(s: {
|
|
|
65
65
|
shown: boolean;
|
|
66
66
|
isNew: boolean;
|
|
67
67
|
} | null;
|
|
68
|
+
export interface DeskLook {
|
|
69
|
+
label?: string;
|
|
70
|
+
color?: string;
|
|
71
|
+
/** A path on the support host; null: the chat glyph on the colour. */
|
|
72
|
+
faceUrl?: string | null;
|
|
73
|
+
}
|
|
74
|
+
/** The face's address for a desk's look: its own path on the support host, the shipped one, or none (the glyph). Pure. */
|
|
75
|
+
export declare function lookFaceUrl(host: string, look: DeskLook | null): string | null;
|
|
68
76
|
/** Open the panel — from the bubble, or from the host app's own button (`openSupportChat`). */
|
|
69
77
|
export declare function openSupportChat(): void;
|
|
70
78
|
/** Fold the panel — it stays loaded, so the chat is there as it was the next time. */
|
|
@@ -72,4 +80,6 @@ export declare function closeSupportChat(refocus?: boolean): void;
|
|
|
72
80
|
/** The bubble, once — hidden until the host's token says somebody is signed in. */
|
|
73
81
|
export declare function mountSupportChat(): void;
|
|
74
82
|
/** Take it away — for a host that unmounts, and for the tests. */
|
|
83
|
+
/** Ask again whether there is a bubble, and what waits (0.19.5) — after a sign-in or a sign-out, or `show` changed. */
|
|
84
|
+
export declare function refreshSupportChat(): Promise<void>;
|
|
75
85
|
export declare function unmountSupportChat(): void;
|
package/dist/ui/ring.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export interface RingCall {
|
|
|
2
2
|
id: string;
|
|
3
3
|
ticketKey: string;
|
|
4
4
|
ticketTitle: string;
|
|
5
|
+
/** A chat's call (โทรจากแชท, 0.19.4) — said as แชทกับทีมงาน, never "เรื่อง แชท". */
|
|
6
|
+
chatId?: string | null;
|
|
5
7
|
}
|
|
6
8
|
export interface RingHandlers {
|
|
7
9
|
/** The portal's way: runs INSIDE the press — a tab can be opened only there, before anything is awaited. */
|
|
@@ -14,8 +16,8 @@ export interface RingHandlers {
|
|
|
14
16
|
}
|
|
15
17
|
/** `frame`: the call's own card while its frame comes; `portal`: answer in a new tab (no frame, or it failed). */
|
|
16
18
|
export type RingMode = "frame" | "portal";
|
|
17
|
-
/** "เรื่อง CALL-12 · title" — the line under the card's title. Pure, for the tests. */
|
|
18
|
-
export declare function ringAbout(template: string, call: RingCall): string;
|
|
19
|
+
/** "เรื่อง CALL-12 · title" — the line under the card's title; a chat's call, its own words (`chat`). Pure, for the tests. */
|
|
20
|
+
export declare function ringAbout(template: string, call: RingCall, chat?: string): string;
|
|
19
21
|
/** Show the ring for this call — once; the same call again changes nothing. */
|
|
20
22
|
export declare function showRing(call: RingCall, handlers: RingHandlers, mode?: RingMode): void;
|
|
21
23
|
/** รับสาย pressed while the call's frame comes: said, and nothing more to press until it is there. */
|
package/dist/ui/strings.d.ts
CHANGED
|
@@ -106,6 +106,8 @@ export interface Strings {
|
|
|
106
106
|
/** A call from the support team (ui/ring). `ringAbout` carries `{key}` and `{title}`. */
|
|
107
107
|
ringTitle: string;
|
|
108
108
|
ringAbout: string;
|
|
109
|
+
/** A call from the chat, not a เรื่อง (โทรจากแชท, 0.19.4) — the line under the card's title. */
|
|
110
|
+
ringChat: string;
|
|
109
111
|
ringHow: string;
|
|
110
112
|
ringOpen: string;
|
|
111
113
|
ringDecline: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightworkai.official/debug-capture",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.5",
|
|
4
4
|
"description": "Report-a-problem widget: rolling network/console/action capture, optional rrweb session replay, annotated screenshot, and submission to a Lightwork Support host",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.mjs",
|
package/src/config.ts
CHANGED
|
@@ -232,6 +232,12 @@ export interface ChatOptions {
|
|
|
232
232
|
* of "แชทกับทีมงาน", or `false` for the button alone.
|
|
233
233
|
*/
|
|
234
234
|
label?: string | false;
|
|
235
|
+
/**
|
|
236
|
+
* Whether the bubble is for whoever is signed in now (0.19.5) — asked before every read; false: no bubble. For a
|
|
237
|
+
* host whose own staff should not chat with its support desk, or not on every page. Ask again after a sign-in with
|
|
238
|
+
* `refreshSupportChat()`.
|
|
239
|
+
*/
|
|
240
|
+
show?: () => boolean;
|
|
235
241
|
}
|
|
236
242
|
|
|
237
243
|
/** The bubble's place, from `chat`: null when it is off. */
|
package/src/index.ts
CHANGED
|
@@ -77,7 +77,7 @@ export type { OpenPortalOptions } from "./portal";
|
|
|
77
77
|
* แชทกับทีม (0.19): `chat` in the config mounts the bubble on install; a host with its own button opens and closes
|
|
78
78
|
* the same panel. The panel is the support host's page, framed — as the tickets are, maintained where they live.
|
|
79
79
|
*/
|
|
80
|
-
export { openSupportChat, closeSupportChat, mountSupportChat, unmountSupportChat, supportFrameUrl, badgeText, chatLift, panelHeight } from "./ui/chatBubble";
|
|
80
|
+
export { openSupportChat, closeSupportChat, mountSupportChat, unmountSupportChat, refreshSupportChat, supportFrameUrl, badgeText, chatLift, panelHeight } from "./ui/chatBubble";
|
|
81
81
|
export type { ChatOptions } from "./config";
|
|
82
82
|
|
|
83
83
|
export type * from "./types";
|
package/src/live.ts
CHANGED
|
@@ -31,7 +31,9 @@ import { inkOnPage, removeInkLayer } from "./ui/inkOverlay";
|
|
|
31
31
|
|
|
32
32
|
export interface RingingCall {
|
|
33
33
|
id: string;
|
|
34
|
-
|
|
34
|
+
/** A เรื่อง's call — or a chat's (โทรจากแชท, 0.19.4): then `chatId`, and the key "แชท". */
|
|
35
|
+
ticketId: string | null;
|
|
36
|
+
chatId?: string | null;
|
|
35
37
|
ticketKey: string;
|
|
36
38
|
ticketTitle: string;
|
|
37
39
|
status: string;
|
|
@@ -65,7 +67,9 @@ export function liveSocketUrl(host: string, ticket: string): string {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
/** Where the card sends them: their request, with the call — the support host rings it there. Pure, for the tests. */
|
|
68
|
-
export function answerPath(call: Pick<RingingCall, "id" | "ticketId">): string {
|
|
70
|
+
export function answerPath(call: Pick<RingingCall, "id" | "ticketId" | "chatId">): string {
|
|
71
|
+
// A chat's call (0.19.4): their chat with the team, where it rings.
|
|
72
|
+
if (call.chatId || !call.ticketId) return `/me/chat?call=${call.id}`;
|
|
69
73
|
return `/me/tickets/${call.ticketId}?call=${call.id}`;
|
|
70
74
|
}
|
|
71
75
|
|
package/src/ui/chatBubble.ts
CHANGED
|
@@ -33,11 +33,11 @@ const CSS = `
|
|
|
33
33
|
.bubble {
|
|
34
34
|
position: fixed; z-index: 2147483000; width: ${SIZE}px; height: ${SIZE}px; border-radius: 50%;
|
|
35
35
|
display: grid; place-items: center; border: 0; cursor: pointer; padding: 0;
|
|
36
|
-
background: #00468f; color: #fff; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 4px 10px -4px rgba(0, 0, 0, 0.2);
|
|
36
|
+
background: var(--lw-chat-color, #00468f); color: #fff; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 4px 10px -4px rgba(0, 0, 0, 0.2);
|
|
37
37
|
transition: transform 150ms ease, background 150ms ease;
|
|
38
38
|
font-family: var(--lw-host-font, ${OWN_FONTS});
|
|
39
39
|
}
|
|
40
|
-
.bubble:hover {
|
|
40
|
+
.bubble:hover { filter: brightness(0.92); transform: translateY(-1px); }
|
|
41
41
|
.bubble { touch-action: none; }
|
|
42
42
|
.bubble.dragging { cursor: grabbing; transform: scale(1.06); transition: none; box-shadow: 0 18px 35px -8px rgba(0, 0, 0, 0.4); }
|
|
43
43
|
.bubble:focus-visible { outline: 3px solid rgba(0, 70, 143, 0.35); outline-offset: 3px; }
|
|
@@ -65,7 +65,7 @@ const CSS = `
|
|
|
65
65
|
.label .text { display: block; max-width: min(240px, calc(100vw - ${SIZE + 64}px)); overflow: hidden; text-overflow: ellipsis; }
|
|
66
66
|
.at-left .label { right: auto; left: calc(100% + 12px); transform: translate(-4px, -50%); }
|
|
67
67
|
.at-left .label::after { right: auto; left: -4px; }
|
|
68
|
-
.new .label { color: #00468f; }
|
|
68
|
+
.new .label { color: var(--lw-chat-color, #00468f); }
|
|
69
69
|
.labelled .label { opacity: 1; visibility: visible; pointer-events: auto; transform: translate(0, -50%); }
|
|
70
70
|
@media (hover: hover) { .bubble:hover .label { opacity: 1; visibility: visible; pointer-events: auto; transform: translate(0, -50%); } }
|
|
71
71
|
.unlabelled .label, .bubble.dragging .label, .open .label { display: none; }
|
|
@@ -207,7 +207,9 @@ let opened = false;
|
|
|
207
207
|
function setLabel(): void {
|
|
208
208
|
if (!label || !bubble) return;
|
|
209
209
|
const config = getConfig();
|
|
210
|
-
|
|
210
|
+
// The host's own words in code win; else the desk's (ปุ่มแชท, its look); else the shipped ones.
|
|
211
|
+
const hostOwn = config.chat && config.chat !== true ? config.chat.label : undefined;
|
|
212
|
+
const own = hostOwn !== undefined ? hostOwn : deskLook?.label;
|
|
211
213
|
const l = bubbleLabel({ own, unread, opened, open: isOpen }, strings(config.locale));
|
|
212
214
|
bubble.classList.toggle("unlabelled", !l);
|
|
213
215
|
bubble.classList.toggle("labelled", !!l?.shown);
|
|
@@ -216,28 +218,83 @@ function setLabel(): void {
|
|
|
216
218
|
label.querySelector(".text")!.textContent = l?.text ?? "";
|
|
217
219
|
}
|
|
218
220
|
|
|
221
|
+
/*
|
|
222
|
+
* The desk's look (0.19.5, the support desk's ปุ่มแชท — docs/CHAT-BUBBLE-PLAN.md): its face (or the chat glyph on its
|
|
223
|
+
* colour), its colour, the words beside the button — set on the desk, read with the first answer here.
|
|
224
|
+
*/
|
|
225
|
+
export interface DeskLook {
|
|
226
|
+
label?: string;
|
|
227
|
+
color?: string;
|
|
228
|
+
/** A path on the support host; null: the chat glyph on the colour. */
|
|
229
|
+
faceUrl?: string | null;
|
|
230
|
+
}
|
|
231
|
+
let deskLook: DeskLook | null = null;
|
|
232
|
+
/** The face's address for a desk's look: its own path on the support host, the shipped one, or none (the glyph). Pure. */
|
|
233
|
+
export function lookFaceUrl(host: string, look: DeskLook | null): string | null {
|
|
234
|
+
if (!look || look.faceUrl === undefined) return supportFaceUrl(host);
|
|
235
|
+
if (look.faceUrl === null) return null;
|
|
236
|
+
try {
|
|
237
|
+
return new URL(look.faceUrl.replace(/^\//, ""), base(host)).toString();
|
|
238
|
+
} catch {
|
|
239
|
+
return supportFaceUrl(host);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function wearLook(look: DeskLook | null): void {
|
|
243
|
+
deskLook = look;
|
|
244
|
+
if (!root || !bubble) return;
|
|
245
|
+
const color = look?.color && /^#[0-9a-f]{6}$/i.test(look.color) ? look.color : null;
|
|
246
|
+
if (color) root.style.setProperty("--lw-chat-color", color);
|
|
247
|
+
else root.style.removeProperty("--lw-chat-color");
|
|
248
|
+
const face = shadow?.querySelector<HTMLImageElement>(".face");
|
|
249
|
+
const url = lookFaceUrl(getConfig().host, look);
|
|
250
|
+
if (face) {
|
|
251
|
+
if (!url) {
|
|
252
|
+
bubble.classList.remove("has-face");
|
|
253
|
+
face.removeAttribute("src");
|
|
254
|
+
} else if (face.getAttribute("src") !== url) {
|
|
255
|
+
bubble.classList.remove("has-face");
|
|
256
|
+
face.src = url;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
setLabel();
|
|
260
|
+
}
|
|
261
|
+
|
|
219
262
|
let asking: Promise<void> | null = null;
|
|
220
263
|
/** What waits for the person, read with the host's token — no token: nobody is signed in, and there is no bubble. */
|
|
221
264
|
async function refreshUnread(): Promise<void> {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
265
|
+
if (asking) return asking;
|
|
266
|
+
/*
|
|
267
|
+
* Kept until it settles, then let go — by THIS read only. (`asking ??= (async () => { … finally { asking = null } })()`
|
|
268
|
+
* lost the let-go when the read ended before its first await — `show` saying no: the finally ran before the
|
|
269
|
+
* assignment, and every read after was that settled one, never asked again.)
|
|
270
|
+
*/
|
|
271
|
+
const run = readUnread();
|
|
272
|
+
asking = run;
|
|
273
|
+
try {
|
|
274
|
+
await run;
|
|
275
|
+
} finally {
|
|
276
|
+
if (asking === run) asking = null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function readUnread(): Promise<void> {
|
|
280
|
+
try {
|
|
281
|
+
const config = getConfig();
|
|
282
|
+
// The host's own say (0.19.5): not for whoever is signed in now — no bubble, nothing asked.
|
|
283
|
+
const opts = config.chat && config.chat !== true ? config.chat : null;
|
|
284
|
+
if (opts?.show && !opts.show()) return void show(false);
|
|
285
|
+
const token = await hostToken();
|
|
286
|
+
if (!token) return void show(false);
|
|
287
|
+
const res = await fetch(new URL("ingest/support-unread", base(config.host)).toString(), { headers: { "x-realm-key": config.realmKey, authorization: `Bearer ${token}` } });
|
|
288
|
+
// A support host from before the chat, a desk that turned its bubble off, or a token it does not accept: no bubble.
|
|
289
|
+
if (res.status === 404 || res.status === 401 || res.status === 403) return void show(false);
|
|
290
|
+
if (!res.ok) return;
|
|
291
|
+
const out = (await res.json()) as { total?: number; look?: DeskLook };
|
|
292
|
+
if (out.look && typeof out.look === "object") wearLook(out.look);
|
|
293
|
+
show(true);
|
|
294
|
+
if (!isOpen) setBadge(typeof out.total === "number" ? out.total : 0);
|
|
295
|
+
} catch {
|
|
296
|
+
/* the next nudge asks again */
|
|
297
|
+
}
|
|
241
298
|
}
|
|
242
299
|
|
|
243
300
|
function show(on: boolean): void {
|
|
@@ -491,8 +548,12 @@ export function mountSupportChat(): void {
|
|
|
491
548
|
// The desk's face (the owner, 10-31: "all the support icon that user see … use this icon"): the support host's own,
|
|
492
549
|
// the one its replies wear — the chat glyph until it is there, and if it never comes.
|
|
493
550
|
const face = shadow.querySelector<HTMLImageElement>(".face")!;
|
|
494
|
-
|
|
495
|
-
face.
|
|
551
|
+
// Every picture it is given (the desk's look may change it, 0.19.5): worn once loaded, the glyph if it fails.
|
|
552
|
+
face.addEventListener("load", () => {
|
|
553
|
+
if (face.getAttribute("src")) bubble?.classList.add("has-face");
|
|
554
|
+
});
|
|
555
|
+
face.addEventListener("error", () => bubble?.classList.remove("has-face"));
|
|
556
|
+
face.src = lookFaceUrl(config.host, deskLook) ?? supportFaceUrl(config.host);
|
|
496
557
|
// Where the person left it on this device, else the config's place (0.19.2).
|
|
497
558
|
loadPlace();
|
|
498
559
|
layout();
|
|
@@ -521,7 +582,13 @@ export function mountSupportChat(): void {
|
|
|
521
582
|
}
|
|
522
583
|
|
|
523
584
|
/** Take it away — for a host that unmounts, and for the tests. */
|
|
585
|
+
/** Ask again whether there is a bubble, and what waits (0.19.5) — after a sign-in or a sign-out, or `show` changed. */
|
|
586
|
+
export function refreshSupportChat(): Promise<void> {
|
|
587
|
+
return root ? refreshUnread() : Promise.resolve();
|
|
588
|
+
}
|
|
589
|
+
|
|
524
590
|
export function unmountSupportChat(): void {
|
|
591
|
+
deskLook = null;
|
|
525
592
|
stopFrames?.();
|
|
526
593
|
stopFrames = null;
|
|
527
594
|
window.removeEventListener("message", onMessage);
|
package/src/ui/ring.ts
CHANGED
|
@@ -87,6 +87,8 @@ export interface RingCall {
|
|
|
87
87
|
id: string;
|
|
88
88
|
ticketKey: string;
|
|
89
89
|
ticketTitle: string;
|
|
90
|
+
/** A chat's call (โทรจากแชท, 0.19.4) — said as แชทกับทีมงาน, never "เรื่อง แชท". */
|
|
91
|
+
chatId?: string | null;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
export interface RingHandlers {
|
|
@@ -106,8 +108,9 @@ let host: HTMLElement | null = null;
|
|
|
106
108
|
let shownId: string | null = null;
|
|
107
109
|
let titleBefore: string | null = null;
|
|
108
110
|
|
|
109
|
-
/** "เรื่อง CALL-12 · title" — the line under the card's title. Pure, for the tests. */
|
|
110
|
-
export function ringAbout(template: string, call: RingCall): string {
|
|
111
|
+
/** "เรื่อง CALL-12 · title" — the line under the card's title; a chat's call, its own words (`chat`). Pure, for the tests. */
|
|
112
|
+
export function ringAbout(template: string, call: RingCall, chat?: string): string {
|
|
113
|
+
if (call.chatId) return chat ?? call.ticketTitle;
|
|
111
114
|
return template.replace("{key}", call.ticketKey).replace("{title}", call.ticketTitle);
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -164,7 +167,7 @@ function render(root: ShadowRoot, call: RingCall, handlers: RingHandlers, mode:
|
|
|
164
167
|
h.textContent = s.ringTitle;
|
|
165
168
|
const about = document.createElement("p");
|
|
166
169
|
about.className = "about";
|
|
167
|
-
about.textContent = ringAbout(s.ringAbout, call);
|
|
170
|
+
about.textContent = ringAbout(s.ringAbout, call, s.ringChat);
|
|
168
171
|
about.title = about.textContent;
|
|
169
172
|
words.append(h, about);
|
|
170
173
|
head.append(icon, words);
|
package/src/ui/strings.ts
CHANGED
|
@@ -111,6 +111,8 @@ export interface Strings {
|
|
|
111
111
|
/** A call from the support team (ui/ring). `ringAbout` carries `{key}` and `{title}`. */
|
|
112
112
|
ringTitle: string;
|
|
113
113
|
ringAbout: string;
|
|
114
|
+
/** A call from the chat, not a เรื่อง (โทรจากแชท, 0.19.4) — the line under the card's title. */
|
|
115
|
+
ringChat: string;
|
|
114
116
|
ringHow: string;
|
|
115
117
|
ringOpen: string;
|
|
116
118
|
ringDecline: string;
|
|
@@ -211,6 +213,7 @@ const th: Strings = {
|
|
|
211
213
|
hours: "{n} ชั่วโมง",
|
|
212
214
|
ringTitle: "ทีมงานกำลังโทรหาคุณ",
|
|
213
215
|
ringAbout: "เรื่อง {key} · {title}",
|
|
216
|
+
ringChat: "จากแชทกับทีมงาน",
|
|
214
217
|
ringHow: "รับสายในหน้าคำร้องของคุณ — เปิดในแท็บใหม่ ไม่ต้องเข้าสู่ระบบอีก",
|
|
215
218
|
ringOpen: "เปิดเพื่อรับสาย",
|
|
216
219
|
ringDecline: "ไม่รับ",
|
|
@@ -306,6 +309,7 @@ const en: Strings = {
|
|
|
306
309
|
hours: "{n} hours",
|
|
307
310
|
ringTitle: "The support team is calling you",
|
|
308
311
|
ringAbout: "About {key} · {title}",
|
|
312
|
+
ringChat: "From your chat with support",
|
|
309
313
|
ringHow: "Answer on your request's page — it opens in a new tab, already signed in",
|
|
310
314
|
ringOpen: "Open to answer",
|
|
311
315
|
ringDecline: "Decline",
|