@sphoro/bugcatch-web-sdk 0.18.0 → 0.19.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.
- package/README.md +36 -3
- package/dist/bugcatch-call.es.js +201 -200
- package/dist/bugcatch-call.es.js.map +1 -1
- package/dist/bugcatch-call.umd.js +1 -1
- package/dist/bugcatch-call.umd.js.map +1 -1
- package/dist/bugcatch-core.es.js +1085 -1007
- package/dist/bugcatch-core.es.js.map +1 -1
- package/dist/bugcatch-panel.es.js +1179 -1050
- package/dist/bugcatch-panel.es.js.map +1 -1
- package/dist/bugcatch-panel.umd.js +397 -326
- package/dist/bugcatch-panel.umd.js.map +1 -1
- package/dist/bugcatch.standalone.umd.js +520 -437
- package/dist/bugcatch.standalone.umd.js.map +1 -1
- package/dist/bugcatch.umd.js +132 -120
- package/dist/bugcatch.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/accent.js +38 -0
- package/src/incoming-call.js +17 -15
- package/src/index.d.ts +43 -2
- package/src/index.js +26 -7
- package/src/panel/icons.js +2 -0
- package/src/panel/index.js +17 -3
- package/src/panel/messages.js +2 -2
- package/src/panel/picks.js +6 -2
- package/src/panel/refs.js +1 -1
- package/src/panel/render.js +11 -3
- package/src/panel/styles.js +242 -186
- package/src/panel/thread.js +117 -7
- package/src/updates.js +77 -27
- package/src/widget.js +160 -141
package/package.json
CHANGED
package/src/accent.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// The colour the widget's buttons wear, and the colour of the words on them.
|
|
2
|
+
//
|
|
3
|
+
// With no `accent` configured the widget is ink: a black pill on a light page
|
|
4
|
+
// and a white one on a dark page, which sits on any brand without arguing with
|
|
5
|
+
// it. A configured accent is the host's own colour, and the text on it is
|
|
6
|
+
// whichever of white or ink can be read against it — a pale brand colour with
|
|
7
|
+
// white words on it is a button nobody can read.
|
|
8
|
+
|
|
9
|
+
export const INK = '#0e0e10';
|
|
10
|
+
export const PAPER = '#ffffff';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* White or ink, whichever reads on `color`. Only colours this can parse (#rgb,
|
|
14
|
+
* #rrggbb, rgb()) are measured; anything else (a name, hsl(), a variable) gets
|
|
15
|
+
* white, which is what every accent got before this existed.
|
|
16
|
+
*/
|
|
17
|
+
export function readableOn(color) {
|
|
18
|
+
const rgb = parse(color);
|
|
19
|
+
if (!rgb) return PAPER;
|
|
20
|
+
const [r, g, b] = rgb.map((c) => {
|
|
21
|
+
const s = c / 255;
|
|
22
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
23
|
+
});
|
|
24
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
25
|
+
// The crossover where ink starts to out-contrast white.
|
|
26
|
+
return luminance > 0.179 ? INK : PAPER;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parse(color) {
|
|
30
|
+
const s = String(color || '').trim();
|
|
31
|
+
let m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
32
|
+
if (m) return [...m[1]].map((h) => parseInt(h + h, 16));
|
|
33
|
+
m = /^#([0-9a-f]{6})$/i.exec(s);
|
|
34
|
+
if (m) return [0, 2, 4].map((i) => parseInt(m[1].slice(i, i + 2), 16));
|
|
35
|
+
m = /^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/i.exec(s);
|
|
36
|
+
if (m) return [m[1], m[2], m[3]].map(Number);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
package/src/incoming-call.js
CHANGED
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
// yet, which is its rule and not ours — the card rings silently rather than not
|
|
38
38
|
// appearing.
|
|
39
39
|
|
|
40
|
+
import { readableOn, INK, PAPER } from './accent.js';
|
|
41
|
+
|
|
40
42
|
/**
|
|
41
43
|
* A quiet double-pulse, once every three seconds.
|
|
42
44
|
*
|
|
@@ -157,8 +159,8 @@ const BUTTON = [
|
|
|
157
159
|
'flex:1',
|
|
158
160
|
'padding:9px 12px',
|
|
159
161
|
'border:0',
|
|
160
|
-
'border-radius:
|
|
161
|
-
'font:
|
|
162
|
+
'border-radius:999px',
|
|
163
|
+
'font:560 13px/1.4 inherit',
|
|
162
164
|
'cursor:pointer',
|
|
163
165
|
].join(';');
|
|
164
166
|
|
|
@@ -168,7 +170,8 @@ const BUTTON = [
|
|
|
168
170
|
* @param {object} opts
|
|
169
171
|
* @param {object} opts.call The `incomingCall` object off the update stream —
|
|
170
172
|
* `callId`, `from`, `project`, `reference`, `ringsUntil`, `recorded`.
|
|
171
|
-
* @param {string} opts.accent The integration's own colour, for Answer
|
|
173
|
+
* @param {string|null} opts.accent The integration's own colour, for Answer;
|
|
174
|
+
* null is the ink default, which on this dark card is a white button.
|
|
172
175
|
* @param {string} [opts.position] Same two words the toast takes.
|
|
173
176
|
* @param {() => void} opts.onAnswer Pressed Answer.
|
|
174
177
|
* @param {() => void} opts.onDecline Pressed Decline.
|
|
@@ -181,7 +184,7 @@ const BUTTON = [
|
|
|
181
184
|
* an ordinary dismissal; the caller passes one only for its own logging.
|
|
182
185
|
*/
|
|
183
186
|
export function showIncomingCall({
|
|
184
|
-
call, accent =
|
|
187
|
+
call, accent = null, position = 'bottom-right',
|
|
185
188
|
onAnswer, onDecline, onExpire, touched = () => false,
|
|
186
189
|
}) {
|
|
187
190
|
if (typeof document === 'undefined' || !document.body) return () => {};
|
|
@@ -200,13 +203,12 @@ export function showIncomingCall({
|
|
|
200
203
|
'z-index:2147483647',
|
|
201
204
|
'width:300px',
|
|
202
205
|
'max-width:calc(100vw - 40px)',
|
|
203
|
-
'padding:
|
|
204
|
-
'border-radius:
|
|
205
|
-
'background:#
|
|
206
|
-
'color:#
|
|
206
|
+
'padding:16px',
|
|
207
|
+
'border-radius:16px',
|
|
208
|
+
'background:#111113',
|
|
209
|
+
'color:#f3f2ef',
|
|
207
210
|
'font:13px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif',
|
|
208
|
-
|
|
209
|
-
`border:1px solid ${accent}`,
|
|
211
|
+
`box-shadow:0 0 0 1px ${accent ? `color-mix(in srgb, ${accent} 55%, transparent)` : 'rgba(255,255,255,.12)'},0 24px 50px -12px rgba(0,0,0,.6)`,
|
|
210
212
|
'opacity:0',
|
|
211
213
|
'transform:translateY(8px)',
|
|
212
214
|
'transition:opacity .2s ease,transform .2s ease',
|
|
@@ -219,23 +221,23 @@ export function showIncomingCall({
|
|
|
219
221
|
heading.textContent = `${call.from || 'Support'} is calling`;
|
|
220
222
|
|
|
221
223
|
const about = document.createElement('div');
|
|
222
|
-
about.style.cssText = 'color:#
|
|
224
|
+
about.style.cssText = 'color:#b3b0aa;margin-bottom:10px';
|
|
223
225
|
about.textContent = call.project
|
|
224
226
|
? `About your report ${call.reference} · ${call.project}`
|
|
225
227
|
: `About your report ${call.reference}`;
|
|
226
228
|
|
|
227
229
|
const countdown = document.createElement('div');
|
|
228
|
-
countdown.style.cssText = 'color:#
|
|
230
|
+
countdown.style.cssText = 'color:#8f8c86;font-size:12px;margin-bottom:10px';
|
|
229
231
|
|
|
230
232
|
const answer = document.createElement('button');
|
|
231
233
|
answer.type = 'button';
|
|
232
234
|
answer.textContent = 'Answer';
|
|
233
|
-
answer.style.cssText = `${BUTTON};background:${accent};color
|
|
235
|
+
answer.style.cssText = `${BUTTON};background:${accent || PAPER};color:${accent ? readableOn(accent) : INK}`;
|
|
234
236
|
|
|
235
237
|
const decline = document.createElement('button');
|
|
236
238
|
decline.type = 'button';
|
|
237
239
|
decline.textContent = 'Not now';
|
|
238
|
-
decline.style.cssText = `${BUTTON};background
|
|
240
|
+
decline.style.cssText = `${BUTTON};background:rgba(255,255,255,.08);color:#f3f2ef`;
|
|
239
241
|
|
|
240
242
|
const actions = document.createElement('div');
|
|
241
243
|
actions.style.cssText = 'display:flex;gap:8px';
|
|
@@ -244,7 +246,7 @@ export function showIncomingCall({
|
|
|
244
246
|
el.append(heading, about, countdown);
|
|
245
247
|
if (call.recorded) {
|
|
246
248
|
const kept = document.createElement('div');
|
|
247
|
-
kept.style.cssText = 'color:#
|
|
249
|
+
kept.style.cssText = 'color:#8f8c86;font-size:12px;margin-bottom:10px';
|
|
248
250
|
kept.textContent = 'This call will be recorded.';
|
|
249
251
|
el.append(kept);
|
|
250
252
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -196,8 +196,12 @@ export interface BugCatchConfig {
|
|
|
196
196
|
widgetPosition?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
197
197
|
/** 'auto' follows the visitor's system setting. Default 'auto'. */
|
|
198
198
|
theme?: 'auto' | 'light' | 'dark';
|
|
199
|
-
/**
|
|
200
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Any CSS colour, for the launcher, the buttons and the assistant's orb. The
|
|
201
|
+
* words on it are white or near-black, whichever reads. Default null: ink,
|
|
202
|
+
* which is a black button on light and a white one on dark.
|
|
203
|
+
*/
|
|
204
|
+
accent?: string | null;
|
|
201
205
|
/** Offer screenshot / recording / file attach in the widget. Default true. */
|
|
202
206
|
attachments?: boolean;
|
|
203
207
|
/**
|
|
@@ -462,6 +466,25 @@ export interface MyReport {
|
|
|
462
466
|
project: string;
|
|
463
467
|
workspace: string;
|
|
464
468
|
attachments: number;
|
|
469
|
+
/** Their score for how it was handled, 1–5, or null when they have not rated it. */
|
|
470
|
+
rating: number | null;
|
|
471
|
+
/** Settled, on a project that asks for ratings — so a list can say "rate it". */
|
|
472
|
+
ratingAsked: boolean;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* A reporter's own rating of how a settled report was handled.
|
|
477
|
+
*
|
|
478
|
+
* `current` is false when the report has been reopened and settled again since
|
|
479
|
+
* it was given — the rating stands, but it was for an earlier fix.
|
|
480
|
+
*/
|
|
481
|
+
export interface ReportRating {
|
|
482
|
+
/** 1–5, five best. */
|
|
483
|
+
score: number;
|
|
484
|
+
comment: string | null;
|
|
485
|
+
ratedAt: string;
|
|
486
|
+
via: 'widget' | 'portal' | 'email';
|
|
487
|
+
current: boolean;
|
|
465
488
|
}
|
|
466
489
|
|
|
467
490
|
/** A report of this browser's changed status. */
|
|
@@ -552,6 +575,18 @@ export interface ReportDetail {
|
|
|
552
575
|
attachments: { type: string; mime: string; createdAt: string; url: string | null }[];
|
|
553
576
|
/** Every step from filing to now, oldest first, both sides' messages included. */
|
|
554
577
|
timeline: ReportTimelineEntry[];
|
|
578
|
+
/** When it was last settled, or null while it is open. */
|
|
579
|
+
resolvedAt: string | null;
|
|
580
|
+
/** Their answer to "does the fix work?", or null when they have not given one. */
|
|
581
|
+
verification: 'CONFIRMED' | 'STILL_HAPPENING' | null;
|
|
582
|
+
/** Their rating of how it was handled, or null. See rate(). */
|
|
583
|
+
rating: ReportRating | null;
|
|
584
|
+
/**
|
|
585
|
+
* Whether a rating is being asked for: the report is settled and the project
|
|
586
|
+
* has not switched ratings off. The widget draws its stars only when this is
|
|
587
|
+
* true; an integration's own view should do the same.
|
|
588
|
+
*/
|
|
589
|
+
ratingAsked: boolean;
|
|
555
590
|
/**
|
|
556
591
|
* The same figure they were given when they filed it, so coming back to check
|
|
557
592
|
* is not a worse experience than not checking. Null on a settled report — the
|
|
@@ -787,6 +822,12 @@ export declare class BugCatchClient {
|
|
|
787
822
|
endCall(): this;
|
|
788
823
|
/** Tell the team whether their fix worked. STILL_HAPPENING re-sends this browser's context. */
|
|
789
824
|
verifyFix(bugId: string, feedback: { result: 'CONFIRMED' | 'STILL_HAPPENING'; resolvedAt: string; note?: string }): Promise<{ verification: string; status: string }>;
|
|
825
|
+
/**
|
|
826
|
+
* Rate how a settled report was handled, 1–5, five best. Rating again
|
|
827
|
+
* replaces the earlier score. Rejects while the report is open, or on a
|
|
828
|
+
* project that does not ask for ratings (`ratingAsked` is false).
|
|
829
|
+
*/
|
|
830
|
+
rate(bugId: string, rating: { score: 1 | 2 | 3 | 4 | 5; comment?: string }): Promise<ReportRating>;
|
|
790
831
|
/** One report with its full timeline, or null if this browser cannot prove it filed it. */
|
|
791
832
|
myReport(bugId: string): Promise<ReportDetail | null>;
|
|
792
833
|
/**
|
package/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { mountWidget } from './widget.js';
|
|
|
6
6
|
import { uploadAttachment, normalizeMime, REPORT_MIMES } from './capture.js';
|
|
7
7
|
import {
|
|
8
8
|
watchUpdates, createUpdateNotifier, fetchMyReports, fetchReport, writeToken, readToken,
|
|
9
|
-
clearToken, scopeReports, sendMessage, verifyFix, callKindsForProject,
|
|
9
|
+
clearToken, scopeReports, sendMessage, verifyFix, rateReport, callKindsForProject,
|
|
10
10
|
reportByCall, liveCallAvailable, requestLiveCall, giveUpLiveCall,
|
|
11
11
|
answerIncomingCall, declineIncomingCall,
|
|
12
12
|
readUnread, rememberUpdate, forgetUpdate, forgetReportUpdates, forgetAllUpdates,
|
|
@@ -49,7 +49,9 @@ const DEFAULTS = {
|
|
|
49
49
|
widgetPosition: 'bottom-right',
|
|
50
50
|
// 'auto' follows the visitor's system setting; 'light' / 'dark' pin it.
|
|
51
51
|
theme: 'auto',
|
|
52
|
-
|
|
52
|
+
// null is ink: black buttons on light, white on dark (accent.js). Any CSS
|
|
53
|
+
// colour makes the buttons, the launcher and the assistant's orb that colour.
|
|
54
|
+
accent: null,
|
|
53
55
|
// Draw what the project's team is telling visitors — written on the Widget
|
|
54
56
|
// announcements settings page — in the panel, and beside the button for the
|
|
55
57
|
// ones written for that. false draws nothing; announcements() still answers,
|
|
@@ -456,6 +458,28 @@ class BugCatchClient {
|
|
|
456
458
|
this.notifier.render(unread);
|
|
457
459
|
}
|
|
458
460
|
|
|
461
|
+
/**
|
|
462
|
+
* Tell the team whether their fix worked. STILL_HAPPENING reopens the report
|
|
463
|
+
* and re-sends this browser's context with it.
|
|
464
|
+
*/
|
|
465
|
+
async verifyFix(bugId, { result, resolvedAt, note }) {
|
|
466
|
+
const context = result === 'STILL_HAPPENING' ? await this.#payload({}) : {};
|
|
467
|
+
return verifyFix({ apiBase: this.config.apiBase, bugId, result, resolvedAt, note, metadata: context.metadata, logs: context.logs });
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Rate how a settled report was handled, 1 to 5, five best.
|
|
472
|
+
*
|
|
473
|
+
* await BugCatch.rate(report.id, { score: 4, comment: 'Quick, thanks' });
|
|
474
|
+
*
|
|
475
|
+
* Only once the report is RESOLVED or CLOSED, on a project that asks for
|
|
476
|
+
* ratings — `myReport(id).ratingAsked` says whether it does. Rating again
|
|
477
|
+
* replaces the earlier score rather than adding a second one.
|
|
478
|
+
*/
|
|
479
|
+
async rate(bugId, { score, comment } = {}) {
|
|
480
|
+
return rateReport({ apiBase: this.config.apiBase, bugId, score, comment });
|
|
481
|
+
}
|
|
482
|
+
|
|
459
483
|
/**
|
|
460
484
|
* One report in full, with its timeline — every step from filing to now.
|
|
461
485
|
*
|
|
@@ -464,11 +488,6 @@ class BugCatchClient {
|
|
|
464
488
|
*
|
|
465
489
|
* Resolves to null when this browser cannot prove it filed that report.
|
|
466
490
|
*/
|
|
467
|
-
async verifyFix(bugId, { result, resolvedAt, note }) {
|
|
468
|
-
const context = result === 'STILL_HAPPENING' ? await this.#payload({}) : {};
|
|
469
|
-
return verifyFix({ apiBase: this.config.apiBase, bugId, result, resolvedAt, note, metadata: context.metadata, logs: context.logs });
|
|
470
|
-
}
|
|
471
|
-
|
|
472
491
|
async myReport(bugId) {
|
|
473
492
|
const report = await fetchReport({ apiBase: this.config.apiBase, bugId });
|
|
474
493
|
this.#notePhase(bugId, report?.assistant);
|
package/src/panel/icons.js
CHANGED
|
@@ -17,6 +17,8 @@ export const retry = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
|
17
17
|
// The assistant's mark: a four-point spark, drawn wherever it speaks.
|
|
18
18
|
export const sparkle = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.5c.5 4.6 2.9 7 7.5 7.5v.1c-4.6.5-7 2.9-7.5 7.5h-.1c-.5-4.6-2.9-7-7.5-7.5V10c4.6-.5 7-2.9 7.5-7.5z"/><path d="M18.6 15.2c.2 1.9 1.2 2.9 3.1 3.1v.1c-1.9.2-2.9 1.2-3.1 3.1h-.1c-.2-1.9-1.2-2.9-3.1-3.1v-.1c1.9-.2 2.9-1.2 3.1-3.1z" opacity=".7"/></svg>';
|
|
19
19
|
export const chevronRight = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 6l6 6-6 6"/></svg>';
|
|
20
|
+
// A rating star. Filled; the empty and chosen states are colour, set in CSS.
|
|
21
|
+
export const star = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.9l2.75 5.82 6.37.76-4.7 4.37 1.24 6.3L12 17.03l-5.66 3.12 1.24-6.3-4.7-4.37 6.37-.76z"/></svg>';
|
|
20
22
|
export const inbox = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 13.5l2.6-7.2A1.5 1.5 0 0 1 7.5 5.3h9a1.5 1.5 0 0 1 1.4 1L20.5 13.5V18a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 18z"/><path d="M3.5 13.5h4.8l1.2 2.2h5l1.2-2.2h4.8"/></svg>';
|
|
21
23
|
|
|
22
24
|
/* The sphoro lockup, as outlines rather than text.
|
package/src/panel/index.js
CHANGED
|
@@ -44,10 +44,11 @@ import { mountThread } from './thread.js';
|
|
|
44
44
|
* to load code of its own.
|
|
45
45
|
*/
|
|
46
46
|
export function openPanel({
|
|
47
|
-
root, host, dock, client, onClose, preloadCall = () => {},
|
|
47
|
+
root, host, dock, client, onClose, preloadCall = () => {}, themeControls = null,
|
|
48
48
|
}) {
|
|
49
49
|
addStyles(root);
|
|
50
50
|
const cfg = client.config;
|
|
51
|
+
const themeLabel = () => themeControls?.label() || '';
|
|
51
52
|
|
|
52
53
|
const scrim = document.createElement('div');
|
|
53
54
|
// The panel opens out of whichever corner the dock is in, clearing it by the
|
|
@@ -59,8 +60,11 @@ export function openPanel({
|
|
|
59
60
|
<div class="panel" role="dialog" aria-modal="true" aria-labelledby="bc-h">
|
|
60
61
|
${headHtml({
|
|
61
62
|
id: 'bc-h',
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
// Not "Tell us what went wrong": that is the first field's own label,
|
|
64
|
+
// a few pixels below. The header names the errand the launcher named.
|
|
65
|
+
title: 'Report a bug',
|
|
66
|
+
sub: 'We attach the page details for you.',
|
|
67
|
+
theme: themeLabel(),
|
|
64
68
|
})}
|
|
65
69
|
<div class="body">
|
|
66
70
|
<div class="slot" role="alert"></div>
|
|
@@ -133,6 +137,7 @@ export function openPanel({
|
|
|
133
137
|
say,
|
|
134
138
|
close,
|
|
135
139
|
preloadCall,
|
|
140
|
+
themeLabel,
|
|
136
141
|
onDestroy: (fn) => { cleanups.push(fn); },
|
|
137
142
|
// compose.js
|
|
138
143
|
refreshSendState: null,
|
|
@@ -218,6 +223,15 @@ export function openPanel({
|
|
|
218
223
|
document.addEventListener('keydown', onKeydown, true);
|
|
219
224
|
scrim.addEventListener('click', (e) => { if (e.target === scrim && !ctx.sending) close(); });
|
|
220
225
|
$('[data-act="close"]').addEventListener('click', close);
|
|
226
|
+
// On the scrim rather than the button: every screen draws its own header, and
|
|
227
|
+
// the scrim is the one element all of them are drawn inside.
|
|
228
|
+
scrim.addEventListener('click', (e) => {
|
|
229
|
+
const btn = e.target.closest('[data-act="theme"]');
|
|
230
|
+
if (!btn || !themeControls) return;
|
|
231
|
+
const next = themeControls.cycle();
|
|
232
|
+
btn.title = `Theme: ${next.label}`;
|
|
233
|
+
btn.setAttribute('aria-label', `Theme: ${next.label}. Change theme`);
|
|
234
|
+
});
|
|
221
235
|
|
|
222
236
|
/** The banner at the top of the form: an error, or with `info`, a notice. */
|
|
223
237
|
function say(text, kind = 'err') {
|
package/src/panel/messages.js
CHANGED
|
@@ -100,7 +100,7 @@ export function messageHtml(message, state = '') {
|
|
|
100
100
|
let avatar = '';
|
|
101
101
|
if (automated) {
|
|
102
102
|
who = '<span class="msg-who auto">Assistant <span class="tag">AI</span></span>';
|
|
103
|
-
avatar = `<span class="av bot" aria-hidden="true"
|
|
103
|
+
avatar = `<span class="av bot orb" aria-hidden="true"></span>`;
|
|
104
104
|
} else if (!mine) {
|
|
105
105
|
const name = message.author || 'Support team';
|
|
106
106
|
who = `<span class="msg-who">${escapeHtml(name)}</span>`;
|
|
@@ -140,7 +140,7 @@ export function messageHtml(message, state = '') {
|
|
|
140
140
|
export function typingHtml() {
|
|
141
141
|
return `
|
|
142
142
|
<div class="typing-row">
|
|
143
|
-
<span class="av bot" aria-hidden="true"
|
|
143
|
+
<span class="av bot orb" aria-hidden="true"></span>
|
|
144
144
|
<span class="msg-who auto">Assistant <span class="tag">AI</span></span>
|
|
145
145
|
<div class="bubble typing" role="img" aria-label="The assistant is working on it"><i></i><i></i><i></i></div>
|
|
146
146
|
</div>`;
|
package/src/panel/picks.js
CHANGED
|
@@ -18,14 +18,18 @@ export function mountPicks(ctx) {
|
|
|
18
18
|
function drawPicks() {
|
|
19
19
|
if (!agentIssues.length) { picks.hidden = true; picks.innerHTML = ''; return; }
|
|
20
20
|
picks.hidden = false;
|
|
21
|
+
// Chips, not cards: they are a shortcut in front of the form, and three
|
|
22
|
+
// two-line cards pushed the form itself off the first screen. The hint is
|
|
23
|
+
// still there — as the chip's tooltip and its accessible description.
|
|
21
24
|
picks.innerHTML = `
|
|
22
25
|
<p class="picks-h">Get an answer right away</p>
|
|
23
26
|
<div class="picks-grid" role="group" aria-label="Common questions">
|
|
24
|
-
${agentIssues.map((issue) => `
|
|
27
|
+
${agentIssues.map((issue, i) => `
|
|
25
28
|
<button type="button" class="pick" data-pick="${escapeHtml(issue.key)}"
|
|
29
|
+
${issue.hint ? `title="${escapeHtml(issue.hint)}" aria-describedby="bc-pick-h${i}"` : ''}
|
|
26
30
|
aria-pressed="${pickedIssue?.key === issue.key}">
|
|
27
31
|
<span class="t">${escapeHtml(issue.title)}</span>
|
|
28
|
-
${issue.hint ? `<span class="h">${escapeHtml(issue.hint)}</span>` : ''}
|
|
32
|
+
${issue.hint ? `<span class="h sr-only" id="bc-pick-h${i}">${escapeHtml(issue.hint)}</span>` : ''}
|
|
29
33
|
</button>`).join('')}
|
|
30
34
|
</div>
|
|
31
35
|
${pickedIssue ? `<p class="picks-note">${ICONS.check}<span>Our assistant will check your account and reply here. Add anything else below, then send.</span></p>` : ''}`;
|
package/src/panel/refs.js
CHANGED
|
@@ -137,7 +137,7 @@ export function mountReferences(ctx) {
|
|
|
137
137
|
const many = kinds.length > 6;
|
|
138
138
|
refsBox.innerHTML = `
|
|
139
139
|
<div class="ask-head">
|
|
140
|
-
<span class="avatar" aria-hidden="true"
|
|
140
|
+
<span class="avatar orb" aria-hidden="true"></span>
|
|
141
141
|
<div>
|
|
142
142
|
<strong>${escapeHtml(head[0])}</strong>
|
|
143
143
|
<p class="why">${escapeHtml(head[1])}</p>
|
package/src/panel/render.js
CHANGED
|
@@ -6,18 +6,26 @@
|
|
|
6
6
|
import * as ICONS from './icons.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* The panel's header, on every screen. The assistant's
|
|
9
|
+
* The panel's header, on every screen. The assistant's orb carries the
|
|
10
10
|
* identity; `back` adds the way out of a report to the list of them.
|
|
11
|
+
*
|
|
12
|
+
* `theme` is the name of the theme in force ("Light", "Dark", "System
|
|
13
|
+
* default"); given one, the header carries the button that moves to the next.
|
|
14
|
+
* The button holds all three glyphs and the stylesheet shows the one matching
|
|
15
|
+
* the host element's class, so it is right on every screen without anyone
|
|
16
|
+
* redrawing it. Its press is handled once, on the scrim (index.js).
|
|
11
17
|
*/
|
|
12
|
-
export function headHtml({ title, sub, id = '', back = false }) {
|
|
18
|
+
export function headHtml({ title, sub, id = '', back = false, theme = '' }) {
|
|
13
19
|
return `
|
|
14
20
|
<div class="head">
|
|
15
21
|
${back ? `<button class="icon-btn back" type="button" data-act="back" aria-label="Back to your reports">${ICONS.back}</button>` : ''}
|
|
16
|
-
<
|
|
22
|
+
<span class="orb mark" aria-hidden="true"></span>
|
|
17
23
|
<div class="titles">
|
|
18
24
|
<h2${id ? ` id="${id}"` : ''}>${escapeHtml(title)}</h2>
|
|
19
25
|
<p>${escapeHtml(sub)}</p>
|
|
20
26
|
</div>
|
|
27
|
+
${theme ? `<button class="icon-btn theme" type="button" data-act="theme" title="Theme: ${escapeHtml(theme)}"
|
|
28
|
+
aria-label="Theme: ${escapeHtml(theme)}. Change theme"><span class="m-light">${ICONS.sun}</span><span class="m-dark">${ICONS.moon}</span><span class="m-auto">${ICONS.auto}</span></button>` : ''}
|
|
21
29
|
<button class="icon-btn" type="button" data-act="close" aria-label="Close">${ICONS.close}</button>
|
|
22
30
|
</div>`;
|
|
23
31
|
}
|