@qaiddev/thumbs-embed 1.9.0 → 1.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.
- package/README.md +2 -3
- package/dist/chunks/{loader-XbJ2b8KE.js → loader-BVNyi56k.js} +337 -289
- package/dist/chunks/loader-BVNyi56k.js.map +1 -0
- package/dist/chunks/{modal-XxYobTKE.js → modal-DwwbWQ-F.js} +82 -75
- package/dist/chunks/modal-DwwbWQ-F.js.map +1 -0
- package/dist/chunks/{recording-Dx8AX-BC.js → recording-CcfOfIC7.js} +6 -6
- package/dist/chunks/recording-CcfOfIC7.js.map +1 -0
- package/dist/embed.d.ts +17 -1
- package/dist/index.js +443 -388
- package/dist/index.js.map +1 -1
- package/dist/loader.js +1 -1
- package/dist/modal.d.ts +8 -2
- package/dist/qaid.umd.cjs +12 -12
- package/dist/qaid.umd.cjs.map +1 -1
- package/dist/quest-launcher.d.ts +24 -4
- package/package.json +1 -1
- package/dist/chunks/loader-XbJ2b8KE.js.map +0 -1
- package/dist/chunks/modal-XxYobTKE.js.map +0 -1
- package/dist/chunks/recording-Dx8AX-BC.js.map +0 -1
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
import { c as u } from "./loader-
|
|
2
|
-
import { h as
|
|
3
|
-
import { T as
|
|
4
|
-
class
|
|
1
|
+
import { c as u } from "./loader-BVNyi56k.js";
|
|
2
|
+
import { h as s } from "./dom-DvdtcUS8.js";
|
|
3
|
+
import { T as h, a as p, F as b, D as f, E as g } from "./annotate-D-o8xCV9.js";
|
|
4
|
+
class w {
|
|
5
5
|
constructor(t) {
|
|
6
6
|
this.host = t;
|
|
7
7
|
}
|
|
8
8
|
host;
|
|
9
9
|
modalContainer = null;
|
|
10
10
|
backdrop = null;
|
|
11
|
+
/** Set once the message is sent, so closing does not PATCH over it. */
|
|
12
|
+
finalized = !1;
|
|
11
13
|
open() {
|
|
14
|
+
this.finalized = !1;
|
|
12
15
|
const t = this.host.ensureOverlayHost();
|
|
13
16
|
this.backdrop = document.createElement("div"), this.backdrop.className = "qaid-backdrop", this.backdrop.style.zIndex = String(this.host.config.zIndex + 2), this.backdrop.style.background = `rgba(0, 0, 0, ${this.host.config.backdropOpacity})`, this.backdrop.addEventListener("click", () => this.close()), this.host.applyVars(this.backdrop), this.host.isMobile ? this.showBottomSheet() : this.showPositionedModal(), t.appendChild(this.backdrop), document.addEventListener("keydown", this.host.boundKeyDown);
|
|
14
17
|
}
|
|
15
18
|
/** Escape / external close. Runs the finalize-PATCH + teardown. */
|
|
16
19
|
close() {
|
|
17
|
-
this.host.state === "MODAL_OPEN" && this.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
this.host.state === "MODAL_OPEN" && !this.finalized && (this.finalized = !0, this.host.whenFeedbackId().then((t) => {
|
|
21
|
+
t && fetch(`${this.host.config.endpoint}/${t}`, {
|
|
22
|
+
method: "PATCH",
|
|
23
|
+
headers: { "Content-Type": "application/json" },
|
|
24
|
+
body: JSON.stringify({ message: null })
|
|
25
|
+
}).catch((e) => console.error("Failed to finalize feedback:", e));
|
|
26
|
+
})), this.teardown(), this.host.resetFeedbackUi();
|
|
22
27
|
}
|
|
23
28
|
/** DOM/listener teardown only — no PATCH, no state reset (for embed destroy). */
|
|
24
29
|
destroy() {
|
|
@@ -28,18 +33,18 @@ class v {
|
|
|
28
33
|
this.host.closeDialogA11y(), this.modalContainer && (this.modalContainer.remove(), this.modalContainer = null), this.backdrop && (this.backdrop.remove(), this.backdrop = null), document.removeEventListener("keydown", this.host.boundKeyDown);
|
|
29
34
|
}
|
|
30
35
|
showBottomSheet() {
|
|
31
|
-
const t = this.host.ensureOverlayHost(),
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
const t = this.host.ensureOverlayHost(), e = document.createElement("div");
|
|
37
|
+
e.className = "qaid-bottom-sheet", e.style.zIndex = String(this.host.config.zIndex + 3), e.appendChild(
|
|
38
|
+
s(
|
|
34
39
|
"div",
|
|
35
40
|
{ class: "qaid-bottom-sheet-content" },
|
|
36
|
-
|
|
41
|
+
s("div", { class: "qaid-bottom-sheet-handle" }),
|
|
37
42
|
this.buildModalContent()
|
|
38
43
|
)
|
|
39
|
-
), this.host.applyVars(
|
|
44
|
+
), this.host.applyVars(e), t.appendChild(e), this.modalContainer = e, this.setupModalInteractions();
|
|
40
45
|
}
|
|
41
46
|
showPositionedModal() {
|
|
42
|
-
const t = this.host.ensureOverlayHost(), { modal:
|
|
47
|
+
const t = this.host.ensureOverlayHost(), { modal: e, arrow: i } = u(
|
|
43
48
|
this.host.selectedBounds,
|
|
44
49
|
window.innerWidth,
|
|
45
50
|
window.innerHeight,
|
|
@@ -51,11 +56,11 @@ class v {
|
|
|
51
56
|
viewportPadding: 16
|
|
52
57
|
}
|
|
53
58
|
);
|
|
54
|
-
this.modalContainer = document.createElement("div"), this.modalContainer.className = `qaid-modal-container qaid-${
|
|
55
|
-
const
|
|
56
|
-
|
|
59
|
+
this.modalContainer = document.createElement("div"), this.modalContainer.className = `qaid-modal-container qaid-${e.position}`, this.modalContainer.style.top = `${e.top}px`, this.modalContainer.style.left = `${e.left}px`, this.modalContainer.style.zIndex = String(this.host.config.zIndex + 3);
|
|
60
|
+
const o = document.createElement("div");
|
|
61
|
+
o.className = "qaid-modal-arrow", o.style.left = `${i.left}px`;
|
|
57
62
|
const a = document.createElement("div");
|
|
58
|
-
a.className = "qaid-modal-box", a.appendChild(this.buildModalContent()), this.modalContainer.appendChild(
|
|
63
|
+
a.className = "qaid-modal-box", a.appendChild(this.buildModalContent()), this.modalContainer.appendChild(o), this.modalContainer.appendChild(a), this.host.applyVars(this.modalContainer), t.appendChild(this.modalContainer), this.setupModalInteractions();
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* The modal's contents, built as nodes rather than an HTML string.
|
|
@@ -66,50 +71,50 @@ class v {
|
|
|
66
71
|
* markup, because they are documented as SVG/HTML strings.
|
|
67
72
|
*/
|
|
68
73
|
buildModalContent() {
|
|
69
|
-
const { config: t, uid:
|
|
74
|
+
const { config: t, uid: e } = this.host, i = t.text, o = this.host.feedbackData.feedbackType, a = o === "up", n = t.positiveIcon || h, d = t.negativeIcon || p, l = t.buttonClass ? `qaid-type-toggle qaid-type-toggle-custom ${t.buttonClass} ${a ? "qaid-btn-up" : "qaid-btn-down"}` : `qaid-type-toggle ${a ? "qaid-type-up" : "qaid-type-down"}`, r = a ? "Feedback type: positive" : "Feedback type: negative", m = o === "neutral" ? s("span", {
|
|
70
75
|
class: "qaid-type-static",
|
|
71
76
|
html: t.feedbackIcon || b,
|
|
72
77
|
attrs: { "aria-hidden": "true" }
|
|
73
|
-
}) :
|
|
78
|
+
}) : s("button", {
|
|
74
79
|
class: l,
|
|
75
80
|
html: a ? n : d,
|
|
76
81
|
attrs: {
|
|
77
82
|
type: "button",
|
|
78
83
|
title: "Click to switch",
|
|
79
84
|
"aria-pressed": String(a),
|
|
80
|
-
"aria-label":
|
|
85
|
+
"aria-label": r
|
|
81
86
|
}
|
|
82
87
|
}), c = document.createDocumentFragment();
|
|
83
88
|
return c.append(
|
|
84
|
-
|
|
89
|
+
s(
|
|
85
90
|
"div",
|
|
86
91
|
{ class: "qaid-modal-header" },
|
|
87
92
|
m,
|
|
88
|
-
|
|
93
|
+
s(
|
|
89
94
|
"div",
|
|
90
95
|
{ class: "qaid-modal-header-text" },
|
|
91
|
-
|
|
96
|
+
s("h3", {
|
|
92
97
|
class: "qaid-modal-title",
|
|
93
|
-
text:
|
|
94
|
-
attrs: { id: `qaid-modal-title-${
|
|
98
|
+
text: i.modalTitle,
|
|
99
|
+
attrs: { id: `qaid-modal-title-${e}` }
|
|
95
100
|
}),
|
|
96
|
-
|
|
101
|
+
s("p", {
|
|
97
102
|
class: "qaid-modal-subtitle",
|
|
98
|
-
text:
|
|
99
|
-
attrs: { id: `qaid-modal-subtitle-${
|
|
103
|
+
text: i.modalSubtitle,
|
|
104
|
+
attrs: { id: `qaid-modal-subtitle-${e}` }
|
|
100
105
|
})
|
|
101
106
|
)
|
|
102
107
|
),
|
|
103
|
-
|
|
108
|
+
s("textarea", {
|
|
104
109
|
class: "qaid-textarea",
|
|
105
|
-
attrs: { "aria-label":
|
|
110
|
+
attrs: { "aria-label": i.modalSubtitle, placeholder: i.placeholder }
|
|
106
111
|
}),
|
|
107
|
-
|
|
112
|
+
s(
|
|
108
113
|
"div",
|
|
109
114
|
{ class: "qaid-btn-row" },
|
|
110
|
-
|
|
115
|
+
s("button", {
|
|
111
116
|
class: "qaid-btn-submit",
|
|
112
|
-
text:
|
|
117
|
+
text: i.skipButton,
|
|
113
118
|
attrs: { type: "button" }
|
|
114
119
|
})
|
|
115
120
|
)
|
|
@@ -120,47 +125,49 @@ class v {
|
|
|
120
125
|
labelledbyId: `qaid-modal-title-${this.host.uid}`,
|
|
121
126
|
describedbyId: `qaid-modal-subtitle-${this.host.uid}`
|
|
122
127
|
});
|
|
123
|
-
const t = this.modalContainer.querySelector(".qaid-textarea"),
|
|
128
|
+
const t = this.modalContainer.querySelector(".qaid-textarea"), e = this.modalContainer.querySelector(".qaid-btn-submit");
|
|
124
129
|
t && (setTimeout(() => t.focus(), 100), t.addEventListener("input", () => {
|
|
125
|
-
|
|
126
|
-
})),
|
|
127
|
-
const
|
|
128
|
-
this.submitMessage(
|
|
130
|
+
e && (e.textContent = t.value.trim() ? this.host.config.text.submitButton : this.host.config.text.skipButton);
|
|
131
|
+
})), e && e.addEventListener("click", () => {
|
|
132
|
+
const o = t?.value.trim() || null;
|
|
133
|
+
this.submitMessage(o);
|
|
129
134
|
});
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
this.host.feedbackData.feedbackType =
|
|
134
|
-
const a = this.host.config.positiveIcon ||
|
|
135
|
-
|
|
136
|
-
const d =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
135
|
+
const i = this.modalContainer.querySelector(".qaid-type-toggle");
|
|
136
|
+
i && i.addEventListener("click", () => {
|
|
137
|
+
const o = this.host.feedbackData.feedbackType === "up" ? "down" : "up";
|
|
138
|
+
this.host.feedbackData.feedbackType = o, this.host.config.buttonClass ? (i.classList.toggle("qaid-btn-up", o === "up"), i.classList.toggle("qaid-btn-down", o === "down")) : (i.classList.toggle("qaid-type-up", o === "up"), i.classList.toggle("qaid-type-down", o === "down"));
|
|
139
|
+
const a = this.host.config.positiveIcon || h, n = this.host.config.negativeIcon || p;
|
|
140
|
+
i.innerHTML = o === "up" ? a : n;
|
|
141
|
+
const d = o === "up" ? "Feedback type: positive" : "Feedback type: negative";
|
|
142
|
+
i.setAttribute("aria-pressed", String(o === "up")), i.setAttribute("aria-label", d), this.host.announceMsg(d), this.host.whenFeedbackId().then((l) => {
|
|
143
|
+
l && fetch(`${this.host.config.endpoint}/${l}`, {
|
|
144
|
+
method: "PATCH",
|
|
145
|
+
headers: { "Content-Type": "application/json" },
|
|
146
|
+
body: JSON.stringify({ feedbackType: o })
|
|
147
|
+
}).catch((r) => console.error("Failed to update feedback type:", r));
|
|
148
|
+
});
|
|
142
149
|
});
|
|
143
150
|
}
|
|
144
151
|
async submitMessage(t) {
|
|
145
|
-
let
|
|
146
|
-
|
|
152
|
+
let e = null;
|
|
153
|
+
this.finalized = !0;
|
|
154
|
+
const i = await this.host.whenFeedbackId();
|
|
155
|
+
if (i)
|
|
147
156
|
try {
|
|
148
|
-
const
|
|
157
|
+
const o = await fetch(`${this.host.config.endpoint}/${i}`, {
|
|
149
158
|
method: "PATCH",
|
|
150
159
|
headers: { "Content-Type": "application/json" },
|
|
151
160
|
body: JSON.stringify({ message: t })
|
|
152
161
|
});
|
|
153
|
-
|
|
154
|
-
} catch (
|
|
155
|
-
console.error("Failed to submit feedback message:",
|
|
162
|
+
e = o.ok, o.ok || console.error("Failed to submit feedback message: HTTP", o.status);
|
|
163
|
+
} catch (o) {
|
|
164
|
+
console.error("Failed to submit feedback message:", o), e = !1;
|
|
156
165
|
}
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
if (o === !1) {
|
|
166
|
+
if (e === !1) {
|
|
160
167
|
this.showResult(!1);
|
|
161
168
|
return;
|
|
162
169
|
}
|
|
163
|
-
if (
|
|
170
|
+
if (e && !this.host.config.hideConfirmation) {
|
|
164
171
|
this.showResult(!0);
|
|
165
172
|
return;
|
|
166
173
|
}
|
|
@@ -176,38 +183,38 @@ class v {
|
|
|
176
183
|
* goes in as text, never markup.
|
|
177
184
|
*/
|
|
178
185
|
showResult(t) {
|
|
179
|
-
const
|
|
180
|
-
if (!
|
|
186
|
+
const e = this.modalContainer?.querySelector(".qaid-modal-box") ?? this.modalContainer?.querySelector(".qaid-bottom-sheet-content") ?? this.modalContainer;
|
|
187
|
+
if (!e) {
|
|
181
188
|
this.close();
|
|
182
189
|
return;
|
|
183
190
|
}
|
|
184
|
-
const
|
|
191
|
+
const i = this.host.config.text, o = t ? i.confirmationTitle : i.errorTitle, a = t ? i.confirmationMessage : i.errorMessage, n = s("h3", {
|
|
185
192
|
class: "qaid-confirm-title",
|
|
186
|
-
text:
|
|
193
|
+
text: o,
|
|
187
194
|
attrs: { id: `qaid-confirm-title-${this.host.uid}`, tabindex: "-1" }
|
|
188
195
|
});
|
|
189
|
-
|
|
190
|
-
|
|
196
|
+
e.textContent = "", e.appendChild(
|
|
197
|
+
s(
|
|
191
198
|
"div",
|
|
192
199
|
{ class: t ? "qaid-confirm" : "qaid-confirm qaid-confirm-error" },
|
|
193
|
-
|
|
200
|
+
s("span", {
|
|
194
201
|
class: "qaid-confirm-icon",
|
|
195
202
|
html: t ? f : g,
|
|
196
203
|
attrs: { "aria-hidden": "true" }
|
|
197
204
|
}),
|
|
198
205
|
n,
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
s("p", { class: "qaid-confirm-message", text: a }),
|
|
207
|
+
s("button", {
|
|
201
208
|
class: "qaid-btn-submit qaid-confirm-close",
|
|
202
|
-
text:
|
|
209
|
+
text: i.confirmationClose,
|
|
203
210
|
attrs: { type: "button" },
|
|
204
211
|
on: { click: () => this.close() }
|
|
205
212
|
})
|
|
206
213
|
)
|
|
207
|
-
), requestAnimationFrame(() => n.focus()), this.host.announceMsg(`${
|
|
214
|
+
), requestAnimationFrame(() => n.focus()), this.host.announceMsg(`${o} ${a}`, !0);
|
|
208
215
|
}
|
|
209
216
|
}
|
|
210
217
|
export {
|
|
211
|
-
|
|
218
|
+
w as ModalController
|
|
212
219
|
};
|
|
213
|
-
//# sourceMappingURL=modal-
|
|
220
|
+
//# sourceMappingURL=modal-DwwbWQ-F.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal-DwwbWQ-F.js","sources":["../../src/modal.ts"],"sourcesContent":["/**\n * Feedback message modal — extracted from embed.ts into its own lazily-loaded\n * chunk. It's fetched the first time a message modal opens (after feedback is\n * submitted), and pre-warmed while the user is targeting an element.\n *\n * The controller owns the modal/backdrop DOM and the message PATCH flow, and\n * talks back to the embed through the narrow `ModalHost` interface.\n */\n\nimport { calculateModalAndArrowPosition } from \"./modal-positioning\";\nimport { h } from \"./dom\";\nimport {\n THUMBS_UP_ICON,\n THUMBS_DOWN_ICON,\n FEEDBACK_ICON,\n DONE_ICON,\n ERROR_ICON,\n} from \"./icons\";\nimport type {\n ResolvedFeedbackConfig,\n EmbedState,\n FeedbackData,\n SelectedBounds,\n} from \"./types\";\n\n/** The slice of the embed the modal needs. */\nexport interface ModalHost {\n readonly config: ResolvedFeedbackConfig;\n readonly uid: string;\n readonly isMobile: boolean;\n readonly state: EmbedState;\n readonly boundKeyDown: (e: KeyboardEvent) => void;\n /** Live references — the toggle mutates feedbackType; positioning reads bounds. */\n readonly feedbackData: FeedbackData;\n readonly selectedBounds: SelectedBounds;\n /**\n * The id of this feedback's record. The modal opens while the POST that\n * creates it is still in flight, so every PATCH waits on this; null when\n * the POST failed.\n */\n whenFeedbackId(): Promise<number | null>;\n ensureOverlayHost(): ShadowRoot;\n applyVars(el: HTMLElement): void;\n announceMsg(message: string, assertive?: boolean): void;\n openDialogA11y(\n container: HTMLElement,\n opts: { labelledbyId?: string; describedbyId?: string; label?: string }\n ): void;\n closeDialogA11y(): void;\n resetFeedbackUi(): void;\n}\n\nexport class ModalController {\n private modalContainer: HTMLDivElement | null = null;\n private backdrop: HTMLDivElement | null = null;\n /** Set once the message is sent, so closing does not PATCH over it. */\n private finalized = false;\n\n constructor(private host: ModalHost) {}\n\n open(): void {\n this.finalized = false;\n const root = this.host.ensureOverlayHost();\n\n // Create backdrop\n this.backdrop = document.createElement(\"div\");\n this.backdrop.className = \"qaid-backdrop\";\n this.backdrop.style.zIndex = String(this.host.config.zIndex + 2);\n this.backdrop.style.background = `rgba(0, 0, 0, ${this.host.config.backdropOpacity})`;\n this.backdrop.addEventListener(\"click\", () => this.close());\n this.host.applyVars(this.backdrop);\n\n if (this.host.isMobile) {\n this.showBottomSheet();\n } else {\n this.showPositionedModal();\n }\n\n root.appendChild(this.backdrop);\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n /** Escape / external close. Runs the finalize-PATCH + teardown. */\n close(): void {\n // If the modal is open and we're closing without submitting, still finalize.\n // Taken before the reset below, which starts the next feedback afresh.\n if (this.host.state === \"MODAL_OPEN\" && !this.finalized) {\n this.finalized = true;\n void this.host.whenFeedbackId().then((id) => {\n if (!id) return;\n fetch(`${this.host.config.endpoint}/${id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message: null }),\n }).catch((err) => console.error(\"Failed to finalize feedback:\", err));\n });\n }\n\n this.teardown();\n this.host.resetFeedbackUi();\n }\n\n /** DOM/listener teardown only — no PATCH, no state reset (for embed destroy). */\n destroy(): void {\n this.teardown();\n }\n\n private teardown(): void {\n this.host.closeDialogA11y();\n if (this.modalContainer) {\n this.modalContainer.remove();\n this.modalContainer = null;\n }\n if (this.backdrop) {\n this.backdrop.remove();\n this.backdrop = null;\n }\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n private showBottomSheet(): void {\n const root = this.host.ensureOverlayHost();\n\n const sheet = document.createElement(\"div\");\n sheet.className = \"qaid-bottom-sheet\";\n sheet.style.zIndex = String(this.host.config.zIndex + 3);\n\n sheet.appendChild(\n h(\n \"div\",\n { class: \"qaid-bottom-sheet-content\" },\n h(\"div\", { class: \"qaid-bottom-sheet-handle\" }),\n this.buildModalContent()\n )\n );\n\n this.host.applyVars(sheet);\n root.appendChild(sheet);\n this.modalContainer = sheet;\n\n this.setupModalInteractions();\n }\n\n private showPositionedModal(): void {\n const root = this.host.ensureOverlayHost();\n\n const { modal, arrow } = calculateModalAndArrowPosition(\n this.host.selectedBounds,\n window.innerWidth,\n window.innerHeight,\n {\n width: this.host.config.modalWidth,\n height: 280,\n arrowHeight: 12,\n gap: 8,\n viewportPadding: 16,\n }\n );\n\n this.modalContainer = document.createElement(\"div\");\n this.modalContainer.className = `qaid-modal-container qaid-${modal.position}`;\n this.modalContainer.style.top = `${modal.top}px`;\n this.modalContainer.style.left = `${modal.left}px`;\n this.modalContainer.style.zIndex = String(this.host.config.zIndex + 3);\n\n const arrowEl = document.createElement(\"div\");\n arrowEl.className = \"qaid-modal-arrow\";\n arrowEl.style.left = `${arrow.left}px`;\n\n const box = document.createElement(\"div\");\n box.className = \"qaid-modal-box\";\n box.appendChild(this.buildModalContent());\n\n this.modalContainer.appendChild(arrowEl);\n this.modalContainer.appendChild(box);\n this.host.applyVars(this.modalContainer);\n root.appendChild(this.modalContainer);\n\n this.setupModalInteractions();\n }\n\n /**\n * The modal's contents, built as nodes rather than an HTML string.\n *\n * Every piece of copy (title, subtitle, placeholder, button) goes in as\n * text, so a `<`, `&` or `\"` in a translation shows as written instead of\n * being parsed as markup or cutting an attribute short. Only the icons are\n * markup, because they are documented as SVG/HTML strings.\n */\n private buildModalContent(): DocumentFragment {\n const { config, uid } = this.host;\n const text = config.text;\n const type = this.host.feedbackData.feedbackType;\n const isUp = type === \"up\";\n const positiveIcon = config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = config.negativeIcon || THUMBS_DOWN_ICON;\n const toggleClass = config.buttonClass\n ? `qaid-type-toggle qaid-type-toggle-custom ${config.buttonClass} ${isUp ? \"qaid-btn-up\" : \"qaid-btn-down\"}`\n : `qaid-type-toggle ${isUp ? \"qaid-type-up\" : \"qaid-type-down\"}`;\n const toggleLabel = isUp ? \"Feedback type: positive\" : \"Feedback type: negative\";\n // Neutral (single-button) feedback has no sentiment to toggle — show a\n // static feedback icon in place of the up/down toggle.\n const header =\n type === \"neutral\"\n ? h(\"span\", {\n class: \"qaid-type-static\",\n html: config.feedbackIcon || FEEDBACK_ICON,\n attrs: { \"aria-hidden\": \"true\" },\n })\n : h(\"button\", {\n class: toggleClass,\n html: isUp ? positiveIcon : negativeIcon,\n attrs: {\n type: \"button\",\n title: \"Click to switch\",\n \"aria-pressed\": String(isUp),\n \"aria-label\": toggleLabel,\n },\n });\n\n const fragment = document.createDocumentFragment();\n fragment.append(\n h(\n \"div\",\n { class: \"qaid-modal-header\" },\n header,\n h(\n \"div\",\n { class: \"qaid-modal-header-text\" },\n h(\"h3\", {\n class: \"qaid-modal-title\",\n text: text.modalTitle,\n attrs: { id: `qaid-modal-title-${uid}` },\n }),\n h(\"p\", {\n class: \"qaid-modal-subtitle\",\n text: text.modalSubtitle,\n attrs: { id: `qaid-modal-subtitle-${uid}` },\n })\n )\n ),\n h(\"textarea\", {\n class: \"qaid-textarea\",\n attrs: { \"aria-label\": text.modalSubtitle, placeholder: text.placeholder },\n }),\n h(\n \"div\",\n { class: \"qaid-btn-row\" },\n h(\"button\", {\n class: \"qaid-btn-submit\",\n text: text.skipButton,\n attrs: { type: \"button\" },\n })\n )\n );\n return fragment;\n }\n\n private setupModalInteractions(): void {\n // Dialog semantics + focus trap + background inert\n this.host.openDialogA11y(this.modalContainer!, {\n labelledbyId: `qaid-modal-title-${this.host.uid}`,\n describedbyId: `qaid-modal-subtitle-${this.host.uid}`,\n });\n\n const textarea =\n this.modalContainer!.querySelector<HTMLTextAreaElement>(\".qaid-textarea\");\n const submitBtn =\n this.modalContainer!.querySelector<HTMLButtonElement>(\".qaid-btn-submit\");\n if (textarea) {\n // Auto-focus\n setTimeout(() => textarea.focus(), 100);\n\n // Update button text based on content\n textarea.addEventListener(\"input\", () => {\n if (submitBtn) {\n submitBtn.textContent = textarea.value.trim()\n ? this.host.config.text.submitButton\n : this.host.config.text.skipButton;\n }\n });\n }\n\n if (submitBtn) {\n submitBtn.addEventListener(\"click\", () => {\n const message = textarea?.value.trim() || null;\n this.submitMessage(message);\n });\n }\n\n // Handle feedback type toggle\n const typeToggle = this.modalContainer!.querySelector<HTMLButtonElement>(\".qaid-type-toggle\");\n if (typeToggle) {\n typeToggle.addEventListener(\"click\", () => {\n const newType = this.host.feedbackData.feedbackType === \"up\" ? \"down\" : \"up\";\n this.host.feedbackData.feedbackType = newType;\n\n // Update UI — use different classes depending on custom buttonClass\n if (this.host.config.buttonClass) {\n typeToggle.classList.toggle(\"qaid-btn-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-btn-down\", newType === \"down\");\n } else {\n typeToggle.classList.toggle(\"qaid-type-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-type-down\", newType === \"down\");\n }\n const positiveIcon = this.host.config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = this.host.config.negativeIcon || THUMBS_DOWN_ICON;\n typeToggle.innerHTML = newType === \"up\" ? positiveIcon : negativeIcon;\n\n // Update accessible state and announce the change\n const toggleLabel = newType === \"up\" ? \"Feedback type: positive\" : \"Feedback type: negative\";\n typeToggle.setAttribute(\"aria-pressed\", String(newType === \"up\"));\n typeToggle.setAttribute(\"aria-label\", toggleLabel);\n this.host.announceMsg(toggleLabel);\n\n // Update feedback on server, once the record exists\n void this.host.whenFeedbackId().then((id) => {\n if (!id) return;\n fetch(`${this.host.config.endpoint}/${id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ feedbackType: newType }),\n }).catch((err) => console.error(\"Failed to update feedback type:\", err));\n });\n });\n }\n }\n\n private async submitMessage(message: string | null): Promise<void> {\n // null = nothing was attempted (the initial POST never produced an id);\n // otherwise whether the server accepted the PATCH.\n let delivered: boolean | null = null;\n\n // Before the await: an Escape while the POST is still out must not send\n // a second, empty PATCH over this message.\n this.finalized = true;\n const id = await this.host.whenFeedbackId();\n if (id) {\n try {\n const response = await fetch(`${this.host.config.endpoint}/${id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message }),\n });\n // fetch resolves on any HTTP reply. A 4xx/5xx means the server\n // refused or lost the message, so it is not a success.\n delivered = response.ok;\n if (!response.ok) {\n console.error(\"Failed to submit feedback message: HTTP\", response.status);\n }\n } catch (error) {\n console.error(\"Failed to submit feedback message:\", error);\n delivered = false;\n }\n }\n\n // The message was lost. Closing quietly reads as success, so say so —\n // even with hideConfirmation, which only opts out of the success screen.\n if (delivered === false) {\n this.showResult(false);\n return;\n }\n\n // A submit that simply removes the modal reads as a failure — nothing\n // acknowledges that the message was sent. Show a success screen unless the\n // host opted out.\n //\n // With no feedbackId the initial POST never landed and there was nothing\n // to send the message to; that path closes exactly as it did before.\n if (delivered && !this.host.config.hideConfirmation) {\n this.showResult(true);\n return;\n }\n this.close();\n }\n\n /**\n * Replace the modal's contents with the outcome: a checkmark and a short\n * acknowledgement, or an error mark and a \"not sent\" notice.\n *\n * The submit button that had focus is gone by this point, so focus moves to\n * the heading (WCAG 2.4.3) and the message is announced. Mirrors the quests\n * embed's thank-you screen so the two products confirm the same way. Copy\n * goes in as text, never markup.\n */\n private showResult(delivered: boolean): void {\n const box = this.modalContainer?.querySelector(\".qaid-modal-box\")\n ?? this.modalContainer?.querySelector(\".qaid-bottom-sheet-content\")\n ?? this.modalContainer;\n\n if (!box) {\n // Nothing to draw into — never strand the user with an open modal.\n this.close();\n return;\n }\n\n const text = this.host.config.text;\n const heading = delivered ? text.confirmationTitle : text.errorTitle;\n const body = delivered ? text.confirmationMessage : text.errorMessage;\n\n const title = h(\"h3\", {\n class: \"qaid-confirm-title\",\n text: heading,\n attrs: { id: `qaid-confirm-title-${this.host.uid}`, tabindex: \"-1\" },\n });\n\n box.textContent = \"\";\n box.appendChild(\n h(\n \"div\",\n { class: delivered ? \"qaid-confirm\" : \"qaid-confirm qaid-confirm-error\" },\n h(\"span\", {\n class: \"qaid-confirm-icon\",\n html: delivered ? DONE_ICON : ERROR_ICON,\n attrs: { \"aria-hidden\": \"true\" },\n }),\n title,\n h(\"p\", { class: \"qaid-confirm-message\", text: body }),\n h(\"button\", {\n class: \"qaid-btn-submit qaid-confirm-close\",\n text: text.confirmationClose,\n attrs: { type: \"button\" },\n on: { click: () => this.close() },\n })\n )\n );\n\n // Focus after paint, matching the quests embed.\n requestAnimationFrame(() => title.focus());\n this.host.announceMsg(`${heading} ${body}`, true);\n }\n}\n"],"names":["ModalController","host","root","id","err","sheet","h","modal","arrow","calculateModalAndArrowPosition","arrowEl","box","config","uid","text","type","isUp","positiveIcon","THUMBS_UP_ICON","negativeIcon","THUMBS_DOWN_ICON","toggleClass","toggleLabel","header","FEEDBACK_ICON","fragment","textarea","submitBtn","message","typeToggle","newType","delivered","response","error","heading","body","title","DONE_ICON","ERROR_ICON"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { h as r } from "./dom-DvdtcUS8.js";
|
|
2
|
-
import { b as
|
|
2
|
+
import { b as v } from "./annotate-D-o8xCV9.js";
|
|
3
3
|
const g = 20, u = 4096;
|
|
4
4
|
function l(d) {
|
|
5
5
|
const t = typeof d == "string" ? d : JSON.stringify(d);
|
|
@@ -24,14 +24,14 @@ function R() {
|
|
|
24
24
|
s?.body && (a = l(s.body));
|
|
25
25
|
const h = await t.apply(window, [o, s]);
|
|
26
26
|
if (h.status >= 400) {
|
|
27
|
-
const
|
|
27
|
+
const f = await b(h);
|
|
28
28
|
p(d, {
|
|
29
29
|
url: n,
|
|
30
30
|
method: c.toUpperCase(),
|
|
31
31
|
status: h.status,
|
|
32
32
|
statusText: h.statusText,
|
|
33
33
|
requestBody: a,
|
|
34
|
-
responseBody:
|
|
34
|
+
responseBody: f,
|
|
35
35
|
timestamp: Date.now()
|
|
36
36
|
});
|
|
37
37
|
}
|
|
@@ -154,7 +154,7 @@ class m {
|
|
|
154
154
|
}
|
|
155
155
|
handleRedactPickClick(t) {
|
|
156
156
|
const i = t.target instanceof Element ? t.target : null;
|
|
157
|
-
if (!i ||
|
|
157
|
+
if (!i || v(i) || this.redactPickerRoot?.contains(i)) return;
|
|
158
158
|
t.preventDefault(), t.stopPropagation();
|
|
159
159
|
const e = this.redactPicks.indexOf(i);
|
|
160
160
|
e >= 0 ? this.redactPicks.splice(e, 1) : this.redactPicks.push(i);
|
|
@@ -196,7 +196,7 @@ class m {
|
|
|
196
196
|
attrs: { type: "button" },
|
|
197
197
|
on: { click: () => this.stopRecording() }
|
|
198
198
|
})
|
|
199
|
-
), this.host.applyVars(this.recordingIndicator),
|
|
199
|
+
), this.host.applyVars(this.recordingIndicator), t.appendChild(this.recordingIndicator);
|
|
200
200
|
}
|
|
201
201
|
updateRecordingTimer(t) {
|
|
202
202
|
if (!this.recordingIndicator) return;
|
|
@@ -318,4 +318,4 @@ class m {
|
|
|
318
318
|
export {
|
|
319
319
|
m as RecordingController
|
|
320
320
|
};
|
|
321
|
-
//# sourceMappingURL=recording-
|
|
321
|
+
//# sourceMappingURL=recording-CcfOfIC7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recording-CcfOfIC7.js","sources":["../../src/network-capture.ts","../../src/recording.ts"],"sourcesContent":["/**\n * Network capture utilities\n * Intercepts fetch() and XMLHttpRequest to capture 4xx/5xx errors\n */\n\nimport type { NetworkError } from \"./types\";\n\nconst MAX_ENTRIES = 20;\nconst MAX_BODY_SIZE = 4096; // 4KB\n\nexport interface NetworkCapture {\n errors: NetworkError[];\n restore: () => void;\n}\n\n/**\n * Truncate a body to max size. Callers must guard against null/undefined.\n */\nexport function truncateBody(body: unknown): string {\n const str = typeof body === \"string\" ? body : JSON.stringify(body);\n if (str.length > MAX_BODY_SIZE) {\n return str.slice(0, MAX_BODY_SIZE) + \"…[truncated]\";\n }\n return str;\n}\n\n/**\n * Add an error to the buffer, evicting oldest if at capacity\n */\nexport function addEntry(errors: NetworkError[], entry: NetworkError): void {\n if (errors.length >= MAX_ENTRIES) {\n errors.shift();\n }\n errors.push(entry);\n}\n\n/**\n * Read response body as text, with size limit\n */\nasync function safeReadBody(response: Response): Promise<string | undefined> {\n try {\n const text = await response.clone().text();\n return truncateBody(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Intercept fetch() and XMLHttpRequest to capture network errors (4xx/5xx)\n */\nexport function captureNetworkErrors(): NetworkCapture {\n const errors: NetworkError[] = [];\n const originalFetch = window.fetch;\n\n // Monkey-patch fetch\n window.fetch = async function (input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.toString() : input.url;\n const method = init?.method ?? (typeof input === \"object\" && \"method\" in input ? input.method : \"GET\");\n let requestBody: string | undefined;\n if (init?.body) {\n requestBody = truncateBody(init.body);\n }\n\n const response = await originalFetch.apply(window, [input, init]);\n\n if (response.status >= 400) {\n const responseBody = await safeReadBody(response);\n addEntry(errors, {\n url,\n method: method.toUpperCase(),\n status: response.status,\n statusText: response.statusText,\n requestBody,\n responseBody,\n timestamp: Date.now(),\n });\n }\n\n return response;\n };\n\n // Monkey-patch XMLHttpRequest\n const originalXHROpen = XMLHttpRequest.prototype.open;\n const originalXHRSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL, ...rest: unknown[]) {\n (this as XMLHttpRequest & { _qaid_method: string; _qaid_url: string })._qaid_method = method;\n (this as XMLHttpRequest & { _qaid_url: string })._qaid_url = typeof url === \"string\" ? url : url.toString();\n return originalXHROpen.apply(this, [method, url, ...rest] as Parameters<typeof originalXHROpen>);\n };\n\n XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) {\n const xhr = this as XMLHttpRequest & { _qaid_method: string; _qaid_url: string };\n const requestBody = body ? truncateBody(body) : undefined;\n\n xhr.addEventListener(\"load\", function () {\n if (xhr.status >= 400) {\n addEntry(errors, {\n url: xhr._qaid_url,\n method: xhr._qaid_method.toUpperCase(),\n status: xhr.status,\n statusText: xhr.statusText,\n requestBody,\n responseBody: truncateBody(xhr.responseText),\n timestamp: Date.now(),\n });\n }\n });\n\n return originalXHRSend.apply(this, [body]);\n };\n\n return {\n errors,\n restore: () => {\n window.fetch = originalFetch;\n XMLHttpRequest.prototype.open = originalXHROpen;\n XMLHttpRequest.prototype.send = originalXHRSend;\n },\n };\n}\n","/**\n * Video-recording subsystem — extracted from embed.ts so it lives in its own\n * lazily-loaded chunk. A thumbs-only visitor never downloads any of this; the\n * embed dynamically imports it the first time the record button is used (and\n * warms it on hover — see embed.ts prewarmVideo).\n *\n * The controller owns all recording state and UI (recorder, redaction picker,\n * indicator, preview, submit). It talks back to the embed through the narrow\n * `RecordingHost` interface, so embed internals stay private and the coupling\n * is explicit.\n */\n\nimport { h } from \"./dom\";\nimport { isEmbedElement } from \"./dom-utils\";\nimport { captureNetworkErrors, type NetworkCapture } from \"./network-capture\";\nimport type { VideoRecorder } from \"./video-capture\";\nimport type { ResolvedFeedbackConfig, EmbedState } from \"./types\";\nimport type { ConsoleCapture } from \"./console-capture\";\n\n/** The slice of the embed the recording subsystem needs. */\nexport interface RecordingHost {\n readonly config: ResolvedFeedbackConfig;\n readonly visitorId: string;\n readonly uid: string;\n readonly overlayShadowHost: HTMLDivElement | null;\n readonly consoleCapture: ConsoleCapture | null;\n readonly boundKeyDown: (e: KeyboardEvent) => void;\n readonly state: EmbedState;\n setState(state: EmbedState): void;\n ensureOverlayHost(): ShadowRoot;\n applyVars(el: HTMLElement): void;\n announceMsg(message: string, assertive?: boolean): void;\n openDialogA11y(\n container: HTMLElement,\n opts: { labelledbyId?: string; describedbyId?: string; label?: string }\n ): void;\n closeDialogA11y(): void;\n setButtonsDisabled(disabled: boolean): void;\n tryLaunchQuest(\n type: \"up\" | \"down\" | \"video\",\n feedbackId: number | string | null\n ): Promise<boolean>;\n}\n\nexport class RecordingController {\n private videoRecorder: VideoRecorder | null = null;\n private networkCapture: NetworkCapture | null = null;\n private recordedBlob: Blob | null = null;\n private recordingIndicator: HTMLDivElement | null = null;\n private videoPreview: HTMLDivElement | null = null;\n private isRecording = false;\n private isSendingVideo = false;\n\n // Pre-recording redaction picking.\n private redactPicks: Element[] = [];\n private redactPickerRoot: HTMLDivElement | null = null;\n private redactRafId = 0;\n private boundRedactClick = (e: MouseEvent): void => this.handleRedactPickClick(e);\n\n constructor(private host: RecordingHost) {}\n\n /** Escape-key handler; returns true when it consumed the key. */\n handleEscape(): boolean {\n if (this.isRecording) {\n void this.stopRecording();\n return true;\n }\n if (this.videoPreview) {\n this.cancelRecordingPreview();\n return true;\n }\n if (this.redactPickerRoot) {\n this.finishRedactionPicking(false);\n return true;\n }\n return false;\n }\n\n destroy(): void {\n if (this.redactRafId) cancelAnimationFrame(this.redactRafId);\n this.redactRafId = 0;\n document.removeEventListener(\"click\", this.boundRedactClick, true);\n if (this.redactPickerRoot) {\n this.redactPickerRoot.remove();\n this.redactPickerRoot = null;\n }\n this.redactPicks = [];\n this.cleanupRecording();\n this.removeVideoPreview();\n }\n\n async startRecording(redactionElements: Element[] = []): Promise<void> {\n // Don't start if already recording or in a targeting/modal flow.\n if (this.isRecording || this.host.state !== \"IDLE\") return;\n\n try {\n this.networkCapture = captureNetworkErrors();\n\n const { createVideoRecorder } = await import(\"./video-capture\");\n this.videoRecorder = createVideoRecorder({\n maxDuration: this.host.config.videoOptions.maxDuration,\n redactionElements,\n });\n\n this.videoRecorder.onTick((elapsed) => {\n this.updateRecordingTimer(elapsed);\n });\n\n // Handle unexpected stops (browser stop button, max duration).\n this.videoRecorder.onStop((blob) => {\n if (this.isRecording) {\n this.recordedBlob = blob;\n this.isRecording = false;\n this.host.announceMsg(\"Recording stopped\");\n this.removeRecordingIndicator();\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n this.host.setButtonsDisabled(false);\n\n if (blob && blob.size > 0) {\n this.showRecordingPreview();\n } else {\n this.cleanupRecording();\n }\n }\n });\n\n await this.videoRecorder.start();\n this.isRecording = true;\n this.host.announceMsg(\"Recording started\");\n this.host.setButtonsDisabled(true);\n this.showRecordingIndicator();\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n } catch (error) {\n // User denied screen share, shared a disallowed surface (window / whole\n // screen), the browser cannot blur the picked areas, or another error\n // occurred. Surface the tab-only and no-blur messages so neither is a\n // silent no-op. (Matched by text: video-capture is a separate lazy\n // chunk, so importing its constant here would merge the two.)\n if (error instanceof Error && /current tab|cannot blur/i.test(error.message)) {\n this.host.announceMsg(error.message);\n }\n this.cleanupRecording();\n }\n }\n\n private async stopRecording(): Promise<void> {\n try {\n this.recordedBlob = await this.videoRecorder!.stop();\n } catch {\n this.recordedBlob = null;\n }\n\n this.isRecording = false;\n this.host.announceMsg(\"Recording stopped\");\n this.removeRecordingIndicator();\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n\n if (this.recordedBlob && this.recordedBlob.size > 0) {\n this.showRecordingPreview();\n } else {\n this.cleanupRecording();\n }\n }\n\n /**\n * Pre-recording redaction picker. The user clicks page areas to blur; each\n * gets an outline that tracks its position, then \"Start recording\" hands the\n * picks to the recorder, which blurs their live bounding boxes so the\n * redaction follows the content as the page scrolls. \"Cancel\"/Escape aborts.\n */\n startPicking(): void {\n if (this.isRecording || this.host.state !== \"IDLE\") return;\n this.host.setState(\"REDACT_PICKING\");\n this.redactPicks = [];\n\n const root = this.host.ensureOverlayHost();\n\n // Layer holding the per-pick outlines (repositioned every frame).\n const boxHost = h(\"div\", { style: \"position:absolute;inset:0;\" });\n\n const container = h(\n \"div\",\n { class: \"qaid-redact-picker\", style: \"position:fixed;inset:0;pointer-events:none;\" },\n boxHost,\n // Control bar — the only pointer-interactive part of the overlay.\n h(\n \"div\",\n {\n style:\n \"position:fixed;left:50%;bottom:24px;transform:translateX(-50%);\" +\n \"display:flex;gap:12px;align-items:center;pointer-events:auto;\" +\n \"background:#0b1220;color:#fff;border:1px solid #ff6b6b;border-radius:9999px;\" +\n \"padding:10px 16px;font:600 13px system-ui,sans-serif;box-shadow:0 8px 30px rgba(0,0,0,.5);\",\n },\n h(\"span\", {\n text: \"Click sensitive areas to blur\",\n attrs: { \"data-qaid-redact-label\": \"\" },\n }),\n h(\"button\", {\n text: \"Start recording\",\n attrs: { type: \"button\" },\n style:\n \"cursor:pointer;border:0;border-radius:9999px;padding:8px 14px;background:#ff6b6b;color:#0b1220;font:inherit;\",\n on: { click: () => this.finishRedactionPicking(true) },\n }),\n h(\"button\", {\n text: \"Cancel\",\n attrs: { type: \"button\" },\n style: \"cursor:pointer;border:0;background:transparent;color:#9fb3c8;font:inherit;\",\n on: { click: () => this.finishRedactionPicking(false) },\n }),\n ),\n );\n\n root.appendChild(container);\n this.redactPickerRoot = container;\n\n // Keep the outlines glued to their elements while the user scrolls/picks.\n const tick = (): void => {\n this.renderRedactPicks(boxHost);\n this.redactRafId = requestAnimationFrame(tick);\n };\n tick();\n\n document.addEventListener(\"click\", this.boundRedactClick, true);\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n this.host.announceMsg(\"Click sensitive areas to blur, then start recording\");\n }\n\n private handleRedactPickClick(e: MouseEvent): void {\n const target = e.target instanceof Element ? e.target : null;\n // Ignore clicks on the embed's own UI. isEmbedElement catches the retargeted\n // shadow host (real browsers compose click events to it); the contains()\n // check catches the picker's own control-bar buttons when the event isn't\n // retargeted, so their handlers still fire instead of being swallowed here.\n if (!target || isEmbedElement(target) || this.redactPickerRoot?.contains(target)) return;\n e.preventDefault();\n e.stopPropagation();\n\n const idx = this.redactPicks.indexOf(target);\n if (idx >= 0) {\n this.redactPicks.splice(idx, 1);\n } else {\n this.redactPicks.push(target);\n }\n\n const label = this.redactPickerRoot?.querySelector(\"[data-qaid-redact-label]\");\n if (label) {\n const n = this.redactPicks.length;\n label.textContent =\n n === 0 ? \"Click sensitive areas to blur\" : `${n} area${n === 1 ? \"\" : \"s\"} will be blurred`;\n }\n }\n\n private renderRedactPicks(layer: HTMLElement): void {\n // Rebuild the outline boxes to match current picks and their live positions.\n layer.textContent = \"\";\n for (const el of this.redactPicks) {\n const r = el.getBoundingClientRect();\n if (r.width <= 0 || r.height <= 0) continue;\n layer.appendChild(\n h(\"div\", {\n style:\n `position:fixed;left:${r.left}px;top:${r.top}px;width:${r.width}px;height:${r.height}px;` +\n \"border:2px solid #ff6b6b;border-radius:4px;background:rgba(255,107,107,.18);pointer-events:none;\",\n }),\n );\n }\n }\n\n private finishRedactionPicking(record: boolean): void {\n if (this.redactRafId) cancelAnimationFrame(this.redactRafId);\n this.redactRafId = 0;\n document.removeEventListener(\"click\", this.boundRedactClick, true);\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n if (this.redactPickerRoot) {\n this.redactPickerRoot.remove();\n this.redactPickerRoot = null;\n }\n const picks = this.redactPicks;\n this.redactPicks = [];\n this.host.setState(\"IDLE\");\n if (record) {\n void this.startRecording(picks);\n }\n }\n\n private showRecordingIndicator(): void {\n const root = this.host.ensureOverlayHost();\n\n this.recordingIndicator = h(\n \"div\",\n { class: \"qaid-recording-indicator\", style: `z-index:${this.host.config.zIndex + 100}` },\n h(\"div\", { class: \"qaid-recording-dot\" }),\n h(\"span\", {\n class: \"qaid-recording-time\",\n text: this.formatTime(this.host.config.videoOptions.maxDuration),\n }),\n h(\"button\", {\n class: \"qaid-recording-stop\",\n text: \"Stop\",\n attrs: { type: \"button\" },\n on: { click: () => this.stopRecording() },\n }),\n );\n\n this.host.applyVars(this.recordingIndicator);\n\n // The overlay host stays pointer-events:none. It covers the whole viewport,\n // so turning it on made a clear sheet over the page for the length of the\n // recording and every click the visitor made to show the bug died on it.\n // The pill takes its own clicks (pointer-events:auto in its CSS), the way\n // the redaction picker's control bar does.\n root.appendChild(this.recordingIndicator);\n }\n\n private updateRecordingTimer(elapsed: number): void {\n if (!this.recordingIndicator) return;\n const timer = this.recordingIndicator.querySelector<HTMLSpanElement>(\".qaid-recording-time\");\n if (timer) {\n const remaining = Math.max(0, this.host.config.videoOptions.maxDuration - elapsed);\n timer.textContent = this.formatTime(remaining);\n // Announce the final countdown so it is perceivable non-visually.\n if (remaining > 0 && remaining <= 5) {\n this.host.announceMsg(`${remaining} second${remaining === 1 ? \"\" : \"s\"} remaining`);\n }\n }\n }\n\n private formatTime(seconds: number): string {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n }\n\n private removeRecordingIndicator(): void {\n if (this.recordingIndicator) {\n this.recordingIndicator.remove();\n this.recordingIndicator = null;\n }\n }\n\n private showRecordingPreview(): void {\n const root = this.host.ensureOverlayHost();\n\n const videoUrl = URL.createObjectURL(this.recordedBlob!);\n const titleId = `qaid-video-title-${this.host.uid}`;\n\n const title = h(\"h3\", { text: \"Review your recording\", attrs: { id: titleId } });\n\n // Boolean media props must be set as properties (the `muted` attribute is\n // unreliable for autoplay), so build the element then assign them.\n const videoEl = h(\"video\");\n videoEl.src = videoUrl;\n videoEl.controls = true;\n videoEl.autoplay = true;\n videoEl.muted = true;\n\n const textarea = h(\"textarea\", {\n attrs: {\n placeholder: \"Optional: Describe the issue you recorded...\",\n \"aria-label\": \"Describe the issue you recorded\",\n },\n });\n\n const sendBtn = h(\"button\", {\n class: \"qaid-video-btn qaid-video-btn-send\",\n text: \"Send\",\n attrs: { type: \"button\" },\n on: { click: () => this.submitVideoFeedback(textarea.value.trim() || null, sendBtn) },\n });\n\n const box = h(\n \"div\",\n { class: \"qaid-video-preview-box\" },\n title,\n videoEl,\n textarea,\n h(\n \"div\",\n { class: \"qaid-video-preview-actions\" },\n h(\"button\", {\n class: \"qaid-video-btn qaid-video-btn-cancel\",\n text: \"Cancel\",\n attrs: { type: \"button\" },\n on: { click: () => this.cancelRecordingPreview() },\n }),\n h(\"button\", {\n class: \"qaid-video-btn qaid-video-btn-rerecord\",\n text: \"Re-record\",\n attrs: { type: \"button\" },\n on: {\n click: () => {\n this.cancelRecordingPreview();\n this.startRecording();\n },\n },\n }),\n sendBtn,\n ),\n );\n\n this.videoPreview = h(\n \"div\",\n { class: \"qaid-video-preview\", style: `z-index:${this.host.config.zIndex + 100}` },\n box,\n );\n\n this.host.applyVars(this.videoPreview);\n\n // Enable pointer events on the overlay host for the video preview.\n if (this.host.overlayShadowHost) {\n this.host.overlayShadowHost.style.pointerEvents = \"auto\";\n }\n\n root.appendChild(this.videoPreview);\n\n // Dialog semantics + focus trap + background inert.\n this.host.openDialogA11y(box, { labelledbyId: title.id });\n\n // Listen for the escape key.\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n private cancelRecordingPreview(): void {\n this.removeVideoPreview();\n this.cleanupRecording();\n }\n\n private removeVideoPreview(): void {\n // Release focus trap / inert and restore focus before removing the DOM.\n this.host.closeDialogA11y();\n if (this.videoPreview) {\n const videoEl = this.videoPreview.querySelector<HTMLVideoElement>(\"video\");\n if (videoEl?.src) {\n URL.revokeObjectURL(videoEl.src);\n }\n this.videoPreview.remove();\n this.videoPreview = null;\n }\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n private async submitVideoFeedback(message: string | null, sendBtn: HTMLButtonElement): Promise<void> {\n if (!this.recordedBlob || this.isSendingVideo) return;\n\n this.isSendingVideo = true;\n sendBtn.disabled = true;\n sendBtn.textContent = \"Sending...\";\n\n const formData = new FormData();\n formData.append(\"video\", this.recordedBlob, `recording.${this.recordedBlob.type.includes(\"mp4\") ? \"mp4\" : \"webm\"}`);\n formData.append(\"pageUrl\", window.location.href);\n formData.append(\"visitorId\", this.host.visitorId);\n\n if (this.host.config.apiKey) {\n formData.append(\"apiKey\", this.host.config.apiKey);\n }\n if (message) {\n formData.append(\"message\", message);\n }\n if (this.host.consoleCapture) {\n formData.append(\"consoleErrors\", JSON.stringify(this.host.consoleCapture.errors));\n }\n if (this.networkCapture) {\n formData.append(\"networkErrors\", JSON.stringify(this.networkCapture.errors));\n }\n\n let videoFeedbackId: string | number | null = null;\n // Set when the upload failed: the reason shown to the visitor.\n let failure: string | null = null;\n try {\n const response = await fetch(`${this.host.config.endpoint}/video`, {\n method: \"POST\",\n body: formData,\n });\n\n if (response.ok) {\n this.host.announceMsg(\"Recording sent\");\n try {\n const data = (await response.json()) as { id?: string | number };\n videoFeedbackId = data?.id ?? null;\n } catch {\n // Non-JSON / no id — the quest can still launch, just unlinked.\n }\n } else {\n // A refusal (403 on a plan without video, 400, 413 too large, 429…)\n // used to close the preview exactly like a success.\n console.error(\"Failed to submit video feedback:\", await response.text());\n failure =\n response.status === 413\n ? \"This recording is too large to send. Try a shorter one.\"\n : \"Your recording could not be sent.\";\n }\n } catch (error) {\n console.error(\"Failed to submit video feedback:\", error);\n failure = \"Your recording could not be sent. Check your connection and try again.\";\n }\n\n this.isSendingVideo = false;\n\n if (failure) {\n this.showSendError(failure, sendBtn);\n return;\n }\n\n this.removeVideoPreview();\n this.cleanupRecording();\n\n // Once the recording is safely sent, launch the linked quest (if any),\n // passing the new feedback record id so its response is joinable.\n await this.host.tryLaunchQuest(\"video\", videoFeedbackId);\n }\n\n /**\n * Keep the preview open and say the upload failed. Closing it, as a success\n * does, told the visitor their recording had arrived when it had not. Send\n * is re-enabled for another try; Cancel and Re-record still work.\n */\n private showSendError(message: string, sendBtn: HTMLButtonElement): void {\n this.host.announceMsg(`Failed to send recording. ${message}`, true);\n sendBtn.disabled = false;\n sendBtn.textContent = \"Send\";\n\n const actions = this.videoPreview?.querySelector(\".qaid-video-preview-actions\");\n if (actions) {\n let note = this.videoPreview!.querySelector<HTMLElement>(\".qaid-video-error\");\n if (!note) {\n note = h(\"p\", { class: \"qaid-video-error\" });\n actions.before(note);\n }\n note.textContent = message;\n }\n }\n\n private cleanupRecording(): void {\n this.isRecording = false;\n this.removeRecordingIndicator();\n\n if (this.videoRecorder) {\n this.videoRecorder.destroy();\n this.videoRecorder = null;\n }\n if (this.networkCapture) {\n this.networkCapture.restore();\n this.networkCapture = null;\n }\n if (this.recordedBlob) {\n this.recordedBlob = null;\n }\n\n this.host.setButtonsDisabled(false);\n\n // Reset overlay host pointer events if nothing else needs them.\n if (this.host.overlayShadowHost && this.host.state === \"IDLE\") {\n this.host.overlayShadowHost.style.pointerEvents = \"none\";\n }\n }\n}\n"],"names":["MAX_ENTRIES","MAX_BODY_SIZE","truncateBody","body","str","addEntry","errors","entry","safeReadBody","response","text","captureNetworkErrors","originalFetch","input","init","url","method","requestBody","responseBody","originalXHROpen","originalXHRSend","rest","xhr","RecordingController","host","e","redactionElements","createVideoRecorder","elapsed","blob","error","root","boxHost","h","container","tick","target","isEmbedElement","idx","label","n","layer","el","r","record","picks","timer","remaining","seconds","m","s","videoUrl","titleId","title","videoEl","textarea","sendBtn","box","message","formData","videoFeedbackId","failure","actions","note"],"mappings":"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"}
|
package/dist/embed.d.ts
CHANGED
|
@@ -24,7 +24,13 @@ export declare class QaidFeedback {
|
|
|
24
24
|
private state;
|
|
25
25
|
private feedbackData;
|
|
26
26
|
private selectedBounds;
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* This feedback's record id, once its POST answers (null if it failed).
|
|
29
|
+
* The message box and a linked quest open without waiting for it; the
|
|
30
|
+
* PATCHes that need the id wait on this instead.
|
|
31
|
+
*/
|
|
32
|
+
private feedbackIdPromise;
|
|
33
|
+
private questsPrewarmed;
|
|
28
34
|
private activeQuest;
|
|
29
35
|
private isMobile;
|
|
30
36
|
private visitorId;
|
|
@@ -84,6 +90,14 @@ export declare class QaidFeedback {
|
|
|
84
90
|
*/
|
|
85
91
|
private prewarmVideo;
|
|
86
92
|
private prewarmScreenshot;
|
|
93
|
+
/** Quest ids linked to any button, when quest launching is on. */
|
|
94
|
+
private linkedQuestIds;
|
|
95
|
+
/**
|
|
96
|
+
* Load the quests module and fetch every linked quest's definition, so a
|
|
97
|
+
* click opens the quest with no network wait. Both are best-effort: a miss
|
|
98
|
+
* just means the quest loads on click, as it always did.
|
|
99
|
+
*/
|
|
100
|
+
private prewarmQuests;
|
|
87
101
|
/** Run fn when the main thread is idle; cancelled by destroy(). */
|
|
88
102
|
private schedulePrewarm;
|
|
89
103
|
/**
|
|
@@ -127,6 +141,8 @@ export declare class QaidFeedback {
|
|
|
127
141
|
*/
|
|
128
142
|
private openAnnotationEditor;
|
|
129
143
|
private submitFeedback;
|
|
144
|
+
/** POST the feedback. Resolves to the new record's id, or null if it failed. */
|
|
145
|
+
private postFeedback;
|
|
130
146
|
/**
|
|
131
147
|
* Quest id linked to `type`, or "" when quest launching is disabled
|
|
132
148
|
* (no `base`) or this button has no quest configured.
|