@mhosaic/feedback 0.28.0 → 0.29.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-FD3CJBMR.mjs → chunk-7FGWO6KO.mjs} +511 -34
- package/dist/chunk-7FGWO6KO.mjs.map +1 -0
- package/dist/embed.min.js +139 -5
- package/dist/embed.min.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/widget.min.js +140 -6
- package/dist/widget.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-FD3CJBMR.mjs.map +0 -1
|
@@ -167,10 +167,18 @@ function createApiClient(options) {
|
|
|
167
167
|
);
|
|
168
168
|
return readJsonObject(response, "getReportBySeq");
|
|
169
169
|
}
|
|
170
|
-
async function addComment(reportId, externalId, body, clientNonce) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
async function addComment(reportId, externalId, body, clientNonce, attachments) {
|
|
171
|
+
let init;
|
|
172
|
+
if (attachments && attachments.length > 0) {
|
|
173
|
+
const form = new FormData();
|
|
174
|
+
form.append("body", body);
|
|
175
|
+
if (clientNonce !== void 0) form.append("client_nonce", clientNonce);
|
|
176
|
+
attachments.forEach(
|
|
177
|
+
(blob, i) => form.append("attachment", blob, `attachment-${i + 1}.png`)
|
|
178
|
+
);
|
|
179
|
+
init = { method: "POST", headers: widgetHeaders(externalId), body: form };
|
|
180
|
+
} else {
|
|
181
|
+
init = {
|
|
174
182
|
method: "POST",
|
|
175
183
|
headers: {
|
|
176
184
|
...widgetHeaders(externalId),
|
|
@@ -180,7 +188,11 @@ function createApiClient(options) {
|
|
|
180
188
|
body,
|
|
181
189
|
...clientNonce !== void 0 && { client_nonce: clientNonce }
|
|
182
190
|
})
|
|
183
|
-
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const response = await fetcher(
|
|
194
|
+
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/comments/`,
|
|
195
|
+
init
|
|
184
196
|
);
|
|
185
197
|
if (!response.ok) {
|
|
186
198
|
const text = await response.text().catch(() => "");
|
|
@@ -188,6 +200,24 @@ function createApiClient(options) {
|
|
|
188
200
|
}
|
|
189
201
|
return response.json();
|
|
190
202
|
}
|
|
203
|
+
async function editDescription(reportId, externalId, description) {
|
|
204
|
+
const response = await fetcher(
|
|
205
|
+
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
|
|
206
|
+
{
|
|
207
|
+
method: "PATCH",
|
|
208
|
+
headers: {
|
|
209
|
+
...widgetHeaders(externalId),
|
|
210
|
+
"Content-Type": "application/json"
|
|
211
|
+
},
|
|
212
|
+
body: JSON.stringify({ description })
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
if (!response.ok) {
|
|
216
|
+
const text = await response.text().catch(() => "");
|
|
217
|
+
throw new Error(`editDescription failed: ${response.status} ${text}`);
|
|
218
|
+
}
|
|
219
|
+
return response.json();
|
|
220
|
+
}
|
|
191
221
|
async function closeAsResolved(reportId, externalId) {
|
|
192
222
|
const response = await fetcher(
|
|
193
223
|
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
|
|
@@ -291,6 +321,7 @@ function createApiClient(options) {
|
|
|
291
321
|
getReport,
|
|
292
322
|
getReportBySeq,
|
|
293
323
|
addComment,
|
|
324
|
+
editDescription,
|
|
294
325
|
closeAsResolved,
|
|
295
326
|
reopenUnresolved,
|
|
296
327
|
listBoard,
|
|
@@ -697,6 +728,10 @@ var DEFAULT_STRINGS = {
|
|
|
697
728
|
"mine.refresh": "Refresh",
|
|
698
729
|
"mine.loading": "Loading\u2026",
|
|
699
730
|
"mine.error": "Could not load your reports.",
|
|
731
|
+
"mine.jump.placeholder": "#",
|
|
732
|
+
"mine.jump.go": "Open",
|
|
733
|
+
"mine.jump.aria": "Open a report by number",
|
|
734
|
+
"mine.jump.not_found": "No report with that number.",
|
|
700
735
|
"error.rate_limited": "Too many requests \u2014 retry in {seconds}s.",
|
|
701
736
|
"error.rate_limited_generic": "Too many requests \u2014 try again in a moment.",
|
|
702
737
|
"mine.replies_one": "1 reply",
|
|
@@ -712,6 +747,16 @@ var DEFAULT_STRINGS = {
|
|
|
712
747
|
"detail.compose_placeholder": "Add a follow-up reply\u2026",
|
|
713
748
|
"detail.compose_send": "Reply",
|
|
714
749
|
"detail.compose_sending": "Sending\u2026",
|
|
750
|
+
"detail.attach_add": "Add image",
|
|
751
|
+
"detail.attach_remove": "Remove",
|
|
752
|
+
"detail.attachment_alt": "Attached image",
|
|
753
|
+
"detail.copy_link": "Copy link",
|
|
754
|
+
"detail.copied": "Link copied",
|
|
755
|
+
"detail.edit": "Edit",
|
|
756
|
+
"detail.edit_save": "Save",
|
|
757
|
+
"detail.edit_cancel": "Cancel",
|
|
758
|
+
"detail.edit_saving": "Saving\u2026",
|
|
759
|
+
"detail.edit_failed": "Could not save your changes.",
|
|
715
760
|
"detail.close_cta": "Mark as resolved",
|
|
716
761
|
"detail.close_busy": "Marking\u2026",
|
|
717
762
|
"detail.reopen_cta": "Still not fixed",
|
|
@@ -869,6 +914,10 @@ var FRENCH_STRINGS = {
|
|
|
869
914
|
"mine.refresh": "Actualiser",
|
|
870
915
|
"mine.loading": "Chargement\u2026",
|
|
871
916
|
"mine.error": "Impossible de charger vos rapports.",
|
|
917
|
+
"mine.jump.placeholder": "#",
|
|
918
|
+
"mine.jump.go": "Ouvrir",
|
|
919
|
+
"mine.jump.aria": "Ouvrir un rapport par num\xE9ro",
|
|
920
|
+
"mine.jump.not_found": "Aucun rapport avec ce num\xE9ro.",
|
|
872
921
|
"error.rate_limited": "Trop de requ\xEAtes \u2014 r\xE9essaie dans {seconds} s.",
|
|
873
922
|
"error.rate_limited_generic": "Trop de requ\xEAtes \u2014 r\xE9essaie dans un instant.",
|
|
874
923
|
"mine.replies_one": "1 r\xE9ponse",
|
|
@@ -884,6 +933,16 @@ var FRENCH_STRINGS = {
|
|
|
884
933
|
"detail.compose_placeholder": "Ajouter une r\xE9ponse\u2026",
|
|
885
934
|
"detail.compose_send": "R\xE9pondre",
|
|
886
935
|
"detail.compose_sending": "Envoi\u2026",
|
|
936
|
+
"detail.attach_add": "Ajouter une image",
|
|
937
|
+
"detail.attach_remove": "Retirer",
|
|
938
|
+
"detail.attachment_alt": "Image jointe",
|
|
939
|
+
"detail.copy_link": "Copier le lien",
|
|
940
|
+
"detail.copied": "Lien copi\xE9",
|
|
941
|
+
"detail.edit": "Modifier",
|
|
942
|
+
"detail.edit_save": "Enregistrer",
|
|
943
|
+
"detail.edit_cancel": "Annuler",
|
|
944
|
+
"detail.edit_saving": "Enregistrement\u2026",
|
|
945
|
+
"detail.edit_failed": "Impossible d\u2019enregistrer vos modifications.",
|
|
887
946
|
"detail.close_cta": "Marquer comme r\xE9solu",
|
|
888
947
|
"detail.close_busy": "Validation\u2026",
|
|
889
948
|
"detail.reopen_cta": "Toujours pas r\xE9gl\xE9",
|
|
@@ -1094,9 +1153,21 @@ function CommentBubble({ comment, strings, onOpenSeq }) {
|
|
|
1094
1153
|
const variant = isAgent ? "mcp" : isSystem ? "system" : "staff";
|
|
1095
1154
|
const labelKey = variant === "mcp" ? "detail.author.mcp" : variant === "system" ? "detail.author.system" : "detail.author.staff";
|
|
1096
1155
|
const label = comment.author_label || strings[labelKey];
|
|
1156
|
+
const images = (comment.attachments ?? []).filter((a) => a.url);
|
|
1097
1157
|
return /* @__PURE__ */ jsxs2("div", { class: `comment-bubble ${isMine ? "is-mine" : "is-other"}`, children: [
|
|
1098
1158
|
!isMine && label && /* @__PURE__ */ jsx("div", { class: `comment-author comment-author--${variant}`, children: label }),
|
|
1099
|
-
/* @__PURE__ */ jsx("div", { class: "comment-body", children: linkifySeq(comment.body, onOpenSeq) }),
|
|
1159
|
+
comment.body && /* @__PURE__ */ jsx("div", { class: "comment-body", children: linkifySeq(comment.body, onOpenSeq) }),
|
|
1160
|
+
images.length > 0 && /* @__PURE__ */ jsx("div", { class: "comment-attachments", children: images.map((a) => /* @__PURE__ */ jsx(
|
|
1161
|
+
"a",
|
|
1162
|
+
{
|
|
1163
|
+
class: "comment-attachment",
|
|
1164
|
+
href: a.url,
|
|
1165
|
+
target: "_blank",
|
|
1166
|
+
rel: "noopener noreferrer",
|
|
1167
|
+
children: /* @__PURE__ */ jsx("img", { src: a.url, alt: strings["detail.attachment_alt"], loading: "lazy" })
|
|
1168
|
+
},
|
|
1169
|
+
a.id
|
|
1170
|
+
)) }),
|
|
1100
1171
|
/* @__PURE__ */ jsx("div", { class: "comment-time", children: formatTime(comment.created_at) })
|
|
1101
1172
|
] });
|
|
1102
1173
|
}
|
|
@@ -1162,15 +1233,67 @@ function ReportDetailView({
|
|
|
1162
1233
|
onBack,
|
|
1163
1234
|
canModerate = true,
|
|
1164
1235
|
variant = "modal",
|
|
1165
|
-
onOpenSeq
|
|
1236
|
+
onOpenSeq,
|
|
1237
|
+
buildPermalink
|
|
1166
1238
|
}) {
|
|
1167
1239
|
const [detail, setDetail] = useState(null);
|
|
1168
1240
|
const [error, setError] = useState(null);
|
|
1241
|
+
const [copied, setCopied] = useState(false);
|
|
1242
|
+
const [editing, setEditing] = useState(false);
|
|
1243
|
+
const [editBody, setEditBody] = useState("");
|
|
1244
|
+
const [savingEdit, setSavingEdit] = useState(false);
|
|
1245
|
+
const [editError, setEditError] = useState(false);
|
|
1169
1246
|
const [composeBody, setComposeBody] = useState("");
|
|
1170
1247
|
const [sending, setSending] = useState(false);
|
|
1171
1248
|
const [closing, setClosing] = useState(false);
|
|
1172
1249
|
const [reopening, setReopening] = useState(false);
|
|
1250
|
+
const [composeShots, setComposeShots] = useState([]);
|
|
1251
|
+
const [attachError, setAttachError] = useState("");
|
|
1252
|
+
const fileInputRef = useRef(null);
|
|
1173
1253
|
const mountedRef = useRef(true);
|
|
1254
|
+
const shotsRef = useRef(composeShots);
|
|
1255
|
+
shotsRef.current = composeShots;
|
|
1256
|
+
useEffect(() => {
|
|
1257
|
+
return () => shotsRef.current.forEach((s) => URL.revokeObjectURL(s.preview));
|
|
1258
|
+
}, []);
|
|
1259
|
+
const acceptComposeFiles = (files) => {
|
|
1260
|
+
setAttachError("");
|
|
1261
|
+
const remaining = MAX_SCREENSHOTS - composeShots.length;
|
|
1262
|
+
const batch = files.slice(0, Math.max(0, remaining));
|
|
1263
|
+
for (const file of batch) {
|
|
1264
|
+
const err = validateScreenshotFile(file);
|
|
1265
|
+
if (err) {
|
|
1266
|
+
setAttachError(
|
|
1267
|
+
err.kind === "type" ? strings["form.screenshot.error_type"] : strings["form.screenshot.error_size"].replace("{max}", String(err.maxMb))
|
|
1268
|
+
);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
if (batch.length) {
|
|
1273
|
+
const incoming = batch.map((blob) => ({
|
|
1274
|
+
blob,
|
|
1275
|
+
preview: URL.createObjectURL(blob)
|
|
1276
|
+
}));
|
|
1277
|
+
setComposeShots((prev) => [...prev, ...incoming]);
|
|
1278
|
+
}
|
|
1279
|
+
if (files.length > batch.length) {
|
|
1280
|
+
setAttachError(
|
|
1281
|
+
strings["form.screenshot.error_count"].replace("{max}", String(MAX_SCREENSHOTS))
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
const handleAttachChange = (e) => {
|
|
1286
|
+
const input = e.target;
|
|
1287
|
+
const files = Array.from(input.files ?? []);
|
|
1288
|
+
if (files.length) acceptComposeFiles(files);
|
|
1289
|
+
input.value = "";
|
|
1290
|
+
};
|
|
1291
|
+
const removeComposeShot = (index) => {
|
|
1292
|
+
const shot = composeShots[index];
|
|
1293
|
+
if (shot) URL.revokeObjectURL(shot.preview);
|
|
1294
|
+
setComposeShots((prev) => prev.filter((_, i) => i !== index));
|
|
1295
|
+
setAttachError("");
|
|
1296
|
+
};
|
|
1174
1297
|
const fetchDetail = async () => {
|
|
1175
1298
|
try {
|
|
1176
1299
|
const next = await api.getReport(reportId, externalId);
|
|
@@ -1195,13 +1318,23 @@ function ReportDetailView({
|
|
|
1195
1318
|
};
|
|
1196
1319
|
}, [reportId, externalId]);
|
|
1197
1320
|
const handleSend = async () => {
|
|
1198
|
-
if (!composeBody.trim() || sending) return;
|
|
1321
|
+
if (!composeBody.trim() && composeShots.length === 0 || sending) return;
|
|
1199
1322
|
setSending(true);
|
|
1200
1323
|
try {
|
|
1201
1324
|
const nonce = `${reportId}:${Date.now()}`;
|
|
1202
|
-
const
|
|
1325
|
+
const attachments = composeShots.map((s) => s.blob);
|
|
1326
|
+
const created = await api.addComment(
|
|
1327
|
+
reportId,
|
|
1328
|
+
externalId,
|
|
1329
|
+
composeBody.trim(),
|
|
1330
|
+
nonce,
|
|
1331
|
+
attachments.length ? attachments : void 0
|
|
1332
|
+
);
|
|
1203
1333
|
if (!mountedRef.current) return;
|
|
1204
1334
|
setComposeBody("");
|
|
1335
|
+
composeShots.forEach((s) => URL.revokeObjectURL(s.preview));
|
|
1336
|
+
setComposeShots([]);
|
|
1337
|
+
setAttachError("");
|
|
1205
1338
|
setDetail(
|
|
1206
1339
|
(prev) => prev ? { ...prev, comments: appendComment(prev.comments, created) } : prev
|
|
1207
1340
|
);
|
|
@@ -1214,6 +1347,47 @@ function ReportDetailView({
|
|
|
1214
1347
|
if (mountedRef.current) setSending(false);
|
|
1215
1348
|
}
|
|
1216
1349
|
};
|
|
1350
|
+
const handleCopyLink = async () => {
|
|
1351
|
+
if (!buildPermalink || !detail) return;
|
|
1352
|
+
try {
|
|
1353
|
+
await navigator.clipboard.writeText(buildPermalink(detail.id));
|
|
1354
|
+
if (!mountedRef.current) return;
|
|
1355
|
+
setCopied(true);
|
|
1356
|
+
setTimeout(() => {
|
|
1357
|
+
if (mountedRef.current) setCopied(false);
|
|
1358
|
+
}, 2e3);
|
|
1359
|
+
} catch {
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
const startEdit = () => {
|
|
1363
|
+
if (!detail) return;
|
|
1364
|
+
setEditBody(detail.description);
|
|
1365
|
+
setEditError(false);
|
|
1366
|
+
setEditing(true);
|
|
1367
|
+
};
|
|
1368
|
+
const cancelEdit = () => {
|
|
1369
|
+
setEditing(false);
|
|
1370
|
+
setEditError(false);
|
|
1371
|
+
};
|
|
1372
|
+
const handleSaveEdit = async () => {
|
|
1373
|
+
if (!detail || savingEdit) return;
|
|
1374
|
+
const next = editBody.trim();
|
|
1375
|
+
if (!next) return;
|
|
1376
|
+
setSavingEdit(true);
|
|
1377
|
+
setEditError(false);
|
|
1378
|
+
try {
|
|
1379
|
+
const updated = await api.editDescription(reportId, externalId, next);
|
|
1380
|
+
if (!mountedRef.current) return;
|
|
1381
|
+
setDetail(updated);
|
|
1382
|
+
setEditing(false);
|
|
1383
|
+
} catch (err) {
|
|
1384
|
+
if (typeof console !== "undefined") console.warn("[mhosaic] editDescription:", err);
|
|
1385
|
+
if (!mountedRef.current) return;
|
|
1386
|
+
setEditError(true);
|
|
1387
|
+
} finally {
|
|
1388
|
+
if (mountedRef.current) setSavingEdit(false);
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1217
1391
|
const handleClose = async () => {
|
|
1218
1392
|
if (closing || reopening) return;
|
|
1219
1393
|
setClosing(true);
|
|
@@ -1299,7 +1473,63 @@ function ReportDetailView({
|
|
|
1299
1473
|
] }),
|
|
1300
1474
|
/* @__PURE__ */ jsxs3("div", { class: "report-detail-body", children: [
|
|
1301
1475
|
/* @__PURE__ */ jsx2(ContextBlock, { detail, strings }),
|
|
1302
|
-
/* @__PURE__ */
|
|
1476
|
+
editing ? /* @__PURE__ */ jsxs3("div", { class: "report-detail-edit", children: [
|
|
1477
|
+
/* @__PURE__ */ jsx2(
|
|
1478
|
+
"textarea",
|
|
1479
|
+
{
|
|
1480
|
+
class: "report-detail-edit-input",
|
|
1481
|
+
value: editBody,
|
|
1482
|
+
onInput: (e) => {
|
|
1483
|
+
setEditBody(e.target.value);
|
|
1484
|
+
setEditError(false);
|
|
1485
|
+
},
|
|
1486
|
+
disabled: savingEdit
|
|
1487
|
+
}
|
|
1488
|
+
),
|
|
1489
|
+
editError && /* @__PURE__ */ jsx2("p", { class: "error", role: "alert", children: strings["detail.edit_failed"] }),
|
|
1490
|
+
/* @__PURE__ */ jsxs3("div", { class: "report-detail-edit-actions", children: [
|
|
1491
|
+
/* @__PURE__ */ jsx2(
|
|
1492
|
+
"button",
|
|
1493
|
+
{
|
|
1494
|
+
type: "button",
|
|
1495
|
+
class: "btn btn--ghost",
|
|
1496
|
+
onClick: cancelEdit,
|
|
1497
|
+
disabled: savingEdit,
|
|
1498
|
+
children: strings["detail.edit_cancel"]
|
|
1499
|
+
}
|
|
1500
|
+
),
|
|
1501
|
+
/* @__PURE__ */ jsx2(
|
|
1502
|
+
"button",
|
|
1503
|
+
{
|
|
1504
|
+
type: "button",
|
|
1505
|
+
class: "btn btn--primary",
|
|
1506
|
+
onClick: handleSaveEdit,
|
|
1507
|
+
disabled: savingEdit || !editBody.trim(),
|
|
1508
|
+
children: savingEdit ? strings["detail.edit_saving"] : strings["detail.edit_save"]
|
|
1509
|
+
}
|
|
1510
|
+
)
|
|
1511
|
+
] })
|
|
1512
|
+
] }) : /* @__PURE__ */ jsxs3("div", { class: "report-detail-description-row", children: [
|
|
1513
|
+
/* @__PURE__ */ jsx2("p", { class: "report-detail-description", children: linkifySeq(detail.description, onOpenSeq) }),
|
|
1514
|
+
isMine && /* @__PURE__ */ jsx2(
|
|
1515
|
+
"button",
|
|
1516
|
+
{
|
|
1517
|
+
type: "button",
|
|
1518
|
+
class: "btn btn--ghost report-detail-edit",
|
|
1519
|
+
onClick: startEdit,
|
|
1520
|
+
children: strings["detail.edit"]
|
|
1521
|
+
}
|
|
1522
|
+
)
|
|
1523
|
+
] }),
|
|
1524
|
+
buildPermalink && /* @__PURE__ */ jsx2("div", { class: "report-detail-permalink", children: /* @__PURE__ */ jsx2(
|
|
1525
|
+
"button",
|
|
1526
|
+
{
|
|
1527
|
+
type: "button",
|
|
1528
|
+
class: "btn btn--ghost report-detail-copy-link",
|
|
1529
|
+
onClick: handleCopyLink,
|
|
1530
|
+
children: copied ? strings["detail.copied"] : strings["detail.copy_link"]
|
|
1531
|
+
}
|
|
1532
|
+
) }),
|
|
1303
1533
|
(detail.screenshots?.length ? detail.screenshots.map((s) => s.url) : [detail.screenshot_url]).filter((url) => Boolean(url)).map((url) => {
|
|
1304
1534
|
const safeHref = safeExternalHref(url);
|
|
1305
1535
|
return safeHref ? /* @__PURE__ */ jsx2(
|
|
@@ -1334,7 +1564,44 @@ function ReportDetailView({
|
|
|
1334
1564
|
disabled: sending
|
|
1335
1565
|
}
|
|
1336
1566
|
),
|
|
1567
|
+
composeShots.length > 0 && /* @__PURE__ */ jsx2("div", { class: "compose-attachments", children: composeShots.map((s, i) => /* @__PURE__ */ jsxs3("div", { class: "compose-attachment", children: [
|
|
1568
|
+
/* @__PURE__ */ jsx2("img", { src: s.preview, alt: strings["detail.attachment_alt"] }),
|
|
1569
|
+
/* @__PURE__ */ jsx2(
|
|
1570
|
+
"button",
|
|
1571
|
+
{
|
|
1572
|
+
type: "button",
|
|
1573
|
+
class: "compose-attachment-remove",
|
|
1574
|
+
"aria-label": strings["detail.attach_remove"],
|
|
1575
|
+
onClick: () => removeComposeShot(i),
|
|
1576
|
+
disabled: sending,
|
|
1577
|
+
children: "\xD7"
|
|
1578
|
+
}
|
|
1579
|
+
)
|
|
1580
|
+
] }, s.preview)) }),
|
|
1581
|
+
attachError && /* @__PURE__ */ jsx2("p", { class: "compose-attach-error", role: "alert", children: attachError }),
|
|
1582
|
+
/* @__PURE__ */ jsx2(
|
|
1583
|
+
"input",
|
|
1584
|
+
{
|
|
1585
|
+
ref: fileInputRef,
|
|
1586
|
+
type: "file",
|
|
1587
|
+
accept: "image/png,image/jpeg,image/webp",
|
|
1588
|
+
multiple: true,
|
|
1589
|
+
class: "compose-attach-input",
|
|
1590
|
+
onChange: handleAttachChange,
|
|
1591
|
+
hidden: true
|
|
1592
|
+
}
|
|
1593
|
+
),
|
|
1337
1594
|
/* @__PURE__ */ jsxs3("div", { class: "report-compose-actions", children: [
|
|
1595
|
+
/* @__PURE__ */ jsx2(
|
|
1596
|
+
"button",
|
|
1597
|
+
{
|
|
1598
|
+
type: "button",
|
|
1599
|
+
class: "btn btn--ghost compose-attach-btn",
|
|
1600
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1601
|
+
disabled: sending || composeShots.length >= MAX_SCREENSHOTS,
|
|
1602
|
+
children: strings["detail.attach_add"]
|
|
1603
|
+
}
|
|
1604
|
+
),
|
|
1338
1605
|
showCloseCta && /* @__PURE__ */ jsx2(
|
|
1339
1606
|
"button",
|
|
1340
1607
|
{
|
|
@@ -1361,7 +1628,7 @@ function ReportDetailView({
|
|
|
1361
1628
|
type: "button",
|
|
1362
1629
|
class: "btn btn--primary",
|
|
1363
1630
|
onClick: handleSend,
|
|
1364
|
-
disabled: !composeBody.trim() || sending,
|
|
1631
|
+
disabled: !composeBody.trim() && composeShots.length === 0 || sending,
|
|
1365
1632
|
children: sending ? strings["detail.compose_sending"] : strings["detail.compose_send"]
|
|
1366
1633
|
}
|
|
1367
1634
|
)
|
|
@@ -3096,12 +3363,34 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
|
|
|
3096
3363
|
// src/widget/MineList.tsx
|
|
3097
3364
|
import { jsx as jsx10, jsxs as jsxs11 } from "preact/jsx-runtime";
|
|
3098
3365
|
var POLL_MS4 = 3e4;
|
|
3099
|
-
function MineList({ api, externalId, strings, onSelect }) {
|
|
3366
|
+
function MineList({ api, externalId, strings, onSelect, onOpenSeq }) {
|
|
3100
3367
|
const [rows, setRows] = useState6(null);
|
|
3101
3368
|
const [error, setError] = useState6(null);
|
|
3102
3369
|
const [refreshing, setRefreshing] = useState6(false);
|
|
3103
3370
|
const [filter, setFilter] = useState6("all");
|
|
3371
|
+
const [jumpValue, setJumpValue] = useState6("");
|
|
3372
|
+
const [jumpError, setJumpError] = useState6(false);
|
|
3373
|
+
const [jumping, setJumping] = useState6(false);
|
|
3104
3374
|
const mountedRef = useRef5(true);
|
|
3375
|
+
const handleJump = async (e) => {
|
|
3376
|
+
e.preventDefault();
|
|
3377
|
+
if (!onOpenSeq || jumping) return;
|
|
3378
|
+
const seq = parseInt(jumpValue.replace(/^#/, "").trim(), 10);
|
|
3379
|
+
if (!Number.isInteger(seq) || seq <= 0) {
|
|
3380
|
+
setJumpError(true);
|
|
3381
|
+
return;
|
|
3382
|
+
}
|
|
3383
|
+
setJumping(true);
|
|
3384
|
+
setJumpError(false);
|
|
3385
|
+
try {
|
|
3386
|
+
const found = await onOpenSeq(seq);
|
|
3387
|
+
if (!mountedRef.current) return;
|
|
3388
|
+
if (found) setJumpValue("");
|
|
3389
|
+
else setJumpError(true);
|
|
3390
|
+
} finally {
|
|
3391
|
+
if (mountedRef.current) setJumping(false);
|
|
3392
|
+
}
|
|
3393
|
+
};
|
|
3105
3394
|
const fetchRows = async () => {
|
|
3106
3395
|
setRefreshing(true);
|
|
3107
3396
|
setError(null);
|
|
@@ -3134,19 +3423,40 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
3134
3423
|
return /* @__PURE__ */ jsxs11("div", { class: "mine-list", children: [
|
|
3135
3424
|
/* @__PURE__ */ jsxs11("div", { class: "mine-list-header", children: [
|
|
3136
3425
|
/* @__PURE__ */ jsx10("h2", { children: strings["tab.mine"] }),
|
|
3137
|
-
/* @__PURE__ */
|
|
3138
|
-
"
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3426
|
+
/* @__PURE__ */ jsxs11("div", { class: "mine-list-header-actions", children: [
|
|
3427
|
+
onOpenSeq && /* @__PURE__ */ jsxs11("form", { class: "mine-jump", onSubmit: handleJump, children: [
|
|
3428
|
+
/* @__PURE__ */ jsx10(
|
|
3429
|
+
"input",
|
|
3430
|
+
{
|
|
3431
|
+
type: "text",
|
|
3432
|
+
inputMode: "numeric",
|
|
3433
|
+
value: jumpValue,
|
|
3434
|
+
placeholder: strings["mine.jump.placeholder"],
|
|
3435
|
+
"aria-label": strings["mine.jump.aria"],
|
|
3436
|
+
onInput: (e) => {
|
|
3437
|
+
setJumpValue(e.target.value);
|
|
3438
|
+
setJumpError(false);
|
|
3439
|
+
},
|
|
3440
|
+
disabled: jumping
|
|
3441
|
+
}
|
|
3442
|
+
),
|
|
3443
|
+
/* @__PURE__ */ jsx10("button", { type: "submit", class: "btn", disabled: jumping || !jumpValue.trim(), children: strings["mine.jump.go"] })
|
|
3444
|
+
] }),
|
|
3445
|
+
/* @__PURE__ */ jsx10(
|
|
3446
|
+
"button",
|
|
3447
|
+
{
|
|
3448
|
+
type: "button",
|
|
3449
|
+
class: "btn",
|
|
3450
|
+
onClick: () => {
|
|
3451
|
+
void fetchRows();
|
|
3452
|
+
},
|
|
3453
|
+
disabled: refreshing,
|
|
3454
|
+
children: refreshing ? strings["mine.loading"] : strings["mine.refresh"]
|
|
3455
|
+
}
|
|
3456
|
+
)
|
|
3457
|
+
] })
|
|
3149
3458
|
] }),
|
|
3459
|
+
jumpError && /* @__PURE__ */ jsx10("div", { class: "mine-jump-error error", role: "alert", children: strings["mine.jump.not_found"] }),
|
|
3150
3460
|
rows && rows.length > 0 && /* @__PURE__ */ jsx10(KpiStrip, { rows, filter, onFilter: setFilter, strings }),
|
|
3151
3461
|
isLoading && /* @__PURE__ */ jsx10("div", { class: "mine-loading", children: strings["mine.loading"] }),
|
|
3152
3462
|
error && /* @__PURE__ */ jsx10("div", { class: "error", children: error }),
|
|
@@ -4137,6 +4447,31 @@ var WIDGET_STYLES = `
|
|
|
4137
4447
|
justify-content: space-between;
|
|
4138
4448
|
}
|
|
4139
4449
|
.mine-list-header h2 { margin: 0; font-size: var(--mfb-text-lg); font-weight: 600; letter-spacing: -0.01em; }
|
|
4450
|
+
/* #85 \u2014 header actions: jump-by-#seq box + refresh. */
|
|
4451
|
+
.mine-list-header-actions {
|
|
4452
|
+
display: flex;
|
|
4453
|
+
align-items: center;
|
|
4454
|
+
gap: 6px;
|
|
4455
|
+
}
|
|
4456
|
+
.mine-jump {
|
|
4457
|
+
display: flex;
|
|
4458
|
+
align-items: center;
|
|
4459
|
+
gap: 4px;
|
|
4460
|
+
}
|
|
4461
|
+
.mine-jump input {
|
|
4462
|
+
font: inherit;
|
|
4463
|
+
font-size: 13px;
|
|
4464
|
+
width: 56px;
|
|
4465
|
+
padding: 5px 8px;
|
|
4466
|
+
border: 1px solid var(--mfb-border);
|
|
4467
|
+
border-radius: var(--mfb-radius);
|
|
4468
|
+
background: var(--mfb-surface);
|
|
4469
|
+
color: inherit;
|
|
4470
|
+
}
|
|
4471
|
+
.mine-jump-error {
|
|
4472
|
+
margin-top: 6px;
|
|
4473
|
+
font-size: 12px;
|
|
4474
|
+
}
|
|
4140
4475
|
.mine-loading { color: var(--mfb-text-muted); font-size: 13px; }
|
|
4141
4476
|
.mine-empty {
|
|
4142
4477
|
text-align: center;
|
|
@@ -4342,12 +4677,77 @@ var WIDGET_STYLES = `
|
|
|
4342
4677
|
.comment-author--staff { color: #1e40af; }
|
|
4343
4678
|
.comment-author--system { color: var(--mfb-text-muted); }
|
|
4344
4679
|
.comment-body { white-space: pre-wrap; }
|
|
4680
|
+
/* #91 \u2014 image attachments rendered inside a comment bubble. */
|
|
4681
|
+
.comment-attachments {
|
|
4682
|
+
display: flex;
|
|
4683
|
+
flex-wrap: wrap;
|
|
4684
|
+
gap: 6px;
|
|
4685
|
+
margin-top: 6px;
|
|
4686
|
+
}
|
|
4687
|
+
.comment-attachment {
|
|
4688
|
+
display: block;
|
|
4689
|
+
line-height: 0;
|
|
4690
|
+
border-radius: 8px;
|
|
4691
|
+
overflow: hidden;
|
|
4692
|
+
border: 1px solid var(--mfb-border);
|
|
4693
|
+
}
|
|
4694
|
+
.comment-attachment img {
|
|
4695
|
+
width: 96px;
|
|
4696
|
+
height: 96px;
|
|
4697
|
+
object-fit: cover;
|
|
4698
|
+
display: block;
|
|
4699
|
+
}
|
|
4345
4700
|
.comment-time {
|
|
4346
4701
|
font-size: 10px;
|
|
4347
4702
|
margin-top: 4px;
|
|
4348
4703
|
opacity: 0.7;
|
|
4349
4704
|
}
|
|
4350
4705
|
|
|
4706
|
+
/* F4 \u2014 author edits the description in place. */
|
|
4707
|
+
.report-detail-description-row {
|
|
4708
|
+
display: flex;
|
|
4709
|
+
align-items: flex-start;
|
|
4710
|
+
gap: 8px;
|
|
4711
|
+
}
|
|
4712
|
+
.report-detail-description-row .report-detail-description {
|
|
4713
|
+
flex: 1;
|
|
4714
|
+
margin: 0;
|
|
4715
|
+
}
|
|
4716
|
+
.report-detail-edit {
|
|
4717
|
+
display: flex;
|
|
4718
|
+
flex-direction: column;
|
|
4719
|
+
gap: 6px;
|
|
4720
|
+
}
|
|
4721
|
+
.report-detail-edit-input {
|
|
4722
|
+
font: inherit;
|
|
4723
|
+
font-size: 13px;
|
|
4724
|
+
padding: 8px 10px;
|
|
4725
|
+
border: 1px solid var(--mfb-border);
|
|
4726
|
+
border-radius: var(--mfb-radius);
|
|
4727
|
+
background: var(--mfb-surface);
|
|
4728
|
+
color: inherit;
|
|
4729
|
+
min-height: 72px;
|
|
4730
|
+
resize: vertical;
|
|
4731
|
+
}
|
|
4732
|
+
.report-detail-edit-actions {
|
|
4733
|
+
display: flex;
|
|
4734
|
+
justify-content: flex-end;
|
|
4735
|
+
gap: 6px;
|
|
4736
|
+
}
|
|
4737
|
+
.report-detail-edit.btn,
|
|
4738
|
+
button.report-detail-edit {
|
|
4739
|
+
flex: 0 0 auto;
|
|
4740
|
+
font-size: 12px;
|
|
4741
|
+
padding: 4px 8px;
|
|
4742
|
+
}
|
|
4743
|
+
/* #85 \u2014 shareable-permalink affordance under the description. */
|
|
4744
|
+
.report-detail-permalink {
|
|
4745
|
+
margin: -2px 0 6px;
|
|
4746
|
+
}
|
|
4747
|
+
.report-detail-copy-link {
|
|
4748
|
+
font-size: 12px;
|
|
4749
|
+
padding: 4px 8px;
|
|
4750
|
+
}
|
|
4351
4751
|
.report-compose {
|
|
4352
4752
|
display: flex;
|
|
4353
4753
|
flex-direction: column;
|
|
@@ -4370,6 +4770,50 @@ var WIDGET_STYLES = `
|
|
|
4370
4770
|
justify-content: flex-end;
|
|
4371
4771
|
gap: 6px;
|
|
4372
4772
|
}
|
|
4773
|
+
/* #91 \u2014 pending image attachments in the reply compose box. */
|
|
4774
|
+
.compose-attachments {
|
|
4775
|
+
display: flex;
|
|
4776
|
+
flex-wrap: wrap;
|
|
4777
|
+
gap: 6px;
|
|
4778
|
+
}
|
|
4779
|
+
.compose-attachment {
|
|
4780
|
+
position: relative;
|
|
4781
|
+
width: 64px;
|
|
4782
|
+
height: 64px;
|
|
4783
|
+
border-radius: 8px;
|
|
4784
|
+
overflow: hidden;
|
|
4785
|
+
border: 1px solid var(--mfb-border);
|
|
4786
|
+
}
|
|
4787
|
+
.compose-attachment img {
|
|
4788
|
+
width: 100%;
|
|
4789
|
+
height: 100%;
|
|
4790
|
+
object-fit: cover;
|
|
4791
|
+
display: block;
|
|
4792
|
+
}
|
|
4793
|
+
.compose-attachment-remove {
|
|
4794
|
+
position: absolute;
|
|
4795
|
+
top: 2px;
|
|
4796
|
+
right: 2px;
|
|
4797
|
+
width: 20px;
|
|
4798
|
+
height: 20px;
|
|
4799
|
+
display: grid;
|
|
4800
|
+
place-items: center;
|
|
4801
|
+
padding: 0;
|
|
4802
|
+
background: rgba(255, 255, 255, 0.92);
|
|
4803
|
+
border: 1px solid var(--mfb-border);
|
|
4804
|
+
border-radius: 999px;
|
|
4805
|
+
font-size: 15px;
|
|
4806
|
+
line-height: 1;
|
|
4807
|
+
cursor: pointer;
|
|
4808
|
+
color: #111827;
|
|
4809
|
+
}
|
|
4810
|
+
.compose-attachment-remove:hover { background: #fff; }
|
|
4811
|
+
.compose-attach-btn { margin-right: auto; }
|
|
4812
|
+
.compose-attach-error {
|
|
4813
|
+
margin: 0;
|
|
4814
|
+
font-size: 12px;
|
|
4815
|
+
color: var(--mfb-danger, #b91c1c);
|
|
4816
|
+
}
|
|
4373
4817
|
|
|
4374
4818
|
/* v0.15.3 \u2014 teammate-viewing notice. Shown in place of the compose
|
|
4375
4819
|
* box when a non-submitter opens a project-wide row from the Board
|
|
@@ -5342,6 +5786,23 @@ function mountWidget(options) {
|
|
|
5342
5786
|
}
|
|
5343
5787
|
}, []);
|
|
5344
5788
|
const externalId = options.getExternalId?.();
|
|
5789
|
+
const openSeq = (seq) => {
|
|
5790
|
+
if (!options.api || !externalId) return Promise.resolve(false);
|
|
5791
|
+
return options.api.getReportBySeq(seq, externalId).then((r) => {
|
|
5792
|
+
rerender({ ...currentState, open: true, tab: "mine", selectedReportId: r.id });
|
|
5793
|
+
return true;
|
|
5794
|
+
}).catch(() => false);
|
|
5795
|
+
};
|
|
5796
|
+
const buildPermalink = options.deepLinkParam === false ? void 0 : (id) => {
|
|
5797
|
+
const param = options.deepLinkParam || "mhfeedback";
|
|
5798
|
+
try {
|
|
5799
|
+
const u = new URL(window.location.href);
|
|
5800
|
+
u.searchParams.set(param, id);
|
|
5801
|
+
return u.toString();
|
|
5802
|
+
} catch {
|
|
5803
|
+
return "";
|
|
5804
|
+
}
|
|
5805
|
+
};
|
|
5345
5806
|
const pageActivityEnabled = options.showPageActivity !== false;
|
|
5346
5807
|
const pageSignal = usePageOpenCount({
|
|
5347
5808
|
...options.api !== void 0 && { api: options.api },
|
|
@@ -5566,7 +6027,8 @@ function mountWidget(options) {
|
|
|
5566
6027
|
api: options.api,
|
|
5567
6028
|
externalId,
|
|
5568
6029
|
strings: options.strings,
|
|
5569
|
-
onSelect: (row) => rerender({ ...currentState, selectedReportId: row.id })
|
|
6030
|
+
onSelect: (row) => rerender({ ...currentState, selectedReportId: row.id }),
|
|
6031
|
+
onOpenSeq: openSeq
|
|
5570
6032
|
}
|
|
5571
6033
|
),
|
|
5572
6034
|
(state.tab === "mine" || state.tab === "changelog") && options.api && externalId && state.selectedReportId && /* @__PURE__ */ jsx14(
|
|
@@ -5577,12 +6039,8 @@ function mountWidget(options) {
|
|
|
5577
6039
|
reportId: state.selectedReportId,
|
|
5578
6040
|
strings: options.strings,
|
|
5579
6041
|
onBack: () => rerender(clearSelected({ ...currentState })),
|
|
5580
|
-
onOpenSeq:
|
|
5581
|
-
|
|
5582
|
-
(r) => rerender({ ...currentState, selectedReportId: r.id })
|
|
5583
|
-
).catch(() => {
|
|
5584
|
-
});
|
|
5585
|
-
}
|
|
6042
|
+
onOpenSeq: openSeq,
|
|
6043
|
+
...buildPermalink && { buildPermalink }
|
|
5586
6044
|
}
|
|
5587
6045
|
),
|
|
5588
6046
|
state.tab === "changelog" && options.api && externalId && !state.selectedReportId && /* @__PURE__ */ jsx14(
|
|
@@ -5612,6 +6070,25 @@ function mountWidget(options) {
|
|
|
5612
6070
|
] });
|
|
5613
6071
|
}
|
|
5614
6072
|
rerender(currentState);
|
|
6073
|
+
function openReportRef(ref) {
|
|
6074
|
+
const api = options.api;
|
|
6075
|
+
const externalId = options.getExternalId?.();
|
|
6076
|
+
if (!api || !externalId || !ref) return;
|
|
6077
|
+
const seq = /^\d+$/.test(ref) ? parseInt(ref, 10) : null;
|
|
6078
|
+
const resolve = seq !== null ? api.getReportBySeq(seq, externalId) : api.getReport(ref, externalId);
|
|
6079
|
+
void resolve.then(
|
|
6080
|
+
(r) => rerender({ ...currentState, open: true, tab: "mine", selectedReportId: r.id })
|
|
6081
|
+
).catch(() => {
|
|
6082
|
+
});
|
|
6083
|
+
}
|
|
6084
|
+
if (options.deepLinkParam !== false) {
|
|
6085
|
+
try {
|
|
6086
|
+
const param = options.deepLinkParam || "mhfeedback";
|
|
6087
|
+
const value = new URLSearchParams(window.location.search).get(param);
|
|
6088
|
+
if (value) openReportRef(value.trim());
|
|
6089
|
+
} catch {
|
|
6090
|
+
}
|
|
6091
|
+
}
|
|
5615
6092
|
return {
|
|
5616
6093
|
open() {
|
|
5617
6094
|
openWidget(options.getExternalId?.());
|
|
@@ -5698,8 +6175,8 @@ function createFeedback(config) {
|
|
|
5698
6175
|
capture_method: captureMethod,
|
|
5699
6176
|
technical_context
|
|
5700
6177
|
};
|
|
5701
|
-
if ("0.
|
|
5702
|
-
payload.widget_version = "0.
|
|
6178
|
+
if ("0.29.0") {
|
|
6179
|
+
payload.widget_version = "0.29.0";
|
|
5703
6180
|
}
|
|
5704
6181
|
if (manualScreenshots?.length) {
|
|
5705
6182
|
payload.screenshots = manualScreenshots;
|
|
@@ -5825,4 +6302,4 @@ function createFeedback(config) {
|
|
|
5825
6302
|
export {
|
|
5826
6303
|
createFeedback
|
|
5827
6304
|
};
|
|
5828
|
-
//# sourceMappingURL=chunk-
|
|
6305
|
+
//# sourceMappingURL=chunk-7FGWO6KO.mjs.map
|