@sphoro/bugcatch-web-sdk 0.18.0 → 0.20.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 +51 -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 +1337 -1067
- package/dist/bugcatch-panel.es.js.map +1 -1
- package/dist/bugcatch-panel.umd.js +427 -315
- package/dist/bugcatch-panel.umd.js.map +1 -1
- package/dist/bugcatch.standalone.umd.js +553 -429
- 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 +52 -2
- package/src/index.js +26 -7
- package/src/panel/call.js +3 -2
- package/src/panel/compose.js +7 -3
- package/src/panel/icons.js +2 -0
- package/src/panel/index.js +26 -3
- package/src/panel/messages.js +2 -2
- package/src/panel/picks.js +6 -2
- package/src/panel/rate-due.js +155 -0
- package/src/panel/rating.js +87 -0
- package/src/panel/refs.js +1 -1
- package/src/panel/render.js +11 -3
- package/src/panel/signin.js +1 -1
- package/src/panel/styles.js +270 -186
- package/src/panel/thread.js +48 -7
- package/src/updates.js +77 -27
- package/src/widget.js +160 -141
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// "How did we do?" — five stars and, after them, an optional note. Drawn under
|
|
2
|
+
// a settled report's conversation (thread.js) and above the form on the first
|
|
3
|
+
// screen (rate-due.js), so both ask the same question the same way.
|
|
4
|
+
|
|
5
|
+
import * as ICONS from './icons.js';
|
|
6
|
+
import { escapeHtml } from './render.js';
|
|
7
|
+
|
|
8
|
+
/** What a drawn rating box was drawn from, so a refresh can tell if it moved. */
|
|
9
|
+
export function ratingKey(rating) {
|
|
10
|
+
return JSON.stringify(rating ? [rating.score, rating.current, rating.comment || null] : null);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Draw the stars into `box` for `report`, with the reader's `rating` so far.
|
|
15
|
+
*
|
|
16
|
+
* One tap sends the score; a note is offered after it rather than before,
|
|
17
|
+
* because a form that asks for a sentence first is a form people close, and
|
|
18
|
+
* the score is the part that counts. `onRated` hears each saved rating and
|
|
19
|
+
* `onError` each refusal, for a caller that holds something until it lands.
|
|
20
|
+
*/
|
|
21
|
+
export function drawRating(box, {
|
|
22
|
+
client, report, rating, justRated = false, question = null, onRated = null, onError = null,
|
|
23
|
+
}) {
|
|
24
|
+
box.dataset.key = ratingKey(rating);
|
|
25
|
+
// A rating given for an earlier fix stands, but it is not this fix's: the
|
|
26
|
+
// stars start empty and the sentence says what the old score was.
|
|
27
|
+
const shown = rating?.current ? rating.score : 0;
|
|
28
|
+
const asked = !rating
|
|
29
|
+
? (question || 'How did we do?')
|
|
30
|
+
: !rating.current
|
|
31
|
+
? `You rated an earlier fix ${rating.score} out of 5. How did this one go?`
|
|
32
|
+
: justRated
|
|
33
|
+
? 'Thanks — the team will see that.'
|
|
34
|
+
: `You rated this ${rating.score} out of 5.`;
|
|
35
|
+
const askNote = justRated && rating?.current && !rating.comment;
|
|
36
|
+
box.innerHTML = `
|
|
37
|
+
<p class="fr-q">${escapeHtml(asked)}</p>
|
|
38
|
+
<div class="stars" role="group" aria-label="Rate how this was handled, 1 to 5">${[1, 2, 3, 4, 5].map((n) => `
|
|
39
|
+
<button type="button" data-score="${n}" aria-pressed="${n === shown}" aria-label="${n} out of 5${n === 1 ? ', poor' : n === 5 ? ', great' : ''}" class="${n <= shown ? 'on' : ''}">${ICONS.star}</button>`).join('')}
|
|
40
|
+
</div>
|
|
41
|
+
${rating?.current && rating.comment ? `<p class="fr-said">“${escapeHtml(rating.comment)}”</p>` : ''}
|
|
42
|
+
${askNote ? `
|
|
43
|
+
<div class="fr-note">
|
|
44
|
+
<textarea maxlength="2000" aria-label="Add a note to your rating" placeholder="${rating.score <= 2 ? 'What went wrong? (optional)' : 'Anything to add? (optional)'}"></textarea>
|
|
45
|
+
<button type="button" data-act="rating-note">Add note</button>
|
|
46
|
+
</div>` : ''}
|
|
47
|
+
${rating ? '' : '<p class="fr-hint">1 is poor, 5 is great.</p>'}
|
|
48
|
+
<p class="feedback-status" role="status"></p>`;
|
|
49
|
+
|
|
50
|
+
const stars = [...box.querySelectorAll('[data-score]')];
|
|
51
|
+
const paint = (upTo) => stars.forEach((b) => b.classList.toggle('on', Number(b.dataset.score) <= upTo));
|
|
52
|
+
const lock = (on) => box.querySelectorAll('button, textarea').forEach((el) => { el.disabled = on; });
|
|
53
|
+
const status = box.querySelector('.feedback-status');
|
|
54
|
+
|
|
55
|
+
async function send(score, comment) {
|
|
56
|
+
lock(true);
|
|
57
|
+
status.textContent = '';
|
|
58
|
+
try {
|
|
59
|
+
const saved = await client.rate(report.id, { score, comment });
|
|
60
|
+
drawRating(box, {
|
|
61
|
+
client, report, rating: saved, justRated: true, question, onRated, onError,
|
|
62
|
+
});
|
|
63
|
+
box.querySelector('.fr-note textarea')?.focus();
|
|
64
|
+
onRated?.(saved);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
status.textContent = err.message;
|
|
67
|
+
lock(false);
|
|
68
|
+
paint(shown);
|
|
69
|
+
onError?.(err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const btn of stars) {
|
|
74
|
+
const n = Number(btn.dataset.score);
|
|
75
|
+
btn.addEventListener('mouseenter', () => paint(n));
|
|
76
|
+
btn.addEventListener('focus', () => paint(n));
|
|
77
|
+
btn.addEventListener('blur', () => paint(shown));
|
|
78
|
+
btn.addEventListener('click', () => send(n));
|
|
79
|
+
}
|
|
80
|
+
box.querySelector('.stars').addEventListener('mouseleave', () => paint(shown));
|
|
81
|
+
|
|
82
|
+
box.querySelector('[data-act="rating-note"]')?.addEventListener('click', () => {
|
|
83
|
+
const note = box.querySelector('.fr-note textarea').value.trim();
|
|
84
|
+
if (!note) { box.querySelector('.fr-note')?.remove(); return; }
|
|
85
|
+
send(rating.score, note);
|
|
86
|
+
});
|
|
87
|
+
}
|
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
|
}
|
package/src/panel/signin.js
CHANGED
|
@@ -37,7 +37,7 @@ export function mountSignIn(ctx) {
|
|
|
37
37
|
// The call offer is the same errand as the form, so it goes with it — and
|
|
38
38
|
// comes back only if the API had already said this project can take one.
|
|
39
39
|
if (shut) callBtn.hidden = true;
|
|
40
|
-
else if (ctx.callOffered()) callBtn.hidden = false;
|
|
40
|
+
else if (ctx.callOffered() && !ctx.mustRate()) callBtn.hidden = false;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function drawWhoami() {
|