@ourroadmaps/web-sdk 0.3.1 → 1.1.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/dist/chunk-2Q3BAT55.js +904 -0
- package/dist/chunk-2Q3BAT55.js.map +1 -0
- package/dist/chunk-WL3SZK6Q.cjs +907 -0
- package/dist/chunk-WL3SZK6Q.cjs.map +1 -0
- package/dist/index.cjs +13 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/review/index.cjs +13 -0
- package/dist/review/index.cjs.map +1 -0
- package/dist/review/index.d.cts +42 -0
- package/dist/review/index.d.ts +42 -0
- package/dist/review/index.js +4 -0
- package/dist/review/index.js.map +1 -0
- package/dist/review.global.js +345 -0
- package/dist/review.global.js.map +1 -0
- package/package.json +11 -10
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
import { __publicField } from './chunk-V6TY7KAL.js';
|
|
2
|
+
|
|
3
|
+
// src/review/api.ts
|
|
4
|
+
var API_URL = (() => {
|
|
5
|
+
if (typeof import.meta !== "undefined" && import.meta.env?.VITE_API_URL) {
|
|
6
|
+
return import.meta.env.VITE_API_URL;
|
|
7
|
+
}
|
|
8
|
+
return "https://api.ourroadmaps.com";
|
|
9
|
+
})();
|
|
10
|
+
async function validateToken(token) {
|
|
11
|
+
const res = await fetch(`${API_URL}/v1/prototype-review/${token}`);
|
|
12
|
+
if (res.status === 410) throw new ReviewError("expired", "This feedback session has expired");
|
|
13
|
+
if (res.status === 404) throw new ReviewError("invalid", "This review link is not valid");
|
|
14
|
+
if (!res.ok) throw new ReviewError("error", "Something went wrong");
|
|
15
|
+
const body = await res.json();
|
|
16
|
+
return body.data;
|
|
17
|
+
}
|
|
18
|
+
async function submitComment(token, comment) {
|
|
19
|
+
const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify(comment)
|
|
23
|
+
});
|
|
24
|
+
if (!res.ok) throw new ReviewError("error", "Failed to submit comment");
|
|
25
|
+
const body = await res.json();
|
|
26
|
+
return body.data;
|
|
27
|
+
}
|
|
28
|
+
async function fetchComments(token) {
|
|
29
|
+
const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`);
|
|
30
|
+
if (!res.ok) throw new ReviewError("error", "Failed to load comments");
|
|
31
|
+
const body = await res.json();
|
|
32
|
+
return body.data;
|
|
33
|
+
}
|
|
34
|
+
var ReviewError = class extends Error {
|
|
35
|
+
constructor(code, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.code = code;
|
|
38
|
+
this.name = "ReviewError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/review/ElementCapture.ts
|
|
43
|
+
function captureElementContext(clientX, clientY) {
|
|
44
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
45
|
+
const docEl = document.documentElement;
|
|
46
|
+
const pinX = (clientX + window.scrollX) / docEl.scrollWidth * 100;
|
|
47
|
+
const pinY = (clientY + window.scrollY) / docEl.scrollHeight * 100;
|
|
48
|
+
if (!el || el === document.body || el === docEl) {
|
|
49
|
+
return {
|
|
50
|
+
pinX,
|
|
51
|
+
pinY,
|
|
52
|
+
element: {
|
|
53
|
+
selector: "body",
|
|
54
|
+
tag: "body",
|
|
55
|
+
text: "",
|
|
56
|
+
ariaLabel: null,
|
|
57
|
+
className: "",
|
|
58
|
+
boundingBox: { x: 0, y: 0, w: docEl.scrollWidth, h: docEl.scrollHeight }
|
|
59
|
+
},
|
|
60
|
+
context: {
|
|
61
|
+
parentTag: "",
|
|
62
|
+
parentText: "",
|
|
63
|
+
grandparentTag: "",
|
|
64
|
+
siblings: [],
|
|
65
|
+
nearbyText: ""
|
|
66
|
+
},
|
|
67
|
+
viewportWidth: window.innerWidth,
|
|
68
|
+
viewportHeight: window.innerHeight
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const rect = el.getBoundingClientRect();
|
|
72
|
+
return {
|
|
73
|
+
pinX,
|
|
74
|
+
pinY,
|
|
75
|
+
element: {
|
|
76
|
+
selector: buildSelector(el),
|
|
77
|
+
tag: el.tagName.toLowerCase(),
|
|
78
|
+
text: getDirectText(el).slice(0, 200),
|
|
79
|
+
ariaLabel: el.getAttribute("aria-label"),
|
|
80
|
+
className: el.className && typeof el.className === "string" ? el.className : "",
|
|
81
|
+
boundingBox: {
|
|
82
|
+
x: Math.round(rect.x),
|
|
83
|
+
y: Math.round(rect.y),
|
|
84
|
+
w: Math.round(rect.width),
|
|
85
|
+
h: Math.round(rect.height)
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
context: {
|
|
89
|
+
parentTag: el.parentElement ? `${el.parentElement.tagName.toLowerCase()}${classStr(el.parentElement)}` : "",
|
|
90
|
+
parentText: el.parentElement ? getDirectText(el.parentElement).slice(0, 100) : "",
|
|
91
|
+
grandparentTag: el.parentElement?.parentElement ? `${el.parentElement.parentElement.tagName.toLowerCase()}${classStr(el.parentElement.parentElement)}` : "",
|
|
92
|
+
siblings: getSiblingsSummary(el),
|
|
93
|
+
nearbyText: getNearbyText(el).slice(0, 200)
|
|
94
|
+
},
|
|
95
|
+
viewportWidth: window.innerWidth,
|
|
96
|
+
viewportHeight: window.innerHeight
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function buildSelector(el) {
|
|
100
|
+
const parts = [];
|
|
101
|
+
let current = el;
|
|
102
|
+
while (current && current !== document.body) {
|
|
103
|
+
if (current.id) {
|
|
104
|
+
parts.unshift(`#${current.id}`);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
let part = current.tagName.toLowerCase();
|
|
108
|
+
if (current.className && typeof current.className === "string") {
|
|
109
|
+
const classes = current.className.trim().split(/\s+/).slice(0, 2).join(".");
|
|
110
|
+
if (classes) part += `.${classes}`;
|
|
111
|
+
}
|
|
112
|
+
parts.unshift(part);
|
|
113
|
+
current = current.parentElement;
|
|
114
|
+
}
|
|
115
|
+
return parts.join(" > ");
|
|
116
|
+
}
|
|
117
|
+
function getDirectText(el) {
|
|
118
|
+
let text = "";
|
|
119
|
+
for (const node of el.childNodes) {
|
|
120
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
121
|
+
text += node.textContent?.trim() || "";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return text.trim() || el.textContent?.trim().slice(0, 200) || "";
|
|
125
|
+
}
|
|
126
|
+
function classStr(el) {
|
|
127
|
+
if (!el.className || typeof el.className !== "string") return "";
|
|
128
|
+
const cls = el.className.trim().split(/\s+/).slice(0, 2).join(".");
|
|
129
|
+
return cls ? `.${cls}` : "";
|
|
130
|
+
}
|
|
131
|
+
function getSiblingsSummary(el) {
|
|
132
|
+
if (!el.parentElement) return [];
|
|
133
|
+
const siblings = [];
|
|
134
|
+
for (const child of el.parentElement.children) {
|
|
135
|
+
if (child === el) continue;
|
|
136
|
+
const tag = child.tagName.toLowerCase();
|
|
137
|
+
const text = (child.textContent?.trim() || "").slice(0, 50);
|
|
138
|
+
siblings.push(text ? `${tag}:${text}` : tag);
|
|
139
|
+
if (siblings.length >= 4) break;
|
|
140
|
+
}
|
|
141
|
+
return siblings;
|
|
142
|
+
}
|
|
143
|
+
function getNearbyText(el) {
|
|
144
|
+
let current = el.parentElement;
|
|
145
|
+
let depth = 0;
|
|
146
|
+
while (current && depth < 3) {
|
|
147
|
+
const text = getDirectText(current);
|
|
148
|
+
if (text && text !== getDirectText(el)) return text;
|
|
149
|
+
current = current.parentElement;
|
|
150
|
+
depth++;
|
|
151
|
+
}
|
|
152
|
+
return "";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/review/styles.ts
|
|
156
|
+
var REVIEW_STYLES = `
|
|
157
|
+
:host {
|
|
158
|
+
all: initial;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* \u2500\u2500\u2500 Pin Container (rendered outside shadow DOM) \u2500\u2500\u2500 */
|
|
162
|
+
.pin-container {
|
|
163
|
+
position: fixed;
|
|
164
|
+
inset: 0;
|
|
165
|
+
pointer-events: none;
|
|
166
|
+
z-index: 10000;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* \u2500\u2500\u2500 Pin Marker \u2500\u2500\u2500 */
|
|
170
|
+
.review-pin {
|
|
171
|
+
position: absolute;
|
|
172
|
+
width: 24px;
|
|
173
|
+
height: 24px;
|
|
174
|
+
border-radius: 50%;
|
|
175
|
+
background: #7c3aed;
|
|
176
|
+
color: #fff;
|
|
177
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
178
|
+
font-size: 12px;
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
justify-content: center;
|
|
183
|
+
transform: translate(-50%, -50%);
|
|
184
|
+
pointer-events: auto;
|
|
185
|
+
cursor: pointer;
|
|
186
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
|
187
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
188
|
+
user-select: none;
|
|
189
|
+
z-index: 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.review-pin:hover {
|
|
193
|
+
transform: translate(-50%, -50%) scale(1.15);
|
|
194
|
+
box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.review-pin--other {
|
|
198
|
+
opacity: 0.5;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.review-pin--highlighted {
|
|
202
|
+
transform: translate(-50%, -50%) scale(1.2);
|
|
203
|
+
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);
|
|
204
|
+
z-index: 2;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* \u2500\u2500\u2500 Toolbar \u2500\u2500\u2500 */
|
|
208
|
+
.review-toolbar {
|
|
209
|
+
position: fixed;
|
|
210
|
+
bottom: 0;
|
|
211
|
+
left: 50%;
|
|
212
|
+
transform: translateX(-50%);
|
|
213
|
+
background: rgba(0, 0, 0, 0.85);
|
|
214
|
+
color: #fff;
|
|
215
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
216
|
+
font-size: 14px;
|
|
217
|
+
border-radius: 12px 12px 0 0;
|
|
218
|
+
padding: 10px 20px;
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-direction: row;
|
|
221
|
+
align-items: center;
|
|
222
|
+
gap: 12px;
|
|
223
|
+
z-index: 10001;
|
|
224
|
+
backdrop-filter: blur(8px);
|
|
225
|
+
box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.2);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* \u2500\u2500\u2500 Comment Card \u2500\u2500\u2500 */
|
|
229
|
+
.review-comment-card {
|
|
230
|
+
position: fixed;
|
|
231
|
+
background: #fff;
|
|
232
|
+
border-radius: 10px;
|
|
233
|
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15), 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
234
|
+
width: 300px;
|
|
235
|
+
padding: 16px;
|
|
236
|
+
z-index: 10002;
|
|
237
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
238
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* \u2500\u2500\u2500 Comment Textarea \u2500\u2500\u2500 */
|
|
242
|
+
.review-comment-input {
|
|
243
|
+
width: 100%;
|
|
244
|
+
border: 1px solid #d1d5db;
|
|
245
|
+
border-radius: 8px;
|
|
246
|
+
padding: 10px 12px;
|
|
247
|
+
resize: vertical;
|
|
248
|
+
font-family: inherit;
|
|
249
|
+
font-size: 14px;
|
|
250
|
+
line-height: 1.5;
|
|
251
|
+
color: #1f2937;
|
|
252
|
+
background: #fafafa;
|
|
253
|
+
box-sizing: border-box;
|
|
254
|
+
outline: none;
|
|
255
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.review-comment-input:focus {
|
|
259
|
+
border-color: #7c3aed;
|
|
260
|
+
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.15);
|
|
261
|
+
background: #fff;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.review-comment-input::placeholder {
|
|
265
|
+
color: #9ca3af;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.review-comment-input:disabled {
|
|
269
|
+
opacity: 0.6;
|
|
270
|
+
cursor: not-allowed;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* \u2500\u2500\u2500 Comment Actions \u2500\u2500\u2500 */
|
|
274
|
+
.review-comment-actions {
|
|
275
|
+
display: flex;
|
|
276
|
+
flex-direction: row;
|
|
277
|
+
justify-content: flex-end;
|
|
278
|
+
gap: 8px;
|
|
279
|
+
margin-top: 12px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* \u2500\u2500\u2500 Buttons \u2500\u2500\u2500 */
|
|
283
|
+
.review-btn {
|
|
284
|
+
padding: 6px 14px;
|
|
285
|
+
border-radius: 6px;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
font-family: inherit;
|
|
288
|
+
font-size: 13px;
|
|
289
|
+
font-weight: 500;
|
|
290
|
+
border: none;
|
|
291
|
+
transition: background 0.15s ease, opacity 0.15s ease;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.review-btn--cancel {
|
|
295
|
+
color: #6b7280;
|
|
296
|
+
background: transparent;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.review-btn--cancel:hover {
|
|
300
|
+
background: #f3f4f6;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.review-btn--submit {
|
|
304
|
+
background: #7c3aed;
|
|
305
|
+
color: #fff;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.review-btn--submit:hover {
|
|
309
|
+
background: #6d28d9;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.review-btn--submit:disabled {
|
|
313
|
+
opacity: 0.5;
|
|
314
|
+
cursor: not-allowed;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* \u2500\u2500\u2500 Comment Error \u2500\u2500\u2500 */
|
|
318
|
+
.review-comment-error {
|
|
319
|
+
color: #dc2626;
|
|
320
|
+
font-size: 12px;
|
|
321
|
+
margin-top: 8px;
|
|
322
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* \u2500\u2500\u2500 Name Prompt \u2500\u2500\u2500 */
|
|
326
|
+
.review-prompt {
|
|
327
|
+
position: fixed;
|
|
328
|
+
top: 50%;
|
|
329
|
+
left: 50%;
|
|
330
|
+
transform: translate(-50%, -50%);
|
|
331
|
+
background: #fff;
|
|
332
|
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
333
|
+
border-radius: 12px;
|
|
334
|
+
padding: 28px 32px;
|
|
335
|
+
z-index: 10002;
|
|
336
|
+
text-align: center;
|
|
337
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
338
|
+
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
339
|
+
max-width: 360px;
|
|
340
|
+
width: 90%;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* \u2500\u2500\u2500 Expired Overlay \u2500\u2500\u2500 */
|
|
344
|
+
.review-expired-overlay {
|
|
345
|
+
position: fixed;
|
|
346
|
+
inset: 0;
|
|
347
|
+
background: rgba(0, 0, 0, 0.6);
|
|
348
|
+
display: flex;
|
|
349
|
+
align-items: center;
|
|
350
|
+
justify-content: center;
|
|
351
|
+
z-index: 10003;
|
|
352
|
+
backdrop-filter: blur(2px);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.review-expired-card {
|
|
356
|
+
background: #fff;
|
|
357
|
+
border-radius: 12px;
|
|
358
|
+
padding: 28px 32px;
|
|
359
|
+
text-align: center;
|
|
360
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
361
|
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
|
|
362
|
+
max-width: 400px;
|
|
363
|
+
width: 90%;
|
|
364
|
+
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* \u2500\u2500\u2500 Toast \u2500\u2500\u2500 */
|
|
368
|
+
.review-toast {
|
|
369
|
+
position: fixed;
|
|
370
|
+
bottom: 80px;
|
|
371
|
+
left: 50%;
|
|
372
|
+
transform: translateX(-50%);
|
|
373
|
+
background: rgba(0, 0, 0, 0.85);
|
|
374
|
+
color: #fff;
|
|
375
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
376
|
+
font-size: 14px;
|
|
377
|
+
padding: 10px 20px;
|
|
378
|
+
border-radius: 8px;
|
|
379
|
+
z-index: 10003;
|
|
380
|
+
pointer-events: none;
|
|
381
|
+
animation: review-toast-fade 2.5s ease forwards;
|
|
382
|
+
backdrop-filter: blur(8px);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@keyframes review-toast-fade {
|
|
386
|
+
0% { opacity: 0; transform: translateX(-50%) translateY(8px); }
|
|
387
|
+
10% { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
388
|
+
80% { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
389
|
+
100% { opacity: 0; transform: translateX(-50%) translateY(-4px); }
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* \u2500\u2500\u2500 Tooltip \u2500\u2500\u2500 */
|
|
393
|
+
.review-tooltip {
|
|
394
|
+
position: absolute;
|
|
395
|
+
background: rgba(0, 0, 0, 0.85);
|
|
396
|
+
color: #fff;
|
|
397
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
398
|
+
font-size: 13px;
|
|
399
|
+
line-height: 1.4;
|
|
400
|
+
border-radius: 6px;
|
|
401
|
+
padding: 8px 12px;
|
|
402
|
+
max-width: 280px;
|
|
403
|
+
z-index: 10002;
|
|
404
|
+
pointer-events: none;
|
|
405
|
+
backdrop-filter: blur(8px);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* \u2500\u2500\u2500 Reduced Motion \u2500\u2500\u2500 */
|
|
409
|
+
@media (prefers-reduced-motion: reduce) {
|
|
410
|
+
*, *::before, *::after {
|
|
411
|
+
animation-duration: 0.01ms !important;
|
|
412
|
+
transition-duration: 0.01ms !important;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
`;
|
|
416
|
+
var PIN_DOCUMENT_STYLES = `
|
|
417
|
+
body {
|
|
418
|
+
position: relative !important;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.pin-container {
|
|
422
|
+
position: absolute;
|
|
423
|
+
top: 0;
|
|
424
|
+
left: 0;
|
|
425
|
+
width: 100%;
|
|
426
|
+
min-height: 100%;
|
|
427
|
+
pointer-events: none;
|
|
428
|
+
z-index: 10000;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.review-pin {
|
|
432
|
+
position: absolute;
|
|
433
|
+
width: 24px;
|
|
434
|
+
height: 24px;
|
|
435
|
+
border-radius: 50%;
|
|
436
|
+
background: #7c3aed;
|
|
437
|
+
color: #fff;
|
|
438
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
439
|
+
font-size: 12px;
|
|
440
|
+
font-weight: 600;
|
|
441
|
+
display: flex;
|
|
442
|
+
align-items: center;
|
|
443
|
+
justify-content: center;
|
|
444
|
+
transform: translate(-50%, -50%);
|
|
445
|
+
pointer-events: auto;
|
|
446
|
+
cursor: pointer;
|
|
447
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
|
448
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
449
|
+
user-select: none;
|
|
450
|
+
z-index: 1;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.review-pin:hover {
|
|
454
|
+
transform: translate(-50%, -50%) scale(1.15);
|
|
455
|
+
box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.review-pin--other {
|
|
459
|
+
opacity: 0.5;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.review-pin--highlighted {
|
|
463
|
+
transform: translate(-50%, -50%) scale(1.2);
|
|
464
|
+
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);
|
|
465
|
+
z-index: 2;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
@media (prefers-reduced-motion: reduce) {
|
|
469
|
+
.review-pin, .review-pin:hover, .review-pin--highlighted {
|
|
470
|
+
transition-duration: 0.01ms !important;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
`;
|
|
474
|
+
|
|
475
|
+
// src/review/PinManager.ts
|
|
476
|
+
var STYLE_ID = "roadmaps-review-pin-styles";
|
|
477
|
+
var PinManager = class {
|
|
478
|
+
constructor() {
|
|
479
|
+
__publicField(this, "container");
|
|
480
|
+
__publicField(this, "pins", /* @__PURE__ */ new Map());
|
|
481
|
+
__publicField(this, "styleEl", null);
|
|
482
|
+
this.container = document.createElement("div");
|
|
483
|
+
this.container.className = "pin-container";
|
|
484
|
+
}
|
|
485
|
+
mount() {
|
|
486
|
+
if (!document.getElementById(STYLE_ID)) {
|
|
487
|
+
this.styleEl = document.createElement("style");
|
|
488
|
+
this.styleEl.id = STYLE_ID;
|
|
489
|
+
this.styleEl.textContent = PIN_DOCUMENT_STYLES;
|
|
490
|
+
document.head.appendChild(this.styleEl);
|
|
491
|
+
}
|
|
492
|
+
document.body.appendChild(this.container);
|
|
493
|
+
}
|
|
494
|
+
addPin(pinNumber, x, y, isMine) {
|
|
495
|
+
const pin = document.createElement("div");
|
|
496
|
+
pin.className = isMine ? "review-pin" : "review-pin review-pin--other";
|
|
497
|
+
pin.textContent = String(pinNumber);
|
|
498
|
+
pin.style.left = `${x}%`;
|
|
499
|
+
pin.style.top = `${y}%`;
|
|
500
|
+
pin.dataset.pinNumber = String(pinNumber);
|
|
501
|
+
this.container.appendChild(pin);
|
|
502
|
+
this.pins.set(pinNumber, pin);
|
|
503
|
+
}
|
|
504
|
+
removePin(pinNumber) {
|
|
505
|
+
const pin = this.pins.get(pinNumber);
|
|
506
|
+
if (pin) {
|
|
507
|
+
pin.remove();
|
|
508
|
+
this.pins.delete(pinNumber);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
highlightPin(pinNumber) {
|
|
512
|
+
for (const [num, el] of this.pins) {
|
|
513
|
+
el.classList.toggle("review-pin--highlighted", num === pinNumber);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
clearHighlight() {
|
|
517
|
+
for (const el of this.pins.values()) {
|
|
518
|
+
el.classList.remove("review-pin--highlighted");
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
destroy() {
|
|
522
|
+
this.container.remove();
|
|
523
|
+
this.pins.clear();
|
|
524
|
+
if (this.styleEl) {
|
|
525
|
+
this.styleEl.remove();
|
|
526
|
+
this.styleEl = null;
|
|
527
|
+
} else {
|
|
528
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// src/review/CommentCard.ts
|
|
534
|
+
var CommentCard = class {
|
|
535
|
+
constructor(shadowRoot) {
|
|
536
|
+
this.shadowRoot = shadowRoot;
|
|
537
|
+
__publicField(this, "card", null);
|
|
538
|
+
__publicField(this, "onSubmit", null);
|
|
539
|
+
__publicField(this, "onCancel", null);
|
|
540
|
+
}
|
|
541
|
+
show(options) {
|
|
542
|
+
this.hide();
|
|
543
|
+
this.onSubmit = options.onSubmit;
|
|
544
|
+
this.onCancel = options.onCancel;
|
|
545
|
+
this.card = document.createElement("div");
|
|
546
|
+
this.card.className = "review-comment-card";
|
|
547
|
+
this.card.style.left = `${Math.min(options.x, window.innerWidth - 320)}px`;
|
|
548
|
+
this.card.style.top = `${Math.min(options.y + 20, window.innerHeight - 200)}px`;
|
|
549
|
+
this.card.innerHTML = `
|
|
550
|
+
<textarea class="review-comment-input" placeholder="Leave your feedback..." rows="3"></textarea>
|
|
551
|
+
<div class="review-comment-actions">
|
|
552
|
+
<button class="review-btn review-btn--cancel">Cancel</button>
|
|
553
|
+
<button class="review-btn review-btn--submit">Submit</button>
|
|
554
|
+
</div>
|
|
555
|
+
<div class="review-comment-error" style="display:none"></div>
|
|
556
|
+
`;
|
|
557
|
+
this.attachListeners();
|
|
558
|
+
this.shadowRoot.appendChild(this.card);
|
|
559
|
+
const textarea = this.card.querySelector("textarea");
|
|
560
|
+
textarea?.focus();
|
|
561
|
+
}
|
|
562
|
+
hide() {
|
|
563
|
+
this.card?.remove();
|
|
564
|
+
this.card = null;
|
|
565
|
+
}
|
|
566
|
+
showForGeneral(options) {
|
|
567
|
+
this.show({
|
|
568
|
+
x: window.innerWidth / 2 - 150,
|
|
569
|
+
y: window.innerHeight / 2 - 100,
|
|
570
|
+
onSubmit: options.onSubmit,
|
|
571
|
+
onCancel: options.onCancel
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
attachListeners() {
|
|
575
|
+
if (!this.card) return;
|
|
576
|
+
this.card.querySelector(".review-btn--cancel")?.addEventListener("click", () => {
|
|
577
|
+
this.onCancel?.();
|
|
578
|
+
this.hide();
|
|
579
|
+
});
|
|
580
|
+
this.card.querySelector(".review-btn--submit")?.addEventListener("click", () => this.handleSubmit());
|
|
581
|
+
this.card.querySelector("textarea")?.addEventListener("keydown", (e) => {
|
|
582
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
583
|
+
e.preventDefault();
|
|
584
|
+
this.handleSubmit();
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
async handleSubmit() {
|
|
589
|
+
if (!this.card) return;
|
|
590
|
+
const textarea = this.card.querySelector("textarea");
|
|
591
|
+
const text = textarea.value.trim();
|
|
592
|
+
if (!text) return;
|
|
593
|
+
const submitBtn = this.card.querySelector(".review-btn--submit");
|
|
594
|
+
const errorEl = this.card.querySelector(".review-comment-error");
|
|
595
|
+
submitBtn.disabled = true;
|
|
596
|
+
submitBtn.textContent = "Submitting...";
|
|
597
|
+
textarea.disabled = true;
|
|
598
|
+
errorEl.style.display = "none";
|
|
599
|
+
try {
|
|
600
|
+
await this.onSubmit?.(text);
|
|
601
|
+
this.hide();
|
|
602
|
+
} catch (err) {
|
|
603
|
+
submitBtn.disabled = false;
|
|
604
|
+
submitBtn.textContent = "Submit";
|
|
605
|
+
textarea.disabled = false;
|
|
606
|
+
errorEl.textContent = err instanceof Error ? err.message : "Failed to submit";
|
|
607
|
+
errorEl.style.display = "block";
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// src/review/ReviewMode.ts
|
|
613
|
+
var ReviewMode = class {
|
|
614
|
+
constructor(token, shadowRoot, initData) {
|
|
615
|
+
this.token = token;
|
|
616
|
+
this.shadowRoot = shadowRoot;
|
|
617
|
+
this.initData = initData;
|
|
618
|
+
__publicField(this, "pinManager");
|
|
619
|
+
__publicField(this, "commentCard");
|
|
620
|
+
__publicField(this, "nextPinNumber");
|
|
621
|
+
__publicField(this, "pendingPinNumber", null);
|
|
622
|
+
__publicField(this, "promptEl", null);
|
|
623
|
+
__publicField(this, "toolbarEl", null);
|
|
624
|
+
__publicField(this, "clickHandler", null);
|
|
625
|
+
this.pinManager = new PinManager();
|
|
626
|
+
this.commentCard = new CommentCard(shadowRoot);
|
|
627
|
+
this.nextPinNumber = initData.nextPinNumber;
|
|
628
|
+
}
|
|
629
|
+
async init() {
|
|
630
|
+
document.body.style.cursor = "crosshair";
|
|
631
|
+
this.pinManager.mount();
|
|
632
|
+
this.showPrompt();
|
|
633
|
+
this.renderToolbar();
|
|
634
|
+
await this.loadExistingPins();
|
|
635
|
+
this.clickHandler = (e) => this.handleClick(e);
|
|
636
|
+
document.addEventListener("click", this.clickHandler, true);
|
|
637
|
+
}
|
|
638
|
+
showPrompt() {
|
|
639
|
+
this.promptEl = document.createElement("div");
|
|
640
|
+
this.promptEl.className = "review-prompt";
|
|
641
|
+
this.promptEl.innerHTML = `
|
|
642
|
+
<h3 style="margin:0 0 8px;font-size:16px;">Click anywhere to leave feedback</h3>
|
|
643
|
+
<p style="margin:0;color:#666;font-size:14px;">Drop numbered pins on elements you want to comment on</p>
|
|
644
|
+
`;
|
|
645
|
+
this.shadowRoot.appendChild(this.promptEl);
|
|
646
|
+
setTimeout(() => this.dismissPrompt(), 5e3);
|
|
647
|
+
}
|
|
648
|
+
dismissPrompt() {
|
|
649
|
+
if (this.promptEl) {
|
|
650
|
+
this.promptEl.remove();
|
|
651
|
+
this.promptEl = null;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
renderToolbar() {
|
|
655
|
+
this.toolbarEl = document.createElement("div");
|
|
656
|
+
this.toolbarEl.className = "review-toolbar";
|
|
657
|
+
this.updateToolbar();
|
|
658
|
+
this.shadowRoot.appendChild(this.toolbarEl);
|
|
659
|
+
}
|
|
660
|
+
updateToolbar() {
|
|
661
|
+
if (!this.toolbarEl) return;
|
|
662
|
+
const pinCount = this.nextPinNumber - 1;
|
|
663
|
+
this.toolbarEl.innerHTML = `
|
|
664
|
+
<span style="font-weight:500;">${this.initData.session.name}</span>
|
|
665
|
+
<span style="opacity:0.7;">${pinCount} pin${pinCount !== 1 ? "s" : ""}</span>
|
|
666
|
+
<button class="review-btn review-btn--submit" style="margin-left:auto;padding:6px 12px;font-size:13px;">General Comment</button>
|
|
667
|
+
`;
|
|
668
|
+
this.toolbarEl.querySelector("button")?.addEventListener("click", (e) => {
|
|
669
|
+
e.stopPropagation();
|
|
670
|
+
this.handleGeneralComment();
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
async loadExistingPins() {
|
|
674
|
+
try {
|
|
675
|
+
const comments = await fetchComments(this.token);
|
|
676
|
+
for (const c of comments) {
|
|
677
|
+
if (c.pinNumber != null && c.pinData) {
|
|
678
|
+
const isMine = c.commentText != null;
|
|
679
|
+
this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, isMine);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
} catch {
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
handleClick(e) {
|
|
686
|
+
const path = e.composedPath();
|
|
687
|
+
if (path.some((el) => el instanceof HTMLElement && el.closest?.("#ourroadmaps-review"))) return;
|
|
688
|
+
if (this.pendingPinNumber != null) return;
|
|
689
|
+
this.dismissPrompt();
|
|
690
|
+
const pinData = captureElementContext(e.clientX, e.clientY);
|
|
691
|
+
const pinNumber = this.nextPinNumber;
|
|
692
|
+
this.pinManager.addPin(pinNumber, pinData.pinX, pinData.pinY, true);
|
|
693
|
+
this.pendingPinNumber = pinNumber;
|
|
694
|
+
this.commentCard.show({
|
|
695
|
+
x: e.clientX,
|
|
696
|
+
y: e.clientY,
|
|
697
|
+
onSubmit: async (text) => {
|
|
698
|
+
await submitComment(this.token, {
|
|
699
|
+
commentText: text,
|
|
700
|
+
pinNumber,
|
|
701
|
+
pinData,
|
|
702
|
+
pageUrl: window.location.pathname
|
|
703
|
+
});
|
|
704
|
+
this.pendingPinNumber = null;
|
|
705
|
+
this.nextPinNumber++;
|
|
706
|
+
this.updateToolbar();
|
|
707
|
+
this.showToast("Comment saved");
|
|
708
|
+
},
|
|
709
|
+
onCancel: () => {
|
|
710
|
+
this.pinManager.removePin(pinNumber);
|
|
711
|
+
this.pendingPinNumber = null;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
e.preventDefault();
|
|
715
|
+
e.stopPropagation();
|
|
716
|
+
}
|
|
717
|
+
handleGeneralComment() {
|
|
718
|
+
if (this.pendingPinNumber != null) return;
|
|
719
|
+
this.commentCard.showForGeneral({
|
|
720
|
+
onSubmit: async (text) => {
|
|
721
|
+
await submitComment(this.token, {
|
|
722
|
+
commentText: text,
|
|
723
|
+
pinNumber: null,
|
|
724
|
+
pinData: null,
|
|
725
|
+
pageUrl: window.location.pathname
|
|
726
|
+
});
|
|
727
|
+
this.showToast("Comment saved");
|
|
728
|
+
},
|
|
729
|
+
onCancel: () => {
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
showToast(message) {
|
|
734
|
+
const toast = document.createElement("div");
|
|
735
|
+
toast.className = "review-toast";
|
|
736
|
+
toast.textContent = message;
|
|
737
|
+
this.shadowRoot.appendChild(toast);
|
|
738
|
+
setTimeout(() => toast.remove(), 2500);
|
|
739
|
+
}
|
|
740
|
+
destroy() {
|
|
741
|
+
document.body.style.cursor = "";
|
|
742
|
+
if (this.clickHandler) {
|
|
743
|
+
document.removeEventListener("click", this.clickHandler, true);
|
|
744
|
+
}
|
|
745
|
+
this.dismissPrompt();
|
|
746
|
+
this.toolbarEl?.remove();
|
|
747
|
+
this.commentCard.hide();
|
|
748
|
+
this.pinManager.destroy();
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
// src/review/TriageMode.ts
|
|
753
|
+
var TriageMode = class {
|
|
754
|
+
constructor(token, shadowRoot) {
|
|
755
|
+
this.token = token;
|
|
756
|
+
this.shadowRoot = shadowRoot;
|
|
757
|
+
__publicField(this, "pinManager");
|
|
758
|
+
__publicField(this, "toolbarEl", null);
|
|
759
|
+
__publicField(this, "tooltipEl", null);
|
|
760
|
+
__publicField(this, "comments", []);
|
|
761
|
+
__publicField(this, "pinClickHandler", null);
|
|
762
|
+
this.pinManager = new PinManager();
|
|
763
|
+
}
|
|
764
|
+
async init() {
|
|
765
|
+
this.pinManager.mount();
|
|
766
|
+
try {
|
|
767
|
+
this.comments = await fetchComments(this.token);
|
|
768
|
+
for (const c of this.comments) {
|
|
769
|
+
if (c.pinNumber != null && c.pinData) {
|
|
770
|
+
this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, true);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
} catch {
|
|
774
|
+
}
|
|
775
|
+
this.renderToolbar();
|
|
776
|
+
this.pinClickHandler = (e) => {
|
|
777
|
+
const target = e.target;
|
|
778
|
+
if (target.classList?.contains("review-pin")) {
|
|
779
|
+
const num = Number(target.dataset.pinNumber);
|
|
780
|
+
if (num) this.handlePinClick(num, e);
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
document.addEventListener("click", this.pinClickHandler, true);
|
|
784
|
+
}
|
|
785
|
+
renderToolbar() {
|
|
786
|
+
this.toolbarEl = document.createElement("div");
|
|
787
|
+
this.toolbarEl.className = "review-toolbar";
|
|
788
|
+
const count = this.comments.filter((c) => c.pinNumber != null).length;
|
|
789
|
+
this.toolbarEl.innerHTML = `
|
|
790
|
+
<span style="font-weight:500;">Triage Mode</span>
|
|
791
|
+
<span style="opacity:0.7;">${count} pin${count !== 1 ? "s" : ""}</span>
|
|
792
|
+
`;
|
|
793
|
+
this.shadowRoot.appendChild(this.toolbarEl);
|
|
794
|
+
}
|
|
795
|
+
handlePinClick(pinNumber, e) {
|
|
796
|
+
this.hideTooltip();
|
|
797
|
+
this.pinManager.highlightPin(pinNumber);
|
|
798
|
+
const comment = this.comments.find((c) => c.pinNumber === pinNumber);
|
|
799
|
+
if (!comment) return;
|
|
800
|
+
this.tooltipEl = document.createElement("div");
|
|
801
|
+
this.tooltipEl.className = "review-tooltip";
|
|
802
|
+
const time = new Date(comment.createdAt).toLocaleString();
|
|
803
|
+
this.tooltipEl.innerHTML = `
|
|
804
|
+
<div style="font-weight:500;margin-bottom:4px;">Pin #${pinNumber}</div>
|
|
805
|
+
<div style="margin-bottom:4px;">${comment.commentText || "(no text)"}</div>
|
|
806
|
+
<div style="font-size:11px;opacity:0.7;">${time}</div>
|
|
807
|
+
`;
|
|
808
|
+
this.tooltipEl.style.position = "fixed";
|
|
809
|
+
this.tooltipEl.style.left = `${Math.min(e.clientX + 16, window.innerWidth - 300)}px`;
|
|
810
|
+
this.tooltipEl.style.top = `${Math.min(e.clientY - 10, window.innerHeight - 150)}px`;
|
|
811
|
+
this.shadowRoot.appendChild(this.tooltipEl);
|
|
812
|
+
const dismiss = (ev) => {
|
|
813
|
+
if (ev.target !== this.tooltipEl && !this.tooltipEl?.contains(ev.target)) {
|
|
814
|
+
this.hideTooltip();
|
|
815
|
+
this.pinManager.clearHighlight();
|
|
816
|
+
document.removeEventListener("click", dismiss, true);
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
setTimeout(() => document.addEventListener("click", dismiss, true), 0);
|
|
820
|
+
}
|
|
821
|
+
hideTooltip() {
|
|
822
|
+
this.tooltipEl?.remove();
|
|
823
|
+
this.tooltipEl = null;
|
|
824
|
+
}
|
|
825
|
+
destroy() {
|
|
826
|
+
if (this.pinClickHandler) {
|
|
827
|
+
document.removeEventListener("click", this.pinClickHandler, true);
|
|
828
|
+
}
|
|
829
|
+
this.hideTooltip();
|
|
830
|
+
this.toolbarEl?.remove();
|
|
831
|
+
this.pinManager.destroy();
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
// src/review/Review.ts
|
|
836
|
+
var Review = class {
|
|
837
|
+
constructor(_options = {}) {
|
|
838
|
+
__publicField(this, "root");
|
|
839
|
+
__publicField(this, "shadow");
|
|
840
|
+
__publicField(this, "mode", null);
|
|
841
|
+
__publicField(this, "_isDestroyed", false);
|
|
842
|
+
this.root = document.createElement("div");
|
|
843
|
+
this.root.id = "ourroadmaps-review";
|
|
844
|
+
this.shadow = this.root.attachShadow({ mode: "open" });
|
|
845
|
+
const styleEl = document.createElement("style");
|
|
846
|
+
styleEl.textContent = REVIEW_STYLES;
|
|
847
|
+
this.shadow.appendChild(styleEl);
|
|
848
|
+
document.body.appendChild(this.root);
|
|
849
|
+
}
|
|
850
|
+
async init() {
|
|
851
|
+
const params = new URLSearchParams(window.location.search);
|
|
852
|
+
const reviewToken = params.get("review");
|
|
853
|
+
const triageToken = params.get("triage");
|
|
854
|
+
if (reviewToken) {
|
|
855
|
+
try {
|
|
856
|
+
const data = await validateToken(reviewToken);
|
|
857
|
+
this.mode = new ReviewMode(reviewToken, this.shadow, data);
|
|
858
|
+
await this.mode.init();
|
|
859
|
+
} catch (err) {
|
|
860
|
+
if (err instanceof ReviewError) {
|
|
861
|
+
this.showErrorOverlay(
|
|
862
|
+
err.code === "expired" ? "This feedback session has expired" : "This review link is no longer valid"
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
} else if (triageToken) {
|
|
867
|
+
try {
|
|
868
|
+
await validateToken(triageToken);
|
|
869
|
+
this.mode = new TriageMode(triageToken, this.shadow);
|
|
870
|
+
await this.mode.init();
|
|
871
|
+
} catch (err) {
|
|
872
|
+
if (err instanceof ReviewError) {
|
|
873
|
+
this.showErrorOverlay(
|
|
874
|
+
err.code === "expired" ? "This feedback session has expired" : "This review link is no longer valid"
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
showErrorOverlay(message) {
|
|
881
|
+
const overlay = document.createElement("div");
|
|
882
|
+
overlay.className = "review-expired-overlay";
|
|
883
|
+
overlay.innerHTML = `
|
|
884
|
+
<div class="review-expired-card">
|
|
885
|
+
<h2 style="margin:0 0 8px;font-size:18px;">Session Unavailable</h2>
|
|
886
|
+
<p style="margin:0;color:#666;font-size:14px;">${message}</p>
|
|
887
|
+
<p style="margin:12px 0 0;color:#999;font-size:13px;">Contact the prototype owner for a new link.</p>
|
|
888
|
+
</div>
|
|
889
|
+
`;
|
|
890
|
+
this.shadow.appendChild(overlay);
|
|
891
|
+
document.body.style.filter = "grayscale(0.8)";
|
|
892
|
+
}
|
|
893
|
+
destroy() {
|
|
894
|
+
if (this._isDestroyed) return;
|
|
895
|
+
this._isDestroyed = true;
|
|
896
|
+
this.mode?.destroy();
|
|
897
|
+
this.root.remove();
|
|
898
|
+
document.body.style.filter = "";
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
export { Review };
|
|
903
|
+
//# sourceMappingURL=chunk-2Q3BAT55.js.map
|
|
904
|
+
//# sourceMappingURL=chunk-2Q3BAT55.js.map
|