@mhosaic/feedback 0.27.1 → 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-ZFXGV5W5.mjs → chunk-7FGWO6KO.mjs} +904 -198
- package/dist/chunk-7FGWO6KO.mjs.map +1 -0
- package/dist/embed.min.js +179 -5
- package/dist/embed.min.js.map +1 -1
- package/dist/error-tracking.d.ts +1 -1
- package/dist/{index-BYDyenfe.d.ts → index-Dp0nfVYj.d.ts} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/loader/react.d.ts +1 -1
- package/dist/loader.d.ts +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.mjs +1 -1
- package/dist/replay.d.ts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/{types-C4iSSmUD.d.ts → types-Cg1v8AbB.d.ts} +3 -0
- package/dist/webvitals.d.ts +1 -1
- package/dist/widget.min.js +180 -6
- package/dist/widget.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZFXGV5W5.mjs.map +0 -1
|
@@ -32,6 +32,42 @@ function assertSafeEndpoint(endpoint) {
|
|
|
32
32
|
`[mhosaic-feedback] \`endpoint\` must use https:// (got ${parsed.protocol}//${parsed.hostname}). http:// is only allowed for localhost in dev.`
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
|
+
var RateLimitError = class extends Error {
|
|
36
|
+
retryAfterSeconds;
|
|
37
|
+
constructor(retryAfterSeconds) {
|
|
38
|
+
super(`rate limited; retry after ${retryAfterSeconds}s`);
|
|
39
|
+
this.name = "RateLimitError";
|
|
40
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
function parseRetryAfter(response) {
|
|
44
|
+
const raw = response.headers.get("Retry-After");
|
|
45
|
+
const n = raw ? Number.parseInt(raw, 10) : NaN;
|
|
46
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
47
|
+
}
|
|
48
|
+
async function ensureReadable(response, label) {
|
|
49
|
+
if (response.status === 429) throw new RateLimitError(parseRetryAfter(response));
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const text = await response.text().catch(() => "");
|
|
52
|
+
throw new Error(`${label} failed: ${response.status} ${text}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function readJsonArray(response, label) {
|
|
56
|
+
await ensureReadable(response, label);
|
|
57
|
+
const data = await response.json().catch(() => void 0);
|
|
58
|
+
if (!Array.isArray(data)) {
|
|
59
|
+
throw new Error(`${label}: unexpected response shape (expected an array)`);
|
|
60
|
+
}
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
async function readJsonObject(response, label) {
|
|
64
|
+
await ensureReadable(response, label);
|
|
65
|
+
const data = await response.json().catch(() => void 0);
|
|
66
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
67
|
+
throw new Error(`${label}: unexpected response shape (expected an object)`);
|
|
68
|
+
}
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
35
71
|
function createApiClient(options) {
|
|
36
72
|
const endpoint = (options.endpoint ?? "").replace(/\/+$/, "");
|
|
37
73
|
if (!endpoint) {
|
|
@@ -107,11 +143,7 @@ function createApiClient(options) {
|
|
|
107
143
|
headers: widgetHeaders(externalId)
|
|
108
144
|
});
|
|
109
145
|
if (response.status === 404) return [];
|
|
110
|
-
|
|
111
|
-
const text = await response.text().catch(() => "");
|
|
112
|
-
throw new Error(`listMine failed: ${response.status} ${text}`);
|
|
113
|
-
}
|
|
114
|
-
return response.json();
|
|
146
|
+
return readJsonArray(response, "listMine");
|
|
115
147
|
}
|
|
116
148
|
async function listChangelog(externalId) {
|
|
117
149
|
const response = await fetcher(
|
|
@@ -119,27 +151,34 @@ function createApiClient(options) {
|
|
|
119
151
|
{ method: "GET", headers: widgetHeaders(externalId) }
|
|
120
152
|
);
|
|
121
153
|
if (response.status === 404) return [];
|
|
122
|
-
|
|
123
|
-
const text = await response.text().catch(() => "");
|
|
124
|
-
throw new Error(`listChangelog failed: ${response.status} ${text}`);
|
|
125
|
-
}
|
|
126
|
-
return response.json();
|
|
154
|
+
return readJsonArray(response, "listChangelog");
|
|
127
155
|
}
|
|
128
156
|
async function getReport(reportId, externalId) {
|
|
129
157
|
const response = await fetcher(
|
|
130
158
|
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
|
|
131
159
|
{ method: "GET", headers: widgetHeaders(externalId) }
|
|
132
160
|
);
|
|
133
|
-
|
|
134
|
-
const text = await response.text().catch(() => "");
|
|
135
|
-
throw new Error(`getReport failed: ${response.status} ${text}`);
|
|
136
|
-
}
|
|
137
|
-
return response.json();
|
|
161
|
+
return readJsonObject(response, "getReport");
|
|
138
162
|
}
|
|
139
|
-
async function
|
|
163
|
+
async function getReportBySeq(seq, externalId) {
|
|
140
164
|
const response = await fetcher(
|
|
141
|
-
`${endpoint}/api/feedback/v1/reports/widget/${
|
|
142
|
-
{
|
|
165
|
+
`${endpoint}/api/feedback/v1/reports/widget/by-seq/${seq}/`,
|
|
166
|
+
{ method: "GET", headers: widgetHeaders(externalId) }
|
|
167
|
+
);
|
|
168
|
+
return readJsonObject(response, "getReportBySeq");
|
|
169
|
+
}
|
|
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 = {
|
|
143
182
|
method: "POST",
|
|
144
183
|
headers: {
|
|
145
184
|
...widgetHeaders(externalId),
|
|
@@ -149,7 +188,11 @@ function createApiClient(options) {
|
|
|
149
188
|
body,
|
|
150
189
|
...clientNonce !== void 0 && { client_nonce: clientNonce }
|
|
151
190
|
})
|
|
152
|
-
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const response = await fetcher(
|
|
194
|
+
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/comments/`,
|
|
195
|
+
init
|
|
153
196
|
);
|
|
154
197
|
if (!response.ok) {
|
|
155
198
|
const text = await response.text().catch(() => "");
|
|
@@ -157,6 +200,24 @@ function createApiClient(options) {
|
|
|
157
200
|
}
|
|
158
201
|
return response.json();
|
|
159
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
|
+
}
|
|
160
221
|
async function closeAsResolved(reportId, externalId) {
|
|
161
222
|
const response = await fetcher(
|
|
162
223
|
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
|
|
@@ -215,11 +276,11 @@ function createApiClient(options) {
|
|
|
215
276
|
if (response.status === 404) {
|
|
216
277
|
return { count: 0, next: null, previous: null, results: [] };
|
|
217
278
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
throw new Error(
|
|
279
|
+
const page = await readJsonObject(response, "listBoard");
|
|
280
|
+
if (!Array.isArray(page.results)) {
|
|
281
|
+
throw new Error("listBoard: unexpected response shape (missing results[])");
|
|
221
282
|
}
|
|
222
|
-
return
|
|
283
|
+
return page;
|
|
223
284
|
}
|
|
224
285
|
async function fetchBoardKpis(externalId, filters) {
|
|
225
286
|
const response = await fetcher(
|
|
@@ -229,11 +290,7 @@ function createApiClient(options) {
|
|
|
229
290
|
if (response.status === 404) {
|
|
230
291
|
return { total: 0, by_status: {}, resolution_rate: 0, scope: "mine" };
|
|
231
292
|
}
|
|
232
|
-
|
|
233
|
-
const text = await response.text().catch(() => "");
|
|
234
|
-
throw new Error(`fetchBoardKpis failed: ${response.status} ${text}`);
|
|
235
|
-
}
|
|
236
|
-
return response.json();
|
|
293
|
+
return readJsonObject(response, "fetchBoardKpis");
|
|
237
294
|
}
|
|
238
295
|
async function checkVisibility(externalId, email) {
|
|
239
296
|
const response = await fetcher(
|
|
@@ -262,7 +319,9 @@ function createApiClient(options) {
|
|
|
262
319
|
listMine,
|
|
263
320
|
listChangelog,
|
|
264
321
|
getReport,
|
|
322
|
+
getReportBySeq,
|
|
265
323
|
addComment,
|
|
324
|
+
editDescription,
|
|
266
325
|
closeAsResolved,
|
|
267
326
|
reopenUnresolved,
|
|
268
327
|
listBoard,
|
|
@@ -573,6 +632,11 @@ var DEFAULT_STRINGS = {
|
|
|
573
632
|
"form.close": "Close",
|
|
574
633
|
"form.submitting": "Sending\u2026",
|
|
575
634
|
"form.success": "Thanks \u2014 your feedback was sent.",
|
|
635
|
+
"form.success.seq": "Thanks \u2014 report #{seq} was sent. \u2713",
|
|
636
|
+
"form.discard.title": "Unsaved changes",
|
|
637
|
+
"form.discard.body": "Discard your feedback?",
|
|
638
|
+
"form.discard.keep": "Keep editing",
|
|
639
|
+
"form.discard.confirm": "Discard",
|
|
576
640
|
"form.error": "Could not send. Please try again.",
|
|
577
641
|
"form.description.required": "Please describe the issue before sending.",
|
|
578
642
|
"form.screenshot.label": "Screenshot",
|
|
@@ -585,6 +649,7 @@ var DEFAULT_STRINGS = {
|
|
|
585
649
|
"form.screenshot.error_size": "File too large (max {max} MB).",
|
|
586
650
|
"form.screenshot.error_count": "Too many screenshots (max {max}).",
|
|
587
651
|
"form.context.label": "Page",
|
|
652
|
+
"form.capture.notice": "Console, network activity and errors are captured automatically to help us debug.",
|
|
588
653
|
"type.bug": "Bug",
|
|
589
654
|
"type.feature": "Feature request",
|
|
590
655
|
"type.question": "Question",
|
|
@@ -663,6 +728,12 @@ var DEFAULT_STRINGS = {
|
|
|
663
728
|
"mine.refresh": "Refresh",
|
|
664
729
|
"mine.loading": "Loading\u2026",
|
|
665
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.",
|
|
735
|
+
"error.rate_limited": "Too many requests \u2014 retry in {seconds}s.",
|
|
736
|
+
"error.rate_limited_generic": "Too many requests \u2014 try again in a moment.",
|
|
666
737
|
"mine.replies_one": "1 reply",
|
|
667
738
|
"mine.replies_many": "{count} replies",
|
|
668
739
|
"mine.filter.empty": "No reports match this filter.",
|
|
@@ -676,6 +747,16 @@ var DEFAULT_STRINGS = {
|
|
|
676
747
|
"detail.compose_placeholder": "Add a follow-up reply\u2026",
|
|
677
748
|
"detail.compose_send": "Reply",
|
|
678
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.",
|
|
679
760
|
"detail.close_cta": "Mark as resolved",
|
|
680
761
|
"detail.close_busy": "Marking\u2026",
|
|
681
762
|
"detail.reopen_cta": "Still not fixed",
|
|
@@ -737,6 +818,11 @@ var FRENCH_STRINGS = {
|
|
|
737
818
|
"form.close": "Fermer",
|
|
738
819
|
"form.submitting": "Envoi\u2026",
|
|
739
820
|
"form.success": "Merci \u2014 votre commentaire a \xE9t\xE9 envoy\xE9.",
|
|
821
|
+
"form.success.seq": "Merci \u2014 rapport n\xB0{seq} envoy\xE9. \u2713",
|
|
822
|
+
"form.discard.title": "Modifications non enregistr\xE9es",
|
|
823
|
+
"form.discard.body": "Abandonner votre commentaire ?",
|
|
824
|
+
"form.discard.keep": "Continuer la saisie",
|
|
825
|
+
"form.discard.confirm": "Abandonner",
|
|
740
826
|
"form.error": "\xC9chec d\u2019envoi. Veuillez r\xE9essayer.",
|
|
741
827
|
"form.description.required": "D\xE9crivez le probl\xE8me avant d\u2019envoyer.",
|
|
742
828
|
"form.screenshot.label": "Capture d\u2019\xE9cran",
|
|
@@ -749,6 +835,7 @@ var FRENCH_STRINGS = {
|
|
|
749
835
|
"form.screenshot.error_size": "Fichier trop volumineux (max {max} Mo).",
|
|
750
836
|
"form.screenshot.error_count": "Trop de captures d\u2019\xE9cran (max {max}).",
|
|
751
837
|
"form.context.label": "Page",
|
|
838
|
+
"form.capture.notice": "La console, l\u2019activit\xE9 r\xE9seau et les erreurs sont captur\xE9es automatiquement pour faciliter le diagnostic.",
|
|
752
839
|
"type.bug": "Bogue",
|
|
753
840
|
"type.feature": "Suggestion",
|
|
754
841
|
"type.question": "Question",
|
|
@@ -827,6 +914,12 @@ var FRENCH_STRINGS = {
|
|
|
827
914
|
"mine.refresh": "Actualiser",
|
|
828
915
|
"mine.loading": "Chargement\u2026",
|
|
829
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.",
|
|
921
|
+
"error.rate_limited": "Trop de requ\xEAtes \u2014 r\xE9essaie dans {seconds} s.",
|
|
922
|
+
"error.rate_limited_generic": "Trop de requ\xEAtes \u2014 r\xE9essaie dans un instant.",
|
|
830
923
|
"mine.replies_one": "1 r\xE9ponse",
|
|
831
924
|
"mine.replies_many": "{count} r\xE9ponses",
|
|
832
925
|
"mine.filter.empty": "Aucun rapport ne correspond \xE0 ce filtre.",
|
|
@@ -840,6 +933,16 @@ var FRENCH_STRINGS = {
|
|
|
840
933
|
"detail.compose_placeholder": "Ajouter une r\xE9ponse\u2026",
|
|
841
934
|
"detail.compose_send": "R\xE9pondre",
|
|
842
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.",
|
|
843
946
|
"detail.close_cta": "Marquer comme r\xE9solu",
|
|
844
947
|
"detail.close_busy": "Validation\u2026",
|
|
845
948
|
"detail.reopen_cta": "Toujours pas r\xE9gl\xE9",
|
|
@@ -995,21 +1098,76 @@ function startPoll(tick, intervalMs) {
|
|
|
995
1098
|
};
|
|
996
1099
|
}
|
|
997
1100
|
|
|
1101
|
+
// src/widget/rateLimit.ts
|
|
1102
|
+
function rateLimitMessage(err, strings) {
|
|
1103
|
+
if (!(err instanceof RateLimitError)) return null;
|
|
1104
|
+
return err.retryAfterSeconds > 0 ? strings["error.rate_limited"].replace(
|
|
1105
|
+
"{seconds}",
|
|
1106
|
+
String(err.retryAfterSeconds)
|
|
1107
|
+
) : strings["error.rate_limited_generic"];
|
|
1108
|
+
}
|
|
1109
|
+
|
|
998
1110
|
// src/widget/ReportDetailView.tsx
|
|
999
1111
|
import { useEffect, useRef, useState } from "preact/hooks";
|
|
1000
1112
|
|
|
1113
|
+
// src/widget/linkifySeq.tsx
|
|
1114
|
+
import { jsxs } from "preact/jsx-runtime";
|
|
1115
|
+
var SEQ_RE = /#(\d+)/g;
|
|
1116
|
+
function linkifySeq(text, onOpenSeq) {
|
|
1117
|
+
const value = text ?? "";
|
|
1118
|
+
if (!value || !onOpenSeq) return [value];
|
|
1119
|
+
const parts = [];
|
|
1120
|
+
let last = 0;
|
|
1121
|
+
SEQ_RE.lastIndex = 0;
|
|
1122
|
+
let m;
|
|
1123
|
+
while ((m = SEQ_RE.exec(value)) !== null) {
|
|
1124
|
+
if (m.index > last) parts.push(value.slice(last, m.index));
|
|
1125
|
+
const seq = Number(m[1]);
|
|
1126
|
+
parts.push(
|
|
1127
|
+
/* @__PURE__ */ jsxs(
|
|
1128
|
+
"button",
|
|
1129
|
+
{
|
|
1130
|
+
type: "button",
|
|
1131
|
+
class: "seq-link",
|
|
1132
|
+
onClick: () => onOpenSeq(seq),
|
|
1133
|
+
"aria-label": `Open report #${seq}`,
|
|
1134
|
+
children: [
|
|
1135
|
+
"#",
|
|
1136
|
+
seq
|
|
1137
|
+
]
|
|
1138
|
+
}
|
|
1139
|
+
)
|
|
1140
|
+
);
|
|
1141
|
+
last = m.index + m[0].length;
|
|
1142
|
+
}
|
|
1143
|
+
if (last < value.length) parts.push(value.slice(last));
|
|
1144
|
+
return parts.length ? parts : [value];
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1001
1147
|
// src/widget/CommentBubble.tsx
|
|
1002
|
-
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
1003
|
-
function CommentBubble({ comment, strings }) {
|
|
1148
|
+
import { jsx, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
1149
|
+
function CommentBubble({ comment, strings, onOpenSeq }) {
|
|
1004
1150
|
const isMine = comment.is_mine;
|
|
1005
1151
|
const isAgent = !isMine && comment.author_source === "mcp";
|
|
1006
1152
|
const isSystem = !isMine && comment.author_source === "system";
|
|
1007
1153
|
const variant = isAgent ? "mcp" : isSystem ? "system" : "staff";
|
|
1008
1154
|
const labelKey = variant === "mcp" ? "detail.author.mcp" : variant === "system" ? "detail.author.system" : "detail.author.staff";
|
|
1009
1155
|
const label = comment.author_label || strings[labelKey];
|
|
1010
|
-
|
|
1156
|
+
const images = (comment.attachments ?? []).filter((a) => a.url);
|
|
1157
|
+
return /* @__PURE__ */ jsxs2("div", { class: `comment-bubble ${isMine ? "is-mine" : "is-other"}`, children: [
|
|
1011
1158
|
!isMine && label && /* @__PURE__ */ jsx("div", { class: `comment-author comment-author--${variant}`, children: label }),
|
|
1012
|
-
/* @__PURE__ */ jsx("div", { class: "comment-body", children: comment.body }),
|
|
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
|
+
)) }),
|
|
1013
1171
|
/* @__PURE__ */ jsx("div", { class: "comment-time", children: formatTime(comment.created_at) })
|
|
1014
1172
|
] });
|
|
1015
1173
|
}
|
|
@@ -1065,7 +1223,7 @@ function safeExternalHref(url) {
|
|
|
1065
1223
|
}
|
|
1066
1224
|
|
|
1067
1225
|
// src/widget/ReportDetailView.tsx
|
|
1068
|
-
import { Fragment, jsx as jsx2, jsxs as
|
|
1226
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
1069
1227
|
var POLL_MS = 3e4;
|
|
1070
1228
|
function ReportDetailView({
|
|
1071
1229
|
api,
|
|
@@ -1074,15 +1232,68 @@ function ReportDetailView({
|
|
|
1074
1232
|
strings,
|
|
1075
1233
|
onBack,
|
|
1076
1234
|
canModerate = true,
|
|
1077
|
-
variant = "modal"
|
|
1235
|
+
variant = "modal",
|
|
1236
|
+
onOpenSeq,
|
|
1237
|
+
buildPermalink
|
|
1078
1238
|
}) {
|
|
1079
1239
|
const [detail, setDetail] = useState(null);
|
|
1080
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);
|
|
1081
1246
|
const [composeBody, setComposeBody] = useState("");
|
|
1082
1247
|
const [sending, setSending] = useState(false);
|
|
1083
1248
|
const [closing, setClosing] = useState(false);
|
|
1084
1249
|
const [reopening, setReopening] = useState(false);
|
|
1250
|
+
const [composeShots, setComposeShots] = useState([]);
|
|
1251
|
+
const [attachError, setAttachError] = useState("");
|
|
1252
|
+
const fileInputRef = useRef(null);
|
|
1085
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
|
+
};
|
|
1086
1297
|
const fetchDetail = async () => {
|
|
1087
1298
|
try {
|
|
1088
1299
|
const next = await api.getReport(reportId, externalId);
|
|
@@ -1093,7 +1304,8 @@ function ReportDetailView({
|
|
|
1093
1304
|
} catch (err) {
|
|
1094
1305
|
if (typeof console !== "undefined") console.warn("[mhosaic] getReport:", err);
|
|
1095
1306
|
if (!mountedRef.current) return false;
|
|
1096
|
-
|
|
1307
|
+
const rl = rateLimitMessage(err, strings);
|
|
1308
|
+
setError(rl ?? "load_failed");
|
|
1097
1309
|
return false;
|
|
1098
1310
|
}
|
|
1099
1311
|
};
|
|
@@ -1106,13 +1318,23 @@ function ReportDetailView({
|
|
|
1106
1318
|
};
|
|
1107
1319
|
}, [reportId, externalId]);
|
|
1108
1320
|
const handleSend = async () => {
|
|
1109
|
-
if (!composeBody.trim() || sending) return;
|
|
1321
|
+
if (!composeBody.trim() && composeShots.length === 0 || sending) return;
|
|
1110
1322
|
setSending(true);
|
|
1111
1323
|
try {
|
|
1112
1324
|
const nonce = `${reportId}:${Date.now()}`;
|
|
1113
|
-
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
|
+
);
|
|
1114
1333
|
if (!mountedRef.current) return;
|
|
1115
1334
|
setComposeBody("");
|
|
1335
|
+
composeShots.forEach((s) => URL.revokeObjectURL(s.preview));
|
|
1336
|
+
setComposeShots([]);
|
|
1337
|
+
setAttachError("");
|
|
1116
1338
|
setDetail(
|
|
1117
1339
|
(prev) => prev ? { ...prev, comments: appendComment(prev.comments, created) } : prev
|
|
1118
1340
|
);
|
|
@@ -1125,6 +1347,47 @@ function ReportDetailView({
|
|
|
1125
1347
|
if (mountedRef.current) setSending(false);
|
|
1126
1348
|
}
|
|
1127
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
|
+
};
|
|
1128
1391
|
const handleClose = async () => {
|
|
1129
1392
|
if (closing || reopening) return;
|
|
1130
1393
|
setClosing(true);
|
|
@@ -1161,10 +1424,20 @@ function ReportDetailView({
|
|
|
1161
1424
|
return /* @__PURE__ */ jsx2("div", { class: "mine-loading", children: strings["mine.loading"] });
|
|
1162
1425
|
}
|
|
1163
1426
|
if (!detail) {
|
|
1164
|
-
|
|
1427
|
+
if (error && error !== "load_failed") {
|
|
1428
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-empty-state", role: "alert", children: [
|
|
1429
|
+
/* @__PURE__ */ jsx2("p", { class: "report-detail-empty-state-body", children: error }),
|
|
1430
|
+
/* @__PURE__ */ jsx2("button", { type: "button", class: "btn", onClick: () => void fetchDetail(), children: strings["board.retry"] }),
|
|
1431
|
+
/* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
|
|
1432
|
+
"\u2190 ",
|
|
1433
|
+
strings["detail.load_failed.cta"]
|
|
1434
|
+
] })
|
|
1435
|
+
] });
|
|
1436
|
+
}
|
|
1437
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-empty-state", role: "alert", children: [
|
|
1165
1438
|
/* @__PURE__ */ jsx2("p", { class: "report-detail-empty-state-title", children: strings["detail.load_failed.title"] }),
|
|
1166
1439
|
/* @__PURE__ */ jsx2("p", { class: "report-detail-empty-state-body", children: strings["detail.load_failed.body"] }),
|
|
1167
|
-
/* @__PURE__ */
|
|
1440
|
+
/* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
|
|
1168
1441
|
"\u2190 ",
|
|
1169
1442
|
strings["detail.load_failed.cta"]
|
|
1170
1443
|
] })
|
|
@@ -1174,16 +1447,16 @@ function ReportDetailView({
|
|
|
1174
1447
|
const canAct = detail.can_moderate ?? isMine;
|
|
1175
1448
|
const showCloseCta = canAct && detail.status === "awaiting_validation";
|
|
1176
1449
|
const showComposeBox = canAct;
|
|
1177
|
-
return /* @__PURE__ */
|
|
1178
|
-
variant === "modal" && /* @__PURE__ */
|
|
1179
|
-
/* @__PURE__ */
|
|
1450
|
+
return /* @__PURE__ */ jsxs3("div", { class: `report-detail variant-${variant}`, children: [
|
|
1451
|
+
variant === "modal" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header", children: [
|
|
1452
|
+
/* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
|
|
1180
1453
|
"\u2190 ",
|
|
1181
1454
|
strings["detail.back"]
|
|
1182
1455
|
] }),
|
|
1183
1456
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
|
|
1184
1457
|
] }),
|
|
1185
|
-
variant === "board" && /* @__PURE__ */
|
|
1186
|
-
/* @__PURE__ */
|
|
1458
|
+
variant === "board" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header report-detail-header--board", children: [
|
|
1459
|
+
/* @__PURE__ */ jsxs3(
|
|
1187
1460
|
"button",
|
|
1188
1461
|
{
|
|
1189
1462
|
type: "button",
|
|
@@ -1198,9 +1471,65 @@ function ReportDetailView({
|
|
|
1198
1471
|
),
|
|
1199
1472
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
|
|
1200
1473
|
] }),
|
|
1201
|
-
/* @__PURE__ */
|
|
1474
|
+
/* @__PURE__ */ jsxs3("div", { class: "report-detail-body", children: [
|
|
1202
1475
|
/* @__PURE__ */ jsx2(ContextBlock, { detail, strings }),
|
|
1203
|
-
/* @__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
|
+
) }),
|
|
1204
1533
|
(detail.screenshots?.length ? detail.screenshots.map((s) => s.url) : [detail.screenshot_url]).filter((url) => Boolean(url)).map((url) => {
|
|
1205
1534
|
const safeHref = safeExternalHref(url);
|
|
1206
1535
|
return safeHref ? /* @__PURE__ */ jsx2(
|
|
@@ -1215,10 +1544,17 @@ function ReportDetailView({
|
|
|
1215
1544
|
) : /* @__PURE__ */ jsx2("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy" }) });
|
|
1216
1545
|
}),
|
|
1217
1546
|
/* @__PURE__ */ jsx2("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
|
|
1218
|
-
detail.comments.length === 0 ? /* @__PURE__ */ jsx2("p", { class: "report-detail-empty", children: strings["detail.no_replies"] }) : /* @__PURE__ */ jsx2("ul", { class: "report-comments", children: detail.comments.map((c) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2(
|
|
1547
|
+
detail.comments.length === 0 ? /* @__PURE__ */ jsx2("p", { class: "report-detail-empty", children: strings["detail.no_replies"] }) : /* @__PURE__ */ jsx2("ul", { class: "report-comments", children: detail.comments.map((c) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2(
|
|
1548
|
+
CommentBubble,
|
|
1549
|
+
{
|
|
1550
|
+
comment: c,
|
|
1551
|
+
strings,
|
|
1552
|
+
...onOpenSeq && { onOpenSeq }
|
|
1553
|
+
}
|
|
1554
|
+
) })) }),
|
|
1219
1555
|
detail.status_history && detail.status_history.length > 0 && /* @__PURE__ */ jsx2(StatusHistorySection, { rows: detail.status_history, strings }),
|
|
1220
1556
|
detail.technical_context && /* @__PURE__ */ jsx2(TechnicalContextDrawer, { ctx: detail.technical_context, strings }),
|
|
1221
|
-
showComposeBox ? /* @__PURE__ */
|
|
1557
|
+
showComposeBox ? /* @__PURE__ */ jsxs3("div", { class: "report-compose", children: [
|
|
1222
1558
|
/* @__PURE__ */ jsx2(
|
|
1223
1559
|
"textarea",
|
|
1224
1560
|
{
|
|
@@ -1228,7 +1564,44 @@ function ReportDetailView({
|
|
|
1228
1564
|
disabled: sending
|
|
1229
1565
|
}
|
|
1230
1566
|
),
|
|
1231
|
-
/* @__PURE__ */
|
|
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
|
+
),
|
|
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
|
+
),
|
|
1232
1605
|
showCloseCta && /* @__PURE__ */ jsx2(
|
|
1233
1606
|
"button",
|
|
1234
1607
|
{
|
|
@@ -1255,7 +1628,7 @@ function ReportDetailView({
|
|
|
1255
1628
|
type: "button",
|
|
1256
1629
|
class: "btn btn--primary",
|
|
1257
1630
|
onClick: handleSend,
|
|
1258
|
-
disabled: !composeBody.trim() || sending,
|
|
1631
|
+
disabled: !composeBody.trim() && composeShots.length === 0 || sending,
|
|
1259
1632
|
children: sending ? strings["detail.compose_sending"] : strings["detail.compose_send"]
|
|
1260
1633
|
}
|
|
1261
1634
|
)
|
|
@@ -1278,19 +1651,19 @@ function appendComment(current, next) {
|
|
|
1278
1651
|
function ContextBlock({ detail, strings }) {
|
|
1279
1652
|
const captureKey = `detail.context.capture.${detail.capture_method}`;
|
|
1280
1653
|
const captureLabel = strings[captureKey] ?? detail.capture_method;
|
|
1281
|
-
return /* @__PURE__ */
|
|
1282
|
-
/* @__PURE__ */
|
|
1654
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-context", children: [
|
|
1655
|
+
/* @__PURE__ */ jsxs3("div", { class: "report-detail-context-pills", children: [
|
|
1283
1656
|
/* @__PURE__ */ jsx2("span", { class: "pill pill-type", children: strings[`type.${detail.feedback_type}`] }),
|
|
1284
1657
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-severity pill-severity--${detail.severity}`, children: strings[`severity.${detail.severity}`] }),
|
|
1285
1658
|
/* @__PURE__ */ jsx2("span", { class: "pill pill-capture", children: captureLabel })
|
|
1286
1659
|
] }),
|
|
1287
|
-
/* @__PURE__ */
|
|
1660
|
+
/* @__PURE__ */ jsxs3("div", { class: "report-detail-context-line", children: [
|
|
1288
1661
|
/* @__PURE__ */ jsx2("span", { class: "report-detail-context-label", children: strings["detail.context.submitted_at"] }),
|
|
1289
1662
|
/* @__PURE__ */ jsx2("span", { children: formatSubmittedAt(detail.created_at) })
|
|
1290
1663
|
] }),
|
|
1291
1664
|
detail.page_url && (() => {
|
|
1292
1665
|
const safeHref = safeExternalHref(detail.page_url);
|
|
1293
|
-
return /* @__PURE__ */
|
|
1666
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-context-line", title: detail.page_url, children: [
|
|
1294
1667
|
/* @__PURE__ */ jsx2("span", { class: "report-detail-context-label", children: strings["detail.context.page"] }),
|
|
1295
1668
|
safeHref ? /* @__PURE__ */ jsx2(
|
|
1296
1669
|
"a",
|
|
@@ -1327,9 +1700,9 @@ function TechnicalContextDrawer({ ctx, strings }) {
|
|
|
1327
1700
|
if (!hasAny) return null;
|
|
1328
1701
|
const errorCount = errors.length;
|
|
1329
1702
|
const summary = errorCount > 0 ? `${strings["detail.tech.title"]} \xB7 ${errorCount} ${errorCount === 1 ? strings["detail.tech.errors_one"] : strings["detail.tech.errors_many"]}` : strings["detail.tech.title"];
|
|
1330
|
-
return /* @__PURE__ */
|
|
1703
|
+
return /* @__PURE__ */ jsxs3("details", { class: "report-detail-tech", children: [
|
|
1331
1704
|
/* @__PURE__ */ jsx2("summary", { children: summary }),
|
|
1332
|
-
/* @__PURE__ */
|
|
1705
|
+
/* @__PURE__ */ jsxs3("div", { class: "tech-body", children: [
|
|
1333
1706
|
device && /* @__PURE__ */ jsx2(DeviceSection, { device, strings }),
|
|
1334
1707
|
errors.length > 0 && /* @__PURE__ */ jsx2(ErrorsSection, { errors, strings }),
|
|
1335
1708
|
consoleLogs.length > 0 && /* @__PURE__ */ jsx2(ConsoleSection, { logs: consoleLogs, strings }),
|
|
@@ -1354,9 +1727,9 @@ function DeviceSection({
|
|
|
1354
1727
|
if (device?.timezone) parts.push({ label: strings["detail.tech.device.timezone"], value: device.timezone });
|
|
1355
1728
|
if (device?.connection) parts.push({ label: strings["detail.tech.device.connection"], value: device.connection });
|
|
1356
1729
|
if (parts.length === 0) return null;
|
|
1357
|
-
return /* @__PURE__ */
|
|
1730
|
+
return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
|
|
1358
1731
|
/* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.device"] }),
|
|
1359
|
-
/* @__PURE__ */ jsx2("dl", { class: "tech-device", children: parts.map((p) => /* @__PURE__ */
|
|
1732
|
+
/* @__PURE__ */ jsx2("dl", { class: "tech-device", children: parts.map((p) => /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1360
1733
|
/* @__PURE__ */ jsx2("dt", { children: p.label }),
|
|
1361
1734
|
/* @__PURE__ */ jsx2("dd", { children: p.value })
|
|
1362
1735
|
] })) })
|
|
@@ -1366,9 +1739,9 @@ function ErrorsSection({
|
|
|
1366
1739
|
errors,
|
|
1367
1740
|
strings
|
|
1368
1741
|
}) {
|
|
1369
|
-
return /* @__PURE__ */
|
|
1742
|
+
return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
|
|
1370
1743
|
/* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.errors"] }),
|
|
1371
|
-
/* @__PURE__ */ jsx2("ul", { class: "tech-errors", children: errors.map((e) => /* @__PURE__ */
|
|
1744
|
+
/* @__PURE__ */ jsx2("ul", { class: "tech-errors", children: errors.map((e) => /* @__PURE__ */ jsxs3("li", { children: [
|
|
1372
1745
|
/* @__PURE__ */ jsx2("div", { class: "tech-errors-msg", children: e.message }),
|
|
1373
1746
|
e.stack && /* @__PURE__ */ jsx2("pre", { class: "tech-errors-stack", children: truncateStack(e.stack) })
|
|
1374
1747
|
] })) })
|
|
@@ -1378,9 +1751,9 @@ function ConsoleSection({
|
|
|
1378
1751
|
logs,
|
|
1379
1752
|
strings
|
|
1380
1753
|
}) {
|
|
1381
|
-
return /* @__PURE__ */
|
|
1754
|
+
return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
|
|
1382
1755
|
/* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.console"] }),
|
|
1383
|
-
/* @__PURE__ */ jsx2("ul", { class: "tech-console", children: logs.map((l) => /* @__PURE__ */
|
|
1756
|
+
/* @__PURE__ */ jsx2("ul", { class: "tech-console", children: logs.map((l) => /* @__PURE__ */ jsxs3("li", { class: `tech-console-row tech-console-row--${l.level}`, children: [
|
|
1384
1757
|
/* @__PURE__ */ jsx2("span", { class: "tech-console-level", children: l.level }),
|
|
1385
1758
|
/* @__PURE__ */ jsx2("span", { class: "tech-console-msg", children: l.message })
|
|
1386
1759
|
] })) })
|
|
@@ -1390,15 +1763,15 @@ function NetworkSection({
|
|
|
1390
1763
|
rows,
|
|
1391
1764
|
strings
|
|
1392
1765
|
}) {
|
|
1393
|
-
return /* @__PURE__ */
|
|
1766
|
+
return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
|
|
1394
1767
|
/* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.network"] }),
|
|
1395
1768
|
/* @__PURE__ */ jsx2("ul", { class: "tech-network", children: rows.map((n) => {
|
|
1396
1769
|
const failed = n.status >= 400 || n.status === 0;
|
|
1397
|
-
return /* @__PURE__ */
|
|
1770
|
+
return /* @__PURE__ */ jsxs3("li", { class: `tech-network-row${failed ? " tech-network-row--fail" : ""}`, children: [
|
|
1398
1771
|
/* @__PURE__ */ jsx2("span", { class: "tech-network-status", children: n.status || "\u2014" }),
|
|
1399
1772
|
/* @__PURE__ */ jsx2("span", { class: "tech-network-method", children: n.method }),
|
|
1400
1773
|
/* @__PURE__ */ jsx2("span", { class: "tech-network-url", title: n.url, children: truncateUrl(n.url, 56) }),
|
|
1401
|
-
/* @__PURE__ */
|
|
1774
|
+
/* @__PURE__ */ jsxs3("span", { class: "tech-network-time", children: [
|
|
1402
1775
|
Math.round(n.durationMs),
|
|
1403
1776
|
"ms"
|
|
1404
1777
|
] })
|
|
@@ -1412,15 +1785,15 @@ function truncateStack(stack) {
|
|
|
1412
1785
|
return lines.slice(0, 12).join("\n") + "\n \u2026";
|
|
1413
1786
|
}
|
|
1414
1787
|
function StatusHistorySection({ rows, strings }) {
|
|
1415
|
-
return /* @__PURE__ */
|
|
1788
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-history", children: [
|
|
1416
1789
|
/* @__PURE__ */ jsx2("h3", { class: "report-detail-section", children: strings["detail.history"] }),
|
|
1417
1790
|
/* @__PURE__ */ jsx2("ol", { class: "status-history", children: rows.map((r) => {
|
|
1418
1791
|
const from = strings[`status.${r.from_status}`] ?? r.from_status;
|
|
1419
1792
|
const to = strings[`status.${r.to_status}`] ?? r.to_status;
|
|
1420
1793
|
const sourceKey = r.changed_by_source === "mcp" ? "detail.author.mcp" : r.changed_by_source === "system" ? "detail.author.system" : "detail.author.staff";
|
|
1421
|
-
return /* @__PURE__ */
|
|
1794
|
+
return /* @__PURE__ */ jsxs3("li", { class: "status-history-row", children: [
|
|
1422
1795
|
/* @__PURE__ */ jsx2("span", { class: "status-history-time", children: formatSubmittedAt(r.created_at) }),
|
|
1423
|
-
/* @__PURE__ */
|
|
1796
|
+
/* @__PURE__ */ jsxs3("span", { class: "status-history-transition", children: [
|
|
1424
1797
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${r.from_status}`, children: from }),
|
|
1425
1798
|
/* @__PURE__ */ jsx2("span", { class: "status-history-arrow", "aria-hidden": "true", children: "\u2192" }),
|
|
1426
1799
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${r.to_status}`, children: to })
|
|
@@ -1432,7 +1805,7 @@ function StatusHistorySection({ rows, strings }) {
|
|
|
1432
1805
|
}
|
|
1433
1806
|
|
|
1434
1807
|
// src/widget/BoardView.tsx
|
|
1435
|
-
import { Fragment as Fragment2, jsx as jsx3, jsxs as
|
|
1808
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
1436
1809
|
var POLL_MS2 = 3e4;
|
|
1437
1810
|
var SORT_OPTIONS = ["recent", "oldest", "updated", "severity", "status"];
|
|
1438
1811
|
var STATUSES = [
|
|
@@ -1470,11 +1843,10 @@ function BoardView({
|
|
|
1470
1843
|
if (!thisPage) return;
|
|
1471
1844
|
return onLocationChange(() => setReloadTick((t) => t + 1));
|
|
1472
1845
|
}, [thisPage]);
|
|
1473
|
-
const fetchFilters = useMemo(
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
);
|
|
1846
|
+
const fetchFilters = useMemo(() => {
|
|
1847
|
+
const searching = !!filters.q?.trim();
|
|
1848
|
+
return thisPage && !searching ? { ...filters, pagePath: currentPagePath(getCurrentPage !== void 0 ? { getCurrentPage } : {}) } : filters;
|
|
1849
|
+
}, [filtersHash(filters), thisPage, reloadTick]);
|
|
1478
1850
|
useEffect2(() => {
|
|
1479
1851
|
let cancelled = false;
|
|
1480
1852
|
setLoading(true);
|
|
@@ -1491,7 +1863,7 @@ function BoardView({
|
|
|
1491
1863
|
return true;
|
|
1492
1864
|
} catch (e) {
|
|
1493
1865
|
if (cancelled) return false;
|
|
1494
|
-
setError(e
|
|
1866
|
+
setError(rateLimitMessage(e, strings) ?? strings["board.list.error"]);
|
|
1495
1867
|
return false;
|
|
1496
1868
|
} finally {
|
|
1497
1869
|
if (!cancelled) setLoading(false);
|
|
@@ -1510,7 +1882,7 @@ function BoardView({
|
|
|
1510
1882
|
setSelectedId(row.id);
|
|
1511
1883
|
setDetailKey((k) => k + 1);
|
|
1512
1884
|
};
|
|
1513
|
-
return /* @__PURE__ */
|
|
1885
|
+
return /* @__PURE__ */ jsxs4("div", { class: "board-view", children: [
|
|
1514
1886
|
/* @__PURE__ */ jsx3(BoardHeader, { strings, kpis, thisPage }),
|
|
1515
1887
|
/* @__PURE__ */ jsx3(
|
|
1516
1888
|
BoardFilters,
|
|
@@ -1523,10 +1895,10 @@ function BoardView({
|
|
|
1523
1895
|
onToggleThisPage: () => setThisPage((v) => !v)
|
|
1524
1896
|
}
|
|
1525
1897
|
),
|
|
1526
|
-
/* @__PURE__ */
|
|
1527
|
-
/* @__PURE__ */
|
|
1528
|
-
error && /* @__PURE__ */
|
|
1529
|
-
/* @__PURE__ */ jsx3("span", { children:
|
|
1898
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-body", children: [
|
|
1899
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-list-wrap", "aria-busy": loading && !page, children: [
|
|
1900
|
+
error && /* @__PURE__ */ jsxs4("div", { class: "board-error", role: "alert", children: [
|
|
1901
|
+
/* @__PURE__ */ jsx3("span", { children: error }),
|
|
1530
1902
|
/* @__PURE__ */ jsx3(
|
|
1531
1903
|
"button",
|
|
1532
1904
|
{
|
|
@@ -1571,10 +1943,17 @@ function BoardView({
|
|
|
1571
1943
|
strings,
|
|
1572
1944
|
onBack: () => setSelectedId(null),
|
|
1573
1945
|
canModerate: selectedRow.can_moderate ?? selectedRow.is_mine,
|
|
1574
|
-
variant: "board"
|
|
1946
|
+
variant: "board",
|
|
1947
|
+
onOpenSeq: (seq) => {
|
|
1948
|
+
void api.getReportBySeq(seq, externalId).then((r) => {
|
|
1949
|
+
setSelectedId(r.id);
|
|
1950
|
+
setDetailKey((k) => k + 1);
|
|
1951
|
+
}).catch(() => {
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1575
1954
|
},
|
|
1576
1955
|
detailKey
|
|
1577
|
-
) : /* @__PURE__ */
|
|
1956
|
+
) : /* @__PURE__ */ jsxs4("div", { class: "board-detail-empty", children: [
|
|
1578
1957
|
/* @__PURE__ */ jsx3(DetailEmptyIllustration, {}),
|
|
1579
1958
|
/* @__PURE__ */ jsx3("p", { children: strings["board.detail.empty"] })
|
|
1580
1959
|
] }) })
|
|
@@ -1587,10 +1966,10 @@ function BoardHeader({
|
|
|
1587
1966
|
thisPage
|
|
1588
1967
|
}) {
|
|
1589
1968
|
const scopeLabel = thisPage ? strings["board.scope.onThisPage"] : kpis?.scope === "project" ? strings["board.scope.project"] : strings["board.scope.mine"];
|
|
1590
|
-
return /* @__PURE__ */
|
|
1591
|
-
/* @__PURE__ */
|
|
1969
|
+
return /* @__PURE__ */ jsxs4("header", { class: "board-header", children: [
|
|
1970
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-header-title", children: [
|
|
1592
1971
|
/* @__PURE__ */ jsx3("span", { class: "board-header-emoji", "aria-hidden": "true", children: "\u{1F4CB}" }),
|
|
1593
|
-
/* @__PURE__ */
|
|
1972
|
+
/* @__PURE__ */ jsxs4("div", { children: [
|
|
1594
1973
|
/* @__PURE__ */ jsx3("h2", { class: "board-header-h", children: strings["tab.board"] }),
|
|
1595
1974
|
/* @__PURE__ */ jsx3("p", { class: "board-header-sub", children: kpis ? `${kpis.total} \xB7 ${scopeLabel}` : scopeLabel })
|
|
1596
1975
|
] })
|
|
@@ -1628,7 +2007,7 @@ function BoardKpiStrip({
|
|
|
1628
2007
|
tone: "tone-rate"
|
|
1629
2008
|
}
|
|
1630
2009
|
];
|
|
1631
|
-
return /* @__PURE__ */ jsx3("div", { class: `board-kpi-strip ${kpis ? "is-ready" : "is-loading"}`, "aria-live": "polite", children: cells.map((c) => /* @__PURE__ */
|
|
2010
|
+
return /* @__PURE__ */ jsx3("div", { class: `board-kpi-strip ${kpis ? "is-ready" : "is-loading"}`, "aria-live": "polite", children: cells.map((c) => /* @__PURE__ */ jsxs4("div", { class: `board-kpi-card ${c.tone}`, children: [
|
|
1632
2011
|
/* @__PURE__ */ jsx3("div", { class: "board-kpi-value", children: c.value }),
|
|
1633
2012
|
/* @__PURE__ */ jsx3("div", { class: "board-kpi-label", children: c.label })
|
|
1634
2013
|
] }, c.key)) });
|
|
@@ -1680,8 +2059,8 @@ function BoardFilters({
|
|
|
1680
2059
|
}
|
|
1681
2060
|
};
|
|
1682
2061
|
const clear = () => onChange({});
|
|
1683
|
-
return /* @__PURE__ */
|
|
1684
|
-
/* @__PURE__ */
|
|
2062
|
+
return /* @__PURE__ */ jsxs4("div", { class: "board-filters", children: [
|
|
2063
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-scope", role: "group", "aria-label": strings["board.scope.thisPage"], children: [
|
|
1685
2064
|
/* @__PURE__ */ jsx3(
|
|
1686
2065
|
"button",
|
|
1687
2066
|
{
|
|
@@ -1713,7 +2092,7 @@ function BoardFilters({
|
|
|
1713
2092
|
children: SORT_OPTIONS.map((o) => /* @__PURE__ */ jsx3("option", { value: o, children: strings[`board.sort.${o}`] }, o))
|
|
1714
2093
|
}
|
|
1715
2094
|
),
|
|
1716
|
-
/* @__PURE__ */
|
|
2095
|
+
/* @__PURE__ */ jsxs4(
|
|
1717
2096
|
"select",
|
|
1718
2097
|
{
|
|
1719
2098
|
class: "board-filter-select",
|
|
@@ -1726,7 +2105,7 @@ function BoardFilters({
|
|
|
1726
2105
|
]
|
|
1727
2106
|
}
|
|
1728
2107
|
),
|
|
1729
|
-
/* @__PURE__ */
|
|
2108
|
+
/* @__PURE__ */ jsxs4(
|
|
1730
2109
|
"select",
|
|
1731
2110
|
{
|
|
1732
2111
|
class: "board-filter-select",
|
|
@@ -1739,7 +2118,7 @@ function BoardFilters({
|
|
|
1739
2118
|
]
|
|
1740
2119
|
}
|
|
1741
2120
|
),
|
|
1742
|
-
/* @__PURE__ */
|
|
2121
|
+
/* @__PURE__ */ jsxs4(
|
|
1743
2122
|
"select",
|
|
1744
2123
|
{
|
|
1745
2124
|
class: "board-filter-select",
|
|
@@ -1762,7 +2141,7 @@ function BoardFilters({
|
|
|
1762
2141
|
onInput: (e) => setSearch(e.target.value)
|
|
1763
2142
|
}
|
|
1764
2143
|
),
|
|
1765
|
-
/* @__PURE__ */
|
|
2144
|
+
/* @__PURE__ */ jsxs4("label", { class: "board-filter-toggle", children: [
|
|
1766
2145
|
/* @__PURE__ */ jsx3(
|
|
1767
2146
|
"input",
|
|
1768
2147
|
{
|
|
@@ -1773,7 +2152,7 @@ function BoardFilters({
|
|
|
1773
2152
|
),
|
|
1774
2153
|
/* @__PURE__ */ jsx3("span", { children: strings["board.filter.mine"] })
|
|
1775
2154
|
] }),
|
|
1776
|
-
activeCount > 0 && /* @__PURE__ */
|
|
2155
|
+
activeCount > 0 && /* @__PURE__ */ jsxs4("button", { type: "button", class: "board-filter-clear", onClick: clear, children: [
|
|
1777
2156
|
/* @__PURE__ */ jsx3(CloseIcon, {}),
|
|
1778
2157
|
strings["board.filter.clear"]
|
|
1779
2158
|
] })
|
|
@@ -1787,7 +2166,7 @@ function BoardList({
|
|
|
1787
2166
|
total,
|
|
1788
2167
|
thisPage
|
|
1789
2168
|
}) {
|
|
1790
|
-
return /* @__PURE__ */
|
|
2169
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1791
2170
|
/* @__PURE__ */ jsx3("div", { class: "board-list-count", children: strings[thisPage ? "board.list.count.page" : "board.list.count"].replace("{n}", String(rows.length)).replace("{total}", String(total)) }),
|
|
1792
2171
|
/* @__PURE__ */ jsx3("ul", { class: "board-list", role: "list", children: rows.map((row) => /* @__PURE__ */ jsx3(
|
|
1793
2172
|
BoardRowCard,
|
|
@@ -1808,7 +2187,7 @@ function BoardRowCard({
|
|
|
1808
2187
|
strings
|
|
1809
2188
|
}) {
|
|
1810
2189
|
const author = row.is_mine ? strings["board.list.you"] : row.author_label || "\u2014";
|
|
1811
|
-
return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */
|
|
2190
|
+
return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs4(
|
|
1812
2191
|
"button",
|
|
1813
2192
|
{
|
|
1814
2193
|
type: "button",
|
|
@@ -1816,14 +2195,14 @@ function BoardRowCard({
|
|
|
1816
2195
|
onClick: () => onPick(row),
|
|
1817
2196
|
"aria-pressed": selected,
|
|
1818
2197
|
children: [
|
|
1819
|
-
/* @__PURE__ */
|
|
2198
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-row-badges", children: [
|
|
1820
2199
|
/* @__PURE__ */ jsx3("span", { class: `badge status-${row.status}`, children: strings[`status.${row.status}`] ?? row.status }),
|
|
1821
2200
|
/* @__PURE__ */ jsx3("span", { class: `badge severity-${row.severity}`, children: strings[`severity.${row.severity}`] ?? row.severity })
|
|
1822
2201
|
] }),
|
|
1823
2202
|
/* @__PURE__ */ jsx3("div", { class: "board-row-description", children: row.description }),
|
|
1824
|
-
/* @__PURE__ */
|
|
2203
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-row-meta", children: [
|
|
1825
2204
|
/* @__PURE__ */ jsx3("span", { class: "board-row-author", children: author }),
|
|
1826
|
-
row.comment_count > 0 && /* @__PURE__ */
|
|
2205
|
+
row.comment_count > 0 && /* @__PURE__ */ jsxs4("span", { class: "board-row-comments", children: [
|
|
1827
2206
|
/* @__PURE__ */ jsx3(CommentIcon, {}),
|
|
1828
2207
|
" ",
|
|
1829
2208
|
row.comment_count
|
|
@@ -1839,7 +2218,7 @@ function BoardEmpty({
|
|
|
1839
2218
|
thisPage,
|
|
1840
2219
|
onAllPages
|
|
1841
2220
|
}) {
|
|
1842
|
-
return /* @__PURE__ */
|
|
2221
|
+
return /* @__PURE__ */ jsxs4("div", { class: "board-empty", children: [
|
|
1843
2222
|
/* @__PURE__ */ jsx3(EmptyIllustration, {}),
|
|
1844
2223
|
/* @__PURE__ */ jsx3("h3", { children: strings[thisPage ? "board.list.empty.page.title" : "board.list.empty.title"] }),
|
|
1845
2224
|
/* @__PURE__ */ jsx3("p", { children: strings[thisPage ? "board.list.empty.page.description" : "board.list.empty.description"] }),
|
|
@@ -1847,7 +2226,7 @@ function BoardEmpty({
|
|
|
1847
2226
|
] });
|
|
1848
2227
|
}
|
|
1849
2228
|
function BoardListSkeleton() {
|
|
1850
|
-
return /* @__PURE__ */ jsx3("ul", { class: "board-list board-list-skeleton", "aria-hidden": "true", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */
|
|
2229
|
+
return /* @__PURE__ */ jsx3("ul", { class: "board-list board-list-skeleton", "aria-hidden": "true", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs4("div", { class: "board-row skeleton-row", children: [
|
|
1851
2230
|
/* @__PURE__ */ jsx3("div", { class: "skeleton-line skeleton-badges" }),
|
|
1852
2231
|
/* @__PURE__ */ jsx3("div", { class: "skeleton-line skeleton-text" }),
|
|
1853
2232
|
/* @__PURE__ */ jsx3("div", { class: "skeleton-line skeleton-text short" })
|
|
@@ -1857,19 +2236,19 @@ function CommentIcon() {
|
|
|
1857
2236
|
return /* @__PURE__ */ jsx3("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx3("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
|
|
1858
2237
|
}
|
|
1859
2238
|
function CloseIcon() {
|
|
1860
|
-
return /* @__PURE__ */
|
|
2239
|
+
return /* @__PURE__ */ jsxs4("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
|
|
1861
2240
|
/* @__PURE__ */ jsx3("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1862
2241
|
/* @__PURE__ */ jsx3("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1863
2242
|
] });
|
|
1864
2243
|
}
|
|
1865
2244
|
function EmptyIllustration() {
|
|
1866
|
-
return /* @__PURE__ */
|
|
2245
|
+
return /* @__PURE__ */ jsxs4("svg", { width: "64", height: "64", viewBox: "0 0 64 64", fill: "none", "aria-hidden": "true", children: [
|
|
1867
2246
|
/* @__PURE__ */ jsx3("circle", { cx: "32", cy: "32", r: "28", stroke: "currentColor", "stroke-opacity": "0.18", "stroke-width": "2" }),
|
|
1868
2247
|
/* @__PURE__ */ jsx3("path", { d: "M22 32h20M32 22v20", stroke: "currentColor", "stroke-opacity": "0.35", "stroke-width": "2", "stroke-linecap": "round" })
|
|
1869
2248
|
] });
|
|
1870
2249
|
}
|
|
1871
2250
|
function DetailEmptyIllustration() {
|
|
1872
|
-
return /* @__PURE__ */
|
|
2251
|
+
return /* @__PURE__ */ jsxs4("svg", { width: "80", height: "80", viewBox: "0 0 64 64", fill: "none", "aria-hidden": "true", children: [
|
|
1873
2252
|
/* @__PURE__ */ jsx3("rect", { x: "10", y: "12", width: "44", height: "36", rx: "4", stroke: "currentColor", "stroke-opacity": "0.22", "stroke-width": "2" }),
|
|
1874
2253
|
/* @__PURE__ */ jsx3("line", { x1: "18", y1: "22", x2: "46", y2: "22", stroke: "currentColor", "stroke-opacity": "0.22", "stroke-width": "2", "stroke-linecap": "round" }),
|
|
1875
2254
|
/* @__PURE__ */ jsx3("line", { x1: "18", y1: "30", x2: "38", y2: "30", stroke: "currentColor", "stroke-opacity": "0.18", "stroke-width": "2", "stroke-linecap": "round" }),
|
|
@@ -1905,7 +2284,7 @@ function formatRelative(iso) {
|
|
|
1905
2284
|
import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "preact/hooks";
|
|
1906
2285
|
|
|
1907
2286
|
// src/widget/ReportRow.tsx
|
|
1908
|
-
import { jsx as jsx4, jsxs as
|
|
2287
|
+
import { jsx as jsx4, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
1909
2288
|
function statusClassName(status) {
|
|
1910
2289
|
return `pill pill-status pill-status--${status}`;
|
|
1911
2290
|
}
|
|
@@ -1933,16 +2312,16 @@ function repliesLabel(count, strings) {
|
|
|
1933
2312
|
}
|
|
1934
2313
|
function ReportRow({ row, strings, onClick }) {
|
|
1935
2314
|
const preview = row.description.length > 120 ? row.description.slice(0, 117) + "\u2026" : row.description;
|
|
1936
|
-
return /* @__PURE__ */
|
|
1937
|
-
/* @__PURE__ */
|
|
2315
|
+
return /* @__PURE__ */ jsxs5("button", { type: "button", class: "mine-row", onClick, children: [
|
|
2316
|
+
/* @__PURE__ */ jsxs5("div", { class: "mine-row-pills", children: [
|
|
1938
2317
|
/* @__PURE__ */ jsx4("span", { class: statusClassName(row.status), children: strings[`status.${row.status}`] ?? row.status }),
|
|
1939
2318
|
/* @__PURE__ */ jsx4("span", { class: typeClassName(), children: strings[`type.${row.feedback_type}`] }),
|
|
1940
2319
|
/* @__PURE__ */ jsx4("span", { class: severityClassName(row.severity), children: strings[`severity.${row.severity}`] })
|
|
1941
2320
|
] }),
|
|
1942
2321
|
/* @__PURE__ */ jsx4("div", { class: "mine-row-preview", children: preview }),
|
|
1943
|
-
/* @__PURE__ */
|
|
2322
|
+
/* @__PURE__ */ jsxs5("div", { class: "mine-row-meta", children: [
|
|
1944
2323
|
/* @__PURE__ */ jsx4("span", { children: formatRelative2(row.updated_at || row.created_at) }),
|
|
1945
|
-
row.comment_count > 0 && /* @__PURE__ */
|
|
2324
|
+
row.comment_count > 0 && /* @__PURE__ */ jsxs5("span", { children: [
|
|
1946
2325
|
"\xB7 ",
|
|
1947
2326
|
repliesLabel(row.comment_count, strings)
|
|
1948
2327
|
] })
|
|
@@ -1951,7 +2330,7 @@ function ReportRow({ row, strings, onClick }) {
|
|
|
1951
2330
|
}
|
|
1952
2331
|
|
|
1953
2332
|
// src/widget/ChangelogList.tsx
|
|
1954
|
-
import { jsx as jsx5, jsxs as
|
|
2333
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
1955
2334
|
var POLL_MS3 = 3e4;
|
|
1956
2335
|
function isoWeekKey(iso) {
|
|
1957
2336
|
const d = new Date(iso);
|
|
@@ -2020,8 +2399,8 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
|
|
|
2020
2399
|
const groups = useMemo2(() => rows ? groupRowsByWeek(rows) : [], [rows]);
|
|
2021
2400
|
const isLoading = rows === null && !error;
|
|
2022
2401
|
const isEmpty = rows !== null && rows.length === 0;
|
|
2023
|
-
return /* @__PURE__ */
|
|
2024
|
-
/* @__PURE__ */
|
|
2402
|
+
return /* @__PURE__ */ jsxs6("div", { class: "mine-list", children: [
|
|
2403
|
+
/* @__PURE__ */ jsxs6("div", { class: "mine-list-header", children: [
|
|
2025
2404
|
/* @__PURE__ */ jsx5("h2", { children: strings["tab.changelog"] }),
|
|
2026
2405
|
/* @__PURE__ */ jsx5(
|
|
2027
2406
|
"button",
|
|
@@ -2038,12 +2417,12 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
|
|
|
2038
2417
|
] }),
|
|
2039
2418
|
isLoading && /* @__PURE__ */ jsx5("div", { class: "mine-loading", children: strings["mine.loading"] }),
|
|
2040
2419
|
error && /* @__PURE__ */ jsx5("div", { class: "error", children: error }),
|
|
2041
|
-
isEmpty && /* @__PURE__ */
|
|
2420
|
+
isEmpty && /* @__PURE__ */ jsxs6("div", { class: "mine-empty", children: [
|
|
2042
2421
|
/* @__PURE__ */ jsx5("strong", { children: strings["changelog.empty.title"] }),
|
|
2043
2422
|
/* @__PURE__ */ jsx5("p", { children: strings["changelog.empty.body"] })
|
|
2044
2423
|
] }),
|
|
2045
|
-
groups.length > 0 && /* @__PURE__ */ jsx5("div", { class: "changelog-groups", children: groups.map((g) => /* @__PURE__ */
|
|
2046
|
-
/* @__PURE__ */
|
|
2424
|
+
groups.length > 0 && /* @__PURE__ */ jsx5("div", { class: "changelog-groups", children: groups.map((g) => /* @__PURE__ */ jsxs6("section", { class: "changelog-group", children: [
|
|
2425
|
+
/* @__PURE__ */ jsxs6("header", { class: "changelog-group-header", children: [
|
|
2047
2426
|
/* @__PURE__ */ jsx5("span", { class: "changelog-group-marker", "aria-hidden": "true", children: "\u25CF" }),
|
|
2048
2427
|
/* @__PURE__ */ jsx5("span", { class: "changelog-group-label", children: strings["changelog.week_of"].replace("{date}", g.label) }),
|
|
2049
2428
|
/* @__PURE__ */ jsx5("span", { class: "changelog-group-rule", "aria-hidden": "true" }),
|
|
@@ -2055,9 +2434,9 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
|
|
|
2055
2434
|
}
|
|
2056
2435
|
|
|
2057
2436
|
// src/widget/Fab.tsx
|
|
2058
|
-
import { jsx as jsx6, jsxs as
|
|
2437
|
+
import { jsx as jsx6, jsxs as jsxs7 } from "preact/jsx-runtime";
|
|
2059
2438
|
function BugIcon() {
|
|
2060
|
-
return /* @__PURE__ */
|
|
2439
|
+
return /* @__PURE__ */ jsxs7(
|
|
2061
2440
|
"svg",
|
|
2062
2441
|
{
|
|
2063
2442
|
width: "20",
|
|
@@ -2087,7 +2466,7 @@ function BugIcon() {
|
|
|
2087
2466
|
);
|
|
2088
2467
|
}
|
|
2089
2468
|
function Fab({ label, onClick, count }) {
|
|
2090
|
-
return /* @__PURE__ */
|
|
2469
|
+
return /* @__PURE__ */ jsxs7("button", { type: "button", class: "fab", "aria-label": label, title: label, onClick, children: [
|
|
2091
2470
|
/* @__PURE__ */ jsx6(BugIcon, {}),
|
|
2092
2471
|
count !== void 0 && count > 0 && /* @__PURE__ */ jsx6("span", { class: "fab-badge", "aria-hidden": "true", children: count > 9 ? "9+" : String(count) })
|
|
2093
2472
|
] });
|
|
@@ -2098,7 +2477,7 @@ import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } fro
|
|
|
2098
2477
|
|
|
2099
2478
|
// src/widget/Annotator.tsx
|
|
2100
2479
|
import { useEffect as useEffect4, useLayoutEffect, useRef as useRef3, useState as useState4 } from "preact/hooks";
|
|
2101
|
-
import { jsx as jsx7, jsxs as
|
|
2480
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "preact/jsx-runtime";
|
|
2102
2481
|
var COLORS = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#ffffff"];
|
|
2103
2482
|
var HIGHLIGHT_ALPHA = 0.35;
|
|
2104
2483
|
function drawShape(ctx, shape, sourceImage) {
|
|
@@ -2224,11 +2603,11 @@ var Icon = {
|
|
|
2224
2603
|
arrow: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M2 8h11M9 4l4 4-4 4", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
|
|
2225
2604
|
pencil: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linejoin": "round" }) }),
|
|
2226
2605
|
text: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M3 3h10M8 3v10M5 13h6", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" }) }),
|
|
2227
|
-
highlight: /* @__PURE__ */
|
|
2606
|
+
highlight: /* @__PURE__ */ jsxs8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
|
|
2228
2607
|
/* @__PURE__ */ jsx7("path", { d: "M3 13l3-3L11 5l-2-2-5 5-3 3v2h2zM10 4l2 2", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }),
|
|
2229
2608
|
/* @__PURE__ */ jsx7("path", { d: "M2 14h12", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" })
|
|
2230
2609
|
] }),
|
|
2231
|
-
blur: /* @__PURE__ */
|
|
2610
|
+
blur: /* @__PURE__ */ jsxs8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
|
|
2232
2611
|
/* @__PURE__ */ jsx7("rect", { x: "2", y: "2", width: "4", height: "4", fill: "currentColor", opacity: "0.85" }),
|
|
2233
2612
|
/* @__PURE__ */ jsx7("rect", { x: "7", y: "2", width: "3", height: "3", fill: "currentColor", opacity: "0.55" }),
|
|
2234
2613
|
/* @__PURE__ */ jsx7("rect", { x: "11", y: "2", width: "3", height: "4", fill: "currentColor", opacity: "0.4" }),
|
|
@@ -2484,8 +2863,8 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2484
2863
|
onClick: (e) => {
|
|
2485
2864
|
if (e.target === e.currentTarget) onCancel();
|
|
2486
2865
|
},
|
|
2487
|
-
children: /* @__PURE__ */
|
|
2488
|
-
/* @__PURE__ */
|
|
2866
|
+
children: /* @__PURE__ */ jsxs8("div", { class: "annotator", role: "dialog", "aria-modal": "true", "aria-label": strings["annotator.title"], children: [
|
|
2867
|
+
/* @__PURE__ */ jsxs8("div", { class: "annotator-header", children: [
|
|
2489
2868
|
/* @__PURE__ */ jsx7("span", { children: strings["annotator.title"] }),
|
|
2490
2869
|
/* @__PURE__ */ jsx7(
|
|
2491
2870
|
"button",
|
|
@@ -2498,7 +2877,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2498
2877
|
}
|
|
2499
2878
|
)
|
|
2500
2879
|
] }),
|
|
2501
|
-
/* @__PURE__ */
|
|
2880
|
+
/* @__PURE__ */ jsxs8("div", { class: "annotator-toolbar", children: [
|
|
2502
2881
|
/* @__PURE__ */ jsx7("div", { class: "annotator-tools", children: tools.map((t) => /* @__PURE__ */ jsx7(
|
|
2503
2882
|
"button",
|
|
2504
2883
|
{
|
|
@@ -2513,7 +2892,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2513
2892
|
t.id
|
|
2514
2893
|
)) }),
|
|
2515
2894
|
/* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
|
|
2516
|
-
/* @__PURE__ */
|
|
2895
|
+
/* @__PURE__ */ jsxs8("div", { class: "annotator-colors", children: [
|
|
2517
2896
|
COLORS.map((c) => /* @__PURE__ */ jsx7(
|
|
2518
2897
|
"button",
|
|
2519
2898
|
{
|
|
@@ -2537,7 +2916,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2537
2916
|
) })
|
|
2538
2917
|
] }),
|
|
2539
2918
|
/* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
|
|
2540
|
-
/* @__PURE__ */
|
|
2919
|
+
/* @__PURE__ */ jsxs8(
|
|
2541
2920
|
"button",
|
|
2542
2921
|
{
|
|
2543
2922
|
type: "button",
|
|
@@ -2551,7 +2930,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2551
2930
|
]
|
|
2552
2931
|
}
|
|
2553
2932
|
),
|
|
2554
|
-
/* @__PURE__ */
|
|
2933
|
+
/* @__PURE__ */ jsxs8(
|
|
2555
2934
|
"button",
|
|
2556
2935
|
{
|
|
2557
2936
|
type: "button",
|
|
@@ -2565,7 +2944,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2565
2944
|
]
|
|
2566
2945
|
}
|
|
2567
2946
|
),
|
|
2568
|
-
/* @__PURE__ */
|
|
2947
|
+
/* @__PURE__ */ jsxs8(
|
|
2569
2948
|
"button",
|
|
2570
2949
|
{
|
|
2571
2950
|
type: "button",
|
|
@@ -2579,7 +2958,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2579
2958
|
}
|
|
2580
2959
|
),
|
|
2581
2960
|
/* @__PURE__ */ jsx7("span", { class: "annotator-spacer" }),
|
|
2582
|
-
/* @__PURE__ */
|
|
2961
|
+
/* @__PURE__ */ jsxs8("span", { class: "annotator-count", children: [
|
|
2583
2962
|
shapes.length,
|
|
2584
2963
|
" ",
|
|
2585
2964
|
strings["annotator.count_suffix"]
|
|
@@ -2596,7 +2975,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2596
2975
|
class: "annotator-canvas"
|
|
2597
2976
|
}
|
|
2598
2977
|
) }),
|
|
2599
|
-
/* @__PURE__ */
|
|
2978
|
+
/* @__PURE__ */ jsxs8("div", { class: "annotator-footer", children: [
|
|
2600
2979
|
/* @__PURE__ */ jsx7("button", { type: "button", class: "btn", onClick: onCancel, children: strings["form.cancel"] }),
|
|
2601
2980
|
/* @__PURE__ */ jsx7(
|
|
2602
2981
|
"button",
|
|
@@ -2615,10 +2994,18 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2615
2994
|
}
|
|
2616
2995
|
|
|
2617
2996
|
// src/widget/Form.tsx
|
|
2618
|
-
import { jsx as jsx8, jsxs as
|
|
2997
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "preact/jsx-runtime";
|
|
2619
2998
|
var TYPES2 = ["bug", "feature", "question", "praise", "typo"];
|
|
2620
2999
|
var SEVERITIES2 = ["blocker", "high", "medium", "low"];
|
|
2621
|
-
function Form({
|
|
3000
|
+
function Form({
|
|
3001
|
+
strings,
|
|
3002
|
+
onSubmit,
|
|
3003
|
+
onCancel,
|
|
3004
|
+
status,
|
|
3005
|
+
errorMessage,
|
|
3006
|
+
submittedSeq,
|
|
3007
|
+
onDirtyChange
|
|
3008
|
+
}) {
|
|
2622
3009
|
const [description, setDescription] = useState5("");
|
|
2623
3010
|
const [feedbackType, setFeedbackType] = useState5("bug");
|
|
2624
3011
|
const [severity, setSeverity] = useState5("medium");
|
|
@@ -2631,6 +3018,10 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2631
3018
|
const submitting = status === "submitting";
|
|
2632
3019
|
const submitLabel = submitting ? strings["form.submitting"] : strings["form.submit"];
|
|
2633
3020
|
const pageUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
3021
|
+
const isDirty = description.trim() !== "" || shots.length > 0;
|
|
3022
|
+
useEffect5(() => {
|
|
3023
|
+
onDirtyChange?.(isDirty);
|
|
3024
|
+
}, [isDirty]);
|
|
2634
3025
|
const shotsRef = useRef4(shots);
|
|
2635
3026
|
shotsRef.current = shots;
|
|
2636
3027
|
useEffect5(() => {
|
|
@@ -2742,9 +3133,9 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2742
3133
|
}
|
|
2743
3134
|
onSubmit(values);
|
|
2744
3135
|
};
|
|
2745
|
-
return /* @__PURE__ */
|
|
3136
|
+
return /* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, children: [
|
|
2746
3137
|
/* @__PURE__ */ jsx8("h2", { children: strings["form.title"] }),
|
|
2747
|
-
/* @__PURE__ */
|
|
3138
|
+
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
2748
3139
|
/* @__PURE__ */ jsx8("label", { for: "mfb-desc", children: strings["form.description.label"] }),
|
|
2749
3140
|
/* @__PURE__ */ jsx8(
|
|
2750
3141
|
"textarea",
|
|
@@ -2756,8 +3147,8 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2756
3147
|
}
|
|
2757
3148
|
)
|
|
2758
3149
|
] }),
|
|
2759
|
-
/* @__PURE__ */
|
|
2760
|
-
/* @__PURE__ */
|
|
3150
|
+
/* @__PURE__ */ jsxs9("div", { class: "row", children: [
|
|
3151
|
+
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
2761
3152
|
/* @__PURE__ */ jsx8("label", { for: "mfb-type", children: strings["form.type.label"] }),
|
|
2762
3153
|
/* @__PURE__ */ jsx8(
|
|
2763
3154
|
"select",
|
|
@@ -2769,7 +3160,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2769
3160
|
}
|
|
2770
3161
|
)
|
|
2771
3162
|
] }),
|
|
2772
|
-
/* @__PURE__ */
|
|
3163
|
+
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
2773
3164
|
/* @__PURE__ */ jsx8("label", { for: "mfb-sev", children: strings["form.severity.label"] }),
|
|
2774
3165
|
/* @__PURE__ */ jsx8(
|
|
2775
3166
|
"select",
|
|
@@ -2782,7 +3173,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2782
3173
|
)
|
|
2783
3174
|
] })
|
|
2784
3175
|
] }),
|
|
2785
|
-
/* @__PURE__ */
|
|
3176
|
+
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
2786
3177
|
/* @__PURE__ */ jsx8("label", { children: strings["form.screenshot.label"] }),
|
|
2787
3178
|
/* @__PURE__ */ jsx8(
|
|
2788
3179
|
"input",
|
|
@@ -2797,17 +3188,17 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2797
3188
|
tabIndex: -1
|
|
2798
3189
|
}
|
|
2799
3190
|
),
|
|
2800
|
-
shots.length > 0 && /* @__PURE__ */ jsx8("div", { class: "screenshot-strip", children: shots.map((shot, index) => /* @__PURE__ */
|
|
3191
|
+
shots.length > 0 && /* @__PURE__ */ jsx8("div", { class: "screenshot-strip", children: shots.map((shot, index) => /* @__PURE__ */ jsxs9("div", { class: "screenshot-preview", children: [
|
|
2801
3192
|
/* @__PURE__ */ jsx8("img", { src: shot.preview, alt: "" }),
|
|
2802
|
-
/* @__PURE__ */
|
|
2803
|
-
/* @__PURE__ */
|
|
3193
|
+
/* @__PURE__ */ jsxs9("div", { class: "screenshot-preview-actions", children: [
|
|
3194
|
+
/* @__PURE__ */ jsxs9(
|
|
2804
3195
|
"button",
|
|
2805
3196
|
{
|
|
2806
3197
|
type: "button",
|
|
2807
3198
|
class: "btn btn--primary screenshot-annotate",
|
|
2808
3199
|
onClick: () => setAnnotatingIndex(index),
|
|
2809
3200
|
children: [
|
|
2810
|
-
/* @__PURE__ */
|
|
3201
|
+
/* @__PURE__ */ jsxs9("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
|
|
2811
3202
|
/* @__PURE__ */ jsx8("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
|
|
2812
3203
|
/* @__PURE__ */ jsx8("path", { d: "M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
|
|
2813
3204
|
] }),
|
|
@@ -2815,13 +3206,13 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2815
3206
|
]
|
|
2816
3207
|
}
|
|
2817
3208
|
),
|
|
2818
|
-
/* @__PURE__ */
|
|
3209
|
+
/* @__PURE__ */ jsxs9("button", { type: "button", class: "btn", onClick: () => removeShot(index), children: [
|
|
2819
3210
|
/* @__PURE__ */ jsx8("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z" }) }),
|
|
2820
3211
|
strings["form.screenshot.remove"]
|
|
2821
3212
|
] })
|
|
2822
3213
|
] })
|
|
2823
3214
|
] }, shot.preview)) }),
|
|
2824
|
-
shots.length < MAX_SCREENSHOTS && /* @__PURE__ */
|
|
3215
|
+
shots.length < MAX_SCREENSHOTS && /* @__PURE__ */ jsxs9(
|
|
2825
3216
|
"div",
|
|
2826
3217
|
{
|
|
2827
3218
|
ref: dropZoneRef,
|
|
@@ -2840,12 +3231,12 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2840
3231
|
onDragLeave: handleDragLeave,
|
|
2841
3232
|
onDrop: handleDrop,
|
|
2842
3233
|
children: [
|
|
2843
|
-
/* @__PURE__ */
|
|
3234
|
+
/* @__PURE__ */ jsxs9("svg", { class: "screenshot-icon", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.6", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
|
|
2844
3235
|
/* @__PURE__ */ jsx8("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
2845
3236
|
/* @__PURE__ */ jsx8("polyline", { points: "17 8 12 3 7 8" }),
|
|
2846
3237
|
/* @__PURE__ */ jsx8("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
2847
3238
|
] }),
|
|
2848
|
-
/* @__PURE__ */
|
|
3239
|
+
/* @__PURE__ */ jsxs9("div", { class: "screenshot-cta", children: [
|
|
2849
3240
|
/* @__PURE__ */ jsx8("strong", { children: strings["form.screenshot.cta_click"] }),
|
|
2850
3241
|
", ",
|
|
2851
3242
|
strings["form.screenshot.cta_rest"]
|
|
@@ -2855,14 +3246,15 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2855
3246
|
}
|
|
2856
3247
|
)
|
|
2857
3248
|
] }),
|
|
2858
|
-
pageUrl && /* @__PURE__ */
|
|
3249
|
+
pageUrl && /* @__PURE__ */ jsxs9("div", { class: "page-context", title: pageUrl, children: [
|
|
2859
3250
|
/* @__PURE__ */ jsx8("span", { class: "page-context-label", children: strings["form.context.label"] }),
|
|
2860
3251
|
/* @__PURE__ */ jsx8("span", { class: "page-context-url", children: truncateUrl(pageUrl, 90) })
|
|
2861
3252
|
] }),
|
|
3253
|
+
/* @__PURE__ */ jsx8("div", { class: "capture-notice", children: strings["form.capture.notice"] }),
|
|
2862
3254
|
localError && /* @__PURE__ */ jsx8("div", { class: "error", children: localError }),
|
|
2863
3255
|
status === "error" && errorMessage && /* @__PURE__ */ jsx8("div", { class: "error", children: errorMessage }),
|
|
2864
|
-
status === "success" && /* @__PURE__ */ jsx8("div", { class: "success", children: strings["form.success"] }),
|
|
2865
|
-
/* @__PURE__ */
|
|
3256
|
+
status === "success" && /* @__PURE__ */ jsx8("div", { class: "success", children: submittedSeq !== void 0 ? strings["form.success.seq"].replace("{seq}", String(submittedSeq)) : strings["form.success"] }),
|
|
3257
|
+
/* @__PURE__ */ jsxs9("div", { class: "actions", children: [
|
|
2866
3258
|
/* @__PURE__ */ jsx8("button", { type: "button", class: "btn", onClick: onCancel, disabled: submitting, children: strings["form.cancel"] }),
|
|
2867
3259
|
/* @__PURE__ */ jsx8("button", { type: "submit", class: "btn btn--primary", disabled: submitting, children: submitLabel })
|
|
2868
3260
|
] }),
|
|
@@ -2882,7 +3274,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2882
3274
|
import { useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "preact/hooks";
|
|
2883
3275
|
|
|
2884
3276
|
// src/widget/KpiStrip.tsx
|
|
2885
|
-
import { jsx as jsx9, jsxs as
|
|
3277
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "preact/jsx-runtime";
|
|
2886
3278
|
function computeKpiCounts(rows) {
|
|
2887
3279
|
const counts = {
|
|
2888
3280
|
new: 0,
|
|
@@ -2952,7 +3344,7 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
|
|
|
2952
3344
|
return /* @__PURE__ */ jsx9("div", { class: "kpi-strip", role: "toolbar", children: cells.map((c) => {
|
|
2953
3345
|
const active = filter === c.key;
|
|
2954
3346
|
const toggleTo = active ? "all" : c.key;
|
|
2955
|
-
return /* @__PURE__ */
|
|
3347
|
+
return /* @__PURE__ */ jsxs10(
|
|
2956
3348
|
"button",
|
|
2957
3349
|
{
|
|
2958
3350
|
type: "button",
|
|
@@ -2969,14 +3361,36 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
|
|
|
2969
3361
|
}
|
|
2970
3362
|
|
|
2971
3363
|
// src/widget/MineList.tsx
|
|
2972
|
-
import { jsx as jsx10, jsxs as
|
|
3364
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "preact/jsx-runtime";
|
|
2973
3365
|
var POLL_MS4 = 3e4;
|
|
2974
|
-
function MineList({ api, externalId, strings, onSelect }) {
|
|
3366
|
+
function MineList({ api, externalId, strings, onSelect, onOpenSeq }) {
|
|
2975
3367
|
const [rows, setRows] = useState6(null);
|
|
2976
3368
|
const [error, setError] = useState6(null);
|
|
2977
3369
|
const [refreshing, setRefreshing] = useState6(false);
|
|
2978
3370
|
const [filter, setFilter] = useState6("all");
|
|
3371
|
+
const [jumpValue, setJumpValue] = useState6("");
|
|
3372
|
+
const [jumpError, setJumpError] = useState6(false);
|
|
3373
|
+
const [jumping, setJumping] = useState6(false);
|
|
2979
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
|
+
};
|
|
2980
3394
|
const fetchRows = async () => {
|
|
2981
3395
|
setRefreshing(true);
|
|
2982
3396
|
setError(null);
|
|
@@ -2988,7 +3402,7 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
2988
3402
|
} catch (err) {
|
|
2989
3403
|
if (typeof console !== "undefined") console.warn("[mhosaic] listMine:", err);
|
|
2990
3404
|
if (!mountedRef.current) return false;
|
|
2991
|
-
setError(strings["mine.error"]);
|
|
3405
|
+
setError(rateLimitMessage(err, strings) ?? strings["mine.error"]);
|
|
2992
3406
|
return false;
|
|
2993
3407
|
} finally {
|
|
2994
3408
|
if (mountedRef.current) setRefreshing(false);
|
|
@@ -3006,26 +3420,47 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
3006
3420
|
const isLoading = rows === null && !error;
|
|
3007
3421
|
const visibleRows = rows ? rowsMatchingFilter(rows, filter) : null;
|
|
3008
3422
|
const visibleEmpty = !!rows && rows.length > 0 && (visibleRows?.length ?? 0) === 0;
|
|
3009
|
-
return /* @__PURE__ */
|
|
3010
|
-
/* @__PURE__ */
|
|
3423
|
+
return /* @__PURE__ */ jsxs11("div", { class: "mine-list", children: [
|
|
3424
|
+
/* @__PURE__ */ jsxs11("div", { class: "mine-list-header", children: [
|
|
3011
3425
|
/* @__PURE__ */ jsx10("h2", { children: strings["tab.mine"] }),
|
|
3012
|
-
/* @__PURE__ */
|
|
3013
|
-
"
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
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
|
+
] })
|
|
3024
3458
|
] }),
|
|
3459
|
+
jumpError && /* @__PURE__ */ jsx10("div", { class: "mine-jump-error error", role: "alert", children: strings["mine.jump.not_found"] }),
|
|
3025
3460
|
rows && rows.length > 0 && /* @__PURE__ */ jsx10(KpiStrip, { rows, filter, onFilter: setFilter, strings }),
|
|
3026
3461
|
isLoading && /* @__PURE__ */ jsx10("div", { class: "mine-loading", children: strings["mine.loading"] }),
|
|
3027
3462
|
error && /* @__PURE__ */ jsx10("div", { class: "error", children: error }),
|
|
3028
|
-
isEmpty && /* @__PURE__ */
|
|
3463
|
+
isEmpty && /* @__PURE__ */ jsxs11("div", { class: "mine-empty", children: [
|
|
3029
3464
|
/* @__PURE__ */ jsx10("strong", { children: strings["mine.empty.title"] }),
|
|
3030
3465
|
/* @__PURE__ */ jsx10("p", { children: strings["mine.empty.body"] })
|
|
3031
3466
|
] }),
|
|
@@ -3036,7 +3471,7 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
3036
3471
|
|
|
3037
3472
|
// src/widget/Modal.tsx
|
|
3038
3473
|
import { useEffect as useEffect7, useRef as useRef6 } from "preact/hooks";
|
|
3039
|
-
import { jsx as jsx11, jsxs as
|
|
3474
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "preact/jsx-runtime";
|
|
3040
3475
|
function Modal({ onDismiss, children, closeLabel = "Close", expanded = false }) {
|
|
3041
3476
|
const modalRef = useRef6(null);
|
|
3042
3477
|
const previouslyFocused = useRef6(null);
|
|
@@ -3049,7 +3484,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
|
|
|
3049
3484
|
return;
|
|
3050
3485
|
}
|
|
3051
3486
|
e.stopPropagation();
|
|
3052
|
-
onDismiss();
|
|
3487
|
+
onDismiss("escape");
|
|
3053
3488
|
};
|
|
3054
3489
|
window.addEventListener("keydown", onKey);
|
|
3055
3490
|
const first = modalRef.current?.querySelector(
|
|
@@ -3068,9 +3503,9 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
|
|
|
3068
3503
|
class: `backdrop ${expanded ? "is-expanded" : ""}`,
|
|
3069
3504
|
role: "presentation",
|
|
3070
3505
|
onClick: (e) => {
|
|
3071
|
-
if (e.target === e.currentTarget) onDismiss();
|
|
3506
|
+
if (e.target === e.currentTarget) onDismiss("backdrop");
|
|
3072
3507
|
},
|
|
3073
|
-
children: /* @__PURE__ */
|
|
3508
|
+
children: /* @__PURE__ */ jsxs12(
|
|
3074
3509
|
"div",
|
|
3075
3510
|
{
|
|
3076
3511
|
ref: modalRef,
|
|
@@ -3084,7 +3519,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
|
|
|
3084
3519
|
type: "button",
|
|
3085
3520
|
class: "modal-close",
|
|
3086
3521
|
"aria-label": closeLabel,
|
|
3087
|
-
onClick: onDismiss,
|
|
3522
|
+
onClick: () => onDismiss("button"),
|
|
3088
3523
|
children: "\xD7"
|
|
3089
3524
|
}
|
|
3090
3525
|
),
|
|
@@ -3097,7 +3532,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
|
|
|
3097
3532
|
}
|
|
3098
3533
|
|
|
3099
3534
|
// src/widget/PageActivityStrip.tsx
|
|
3100
|
-
import { jsx as jsx12, jsxs as
|
|
3535
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "preact/jsx-runtime";
|
|
3101
3536
|
function PageActivityStrip({
|
|
3102
3537
|
open,
|
|
3103
3538
|
strings,
|
|
@@ -3105,7 +3540,7 @@ function PageActivityStrip({
|
|
|
3105
3540
|
}) {
|
|
3106
3541
|
if (open === 0) return null;
|
|
3107
3542
|
const text = open === 1 ? strings["page.strip.text.one"] : strings["page.strip.text"].replace("{n}", String(open));
|
|
3108
|
-
return /* @__PURE__ */
|
|
3543
|
+
return /* @__PURE__ */ jsxs13("div", { class: "page-strip", children: [
|
|
3109
3544
|
/* @__PURE__ */ jsx12("span", { children: text }),
|
|
3110
3545
|
/* @__PURE__ */ jsx12("button", { type: "button", class: "page-strip-view", onClick: onView, children: strings["page.strip.view"] })
|
|
3111
3546
|
] });
|
|
@@ -3113,7 +3548,7 @@ function PageActivityStrip({
|
|
|
3113
3548
|
|
|
3114
3549
|
// src/widget/PagePeekPanel.tsx
|
|
3115
3550
|
import { useEffect as useEffect8, useState as useState7 } from "preact/hooks";
|
|
3116
|
-
import { jsx as jsx13, jsxs as
|
|
3551
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "preact/jsx-runtime";
|
|
3117
3552
|
function PagePeekPanel({
|
|
3118
3553
|
api,
|
|
3119
3554
|
externalId,
|
|
@@ -3141,14 +3576,14 @@ function PagePeekPanel({
|
|
|
3141
3576
|
cancelled = true;
|
|
3142
3577
|
};
|
|
3143
3578
|
}, [api, externalId]);
|
|
3144
|
-
return /* @__PURE__ */
|
|
3579
|
+
return /* @__PURE__ */ jsxs14("div", { class: "page-peek", role: "region", "aria-label": strings["page.peek.title"], children: [
|
|
3145
3580
|
/* @__PURE__ */ jsx13("div", { class: "page-peek-title", children: strings["page.peek.title"] }),
|
|
3146
|
-
rows === null && !failed && /* @__PURE__ */
|
|
3581
|
+
rows === null && !failed && /* @__PURE__ */ jsxs14("div", { class: "page-peek-skeleton", "aria-hidden": "true", children: [
|
|
3147
3582
|
/* @__PURE__ */ jsx13("div", {}),
|
|
3148
3583
|
" ",
|
|
3149
3584
|
/* @__PURE__ */ jsx13("div", {})
|
|
3150
3585
|
] }),
|
|
3151
|
-
rows !== null && rows.map((row) => /* @__PURE__ */
|
|
3586
|
+
rows !== null && rows.map((row) => /* @__PURE__ */ jsxs14(
|
|
3152
3587
|
"button",
|
|
3153
3588
|
{
|
|
3154
3589
|
type: "button",
|
|
@@ -3161,7 +3596,7 @@ function PagePeekPanel({
|
|
|
3161
3596
|
},
|
|
3162
3597
|
row.id
|
|
3163
3598
|
)),
|
|
3164
|
-
/* @__PURE__ */
|
|
3599
|
+
/* @__PURE__ */ jsxs14("div", { class: "page-peek-footer", children: [
|
|
3165
3600
|
/* @__PURE__ */ jsx13("button", { type: "button", class: "page-peek-viewall", onClick: onViewAll, children: open === 1 ? strings["page.peek.viewAll.one"] : strings["page.peek.viewAll"].replace("{n}", String(open)) }),
|
|
3166
3601
|
/* @__PURE__ */ jsx13("button", { type: "button", class: "page-peek-send", onClick: onSend, children: strings["fab.label"] })
|
|
3167
3602
|
] })
|
|
@@ -3621,6 +4056,46 @@ var WIDGET_STYLES = `
|
|
|
3621
4056
|
.error { color: #dc2626; font-size: 13px; }
|
|
3622
4057
|
.success { color: #059669; font-size: 13px; }
|
|
3623
4058
|
|
|
4059
|
+
/* REQ-A7: quiet transparency line under the form. */
|
|
4060
|
+
.capture-notice { color: var(--mfb-muted, #6b7280); font-size: 11px; line-height: 1.4; margin-top: 6px; }
|
|
4061
|
+
|
|
4062
|
+
/* #85: inline "#NN" report reference rendered as a link (a button for a11y). */
|
|
4063
|
+
.seq-link {
|
|
4064
|
+
display: inline;
|
|
4065
|
+
padding: 0;
|
|
4066
|
+
border: none;
|
|
4067
|
+
background: none;
|
|
4068
|
+
color: var(--mfb-accent);
|
|
4069
|
+
font: inherit;
|
|
4070
|
+
cursor: pointer;
|
|
4071
|
+
}
|
|
4072
|
+
.seq-link:hover { text-decoration: underline; }
|
|
4073
|
+
|
|
4074
|
+
/* ---- #89: discard-unsaved-changes confirmation over the form -------- */
|
|
4075
|
+
.discard-confirm {
|
|
4076
|
+
position: absolute;
|
|
4077
|
+
inset: 0;
|
|
4078
|
+
z-index: 5;
|
|
4079
|
+
display: flex;
|
|
4080
|
+
align-items: center;
|
|
4081
|
+
justify-content: center;
|
|
4082
|
+
padding: 16px;
|
|
4083
|
+
background: rgba(15, 23, 42, 0.45);
|
|
4084
|
+
border-radius: inherit;
|
|
4085
|
+
}
|
|
4086
|
+
.discard-confirm-card {
|
|
4087
|
+
max-width: 260px;
|
|
4088
|
+
text-align: center;
|
|
4089
|
+
background: var(--mfb-bg);
|
|
4090
|
+
color: var(--mfb-text);
|
|
4091
|
+
border-radius: var(--mfb-radius-lg);
|
|
4092
|
+
padding: 18px 16px;
|
|
4093
|
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.18);
|
|
4094
|
+
}
|
|
4095
|
+
.discard-confirm-title { font-weight: 600; font-size: 15px; margin: 0 0 4px; }
|
|
4096
|
+
.discard-confirm-body { font-size: 13px; margin: 0 0 14px; opacity: 0.8; }
|
|
4097
|
+
.discard-confirm-actions { display: flex; gap: 8px; justify-content: center; }
|
|
4098
|
+
|
|
3624
4099
|
/* ---- v0.6.0: manual screenshot upload + annotator -------------------- */
|
|
3625
4100
|
|
|
3626
4101
|
.mfb-sr-only {
|
|
@@ -3972,6 +4447,31 @@ var WIDGET_STYLES = `
|
|
|
3972
4447
|
justify-content: space-between;
|
|
3973
4448
|
}
|
|
3974
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
|
+
}
|
|
3975
4475
|
.mine-loading { color: var(--mfb-text-muted); font-size: 13px; }
|
|
3976
4476
|
.mine-empty {
|
|
3977
4477
|
text-align: center;
|
|
@@ -4177,12 +4677,77 @@ var WIDGET_STYLES = `
|
|
|
4177
4677
|
.comment-author--staff { color: #1e40af; }
|
|
4178
4678
|
.comment-author--system { color: var(--mfb-text-muted); }
|
|
4179
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
|
+
}
|
|
4180
4700
|
.comment-time {
|
|
4181
4701
|
font-size: 10px;
|
|
4182
4702
|
margin-top: 4px;
|
|
4183
4703
|
opacity: 0.7;
|
|
4184
4704
|
}
|
|
4185
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
|
+
}
|
|
4186
4751
|
.report-compose {
|
|
4187
4752
|
display: flex;
|
|
4188
4753
|
flex-direction: column;
|
|
@@ -4205,6 +4770,50 @@ var WIDGET_STYLES = `
|
|
|
4205
4770
|
justify-content: flex-end;
|
|
4206
4771
|
gap: 6px;
|
|
4207
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
|
+
}
|
|
4208
4817
|
|
|
4209
4818
|
/* v0.15.3 \u2014 teammate-viewing notice. Shown in place of the compose
|
|
4210
4819
|
* box when a non-submitter opens a project-wide row from the Board
|
|
@@ -5104,7 +5713,7 @@ var WIDGET_STYLES = `
|
|
|
5104
5713
|
`;
|
|
5105
5714
|
|
|
5106
5715
|
// src/widget/mount.tsx
|
|
5107
|
-
import { Fragment as Fragment3, jsx as jsx14, jsxs as
|
|
5716
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs15 } from "preact/jsx-runtime";
|
|
5108
5717
|
function computeFabVisible(opts, externalId, visibilityAllowed) {
|
|
5109
5718
|
if (!opts.showFAB) return false;
|
|
5110
5719
|
if (opts.getExternalId !== void 0 && !externalId) return false;
|
|
@@ -5120,6 +5729,7 @@ function mountWidget(options) {
|
|
|
5120
5729
|
shadow.appendChild(mountPoint);
|
|
5121
5730
|
let currentState = { open: false, status: "idle", tab: "send" };
|
|
5122
5731
|
let postSubmitTimer = null;
|
|
5732
|
+
let formDirty = false;
|
|
5123
5733
|
function rerender(state) {
|
|
5124
5734
|
currentState = state;
|
|
5125
5735
|
render(h(Root, { state }), mountPoint);
|
|
@@ -5145,9 +5755,13 @@ function mountWidget(options) {
|
|
|
5145
5755
|
const handleSubmit = useCallback2(async (values) => {
|
|
5146
5756
|
rerender({ ...currentState, status: "submitting" });
|
|
5147
5757
|
try {
|
|
5148
|
-
await options.onSubmit(values);
|
|
5758
|
+
const result = await options.onSubmit(values);
|
|
5149
5759
|
pageSignal.refresh();
|
|
5150
|
-
rerender({
|
|
5760
|
+
rerender({
|
|
5761
|
+
...currentState,
|
|
5762
|
+
status: "success",
|
|
5763
|
+
...result && result.seq !== void 0 && { submittedSeq: result.seq }
|
|
5764
|
+
});
|
|
5151
5765
|
if (postSubmitTimer !== null) clearTimeout(postSubmitTimer);
|
|
5152
5766
|
postSubmitTimer = setTimeout(() => {
|
|
5153
5767
|
postSubmitTimer = null;
|
|
@@ -5172,6 +5786,23 @@ function mountWidget(options) {
|
|
|
5172
5786
|
}
|
|
5173
5787
|
}, []);
|
|
5174
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
|
+
};
|
|
5175
5806
|
const pageActivityEnabled = options.showPageActivity !== false;
|
|
5176
5807
|
const pageSignal = usePageOpenCount({
|
|
5177
5808
|
...options.api !== void 0 && { api: options.api },
|
|
@@ -5214,8 +5845,8 @@ function mountWidget(options) {
|
|
|
5214
5845
|
if (peekTimers.current.open) clearTimeout(peekTimers.current.open);
|
|
5215
5846
|
peekTimers.current.close = setTimeout(() => setPeekOpen(false), 140);
|
|
5216
5847
|
};
|
|
5217
|
-
return /* @__PURE__ */
|
|
5218
|
-
fabVisible && /* @__PURE__ */
|
|
5848
|
+
return /* @__PURE__ */ jsxs15(Fragment3, { children: [
|
|
5849
|
+
fabVisible && /* @__PURE__ */ jsxs15(
|
|
5219
5850
|
"div",
|
|
5220
5851
|
{
|
|
5221
5852
|
class: "fab-area",
|
|
@@ -5263,14 +5894,62 @@ function mountWidget(options) {
|
|
|
5263
5894
|
]
|
|
5264
5895
|
}
|
|
5265
5896
|
),
|
|
5266
|
-
state.open && /* @__PURE__ */
|
|
5897
|
+
state.open && /* @__PURE__ */ jsxs15(
|
|
5267
5898
|
Modal,
|
|
5268
5899
|
{
|
|
5269
|
-
onDismiss: () =>
|
|
5900
|
+
onDismiss: (reason) => {
|
|
5901
|
+
if (reason !== "button" && formDirty && currentState.tab === "send" && currentState.status !== "submitting") {
|
|
5902
|
+
rerender({ ...currentState, confirmingDiscard: true });
|
|
5903
|
+
return;
|
|
5904
|
+
}
|
|
5905
|
+
formDirty = false;
|
|
5906
|
+
rerender(
|
|
5907
|
+
clearSelected({
|
|
5908
|
+
...currentState,
|
|
5909
|
+
open: false,
|
|
5910
|
+
status: "idle",
|
|
5911
|
+
confirmingDiscard: false
|
|
5912
|
+
})
|
|
5913
|
+
);
|
|
5914
|
+
},
|
|
5270
5915
|
closeLabel: options.strings["form.close"],
|
|
5271
5916
|
expanded: state.tab === "board",
|
|
5272
5917
|
children: [
|
|
5273
|
-
|
|
5918
|
+
state.confirmingDiscard && /* @__PURE__ */ jsx14("div", { class: "discard-confirm", role: "alertdialog", "aria-modal": "true", children: /* @__PURE__ */ jsxs15("div", { class: "discard-confirm-card", children: [
|
|
5919
|
+
/* @__PURE__ */ jsx14("p", { class: "discard-confirm-title", children: options.strings["form.discard.title"] }),
|
|
5920
|
+
/* @__PURE__ */ jsx14("p", { class: "discard-confirm-body", children: options.strings["form.discard.body"] }),
|
|
5921
|
+
/* @__PURE__ */ jsxs15("div", { class: "discard-confirm-actions", children: [
|
|
5922
|
+
/* @__PURE__ */ jsx14(
|
|
5923
|
+
"button",
|
|
5924
|
+
{
|
|
5925
|
+
type: "button",
|
|
5926
|
+
class: "btn",
|
|
5927
|
+
onClick: () => rerender({ ...currentState, confirmingDiscard: false }),
|
|
5928
|
+
children: options.strings["form.discard.keep"]
|
|
5929
|
+
}
|
|
5930
|
+
),
|
|
5931
|
+
/* @__PURE__ */ jsx14(
|
|
5932
|
+
"button",
|
|
5933
|
+
{
|
|
5934
|
+
type: "button",
|
|
5935
|
+
class: "btn btn--primary",
|
|
5936
|
+
onClick: () => {
|
|
5937
|
+
formDirty = false;
|
|
5938
|
+
rerender(
|
|
5939
|
+
clearSelected({
|
|
5940
|
+
...currentState,
|
|
5941
|
+
open: false,
|
|
5942
|
+
status: "idle",
|
|
5943
|
+
confirmingDiscard: false
|
|
5944
|
+
})
|
|
5945
|
+
);
|
|
5946
|
+
},
|
|
5947
|
+
children: options.strings["form.discard.confirm"]
|
|
5948
|
+
}
|
|
5949
|
+
)
|
|
5950
|
+
] })
|
|
5951
|
+
] }) }),
|
|
5952
|
+
showMineTab && /* @__PURE__ */ jsxs15("div", { class: "tab-strip", role: "tablist", children: [
|
|
5274
5953
|
/* @__PURE__ */ jsx14(
|
|
5275
5954
|
"button",
|
|
5276
5955
|
{
|
|
@@ -5316,7 +5995,7 @@ function mountWidget(options) {
|
|
|
5316
5995
|
}
|
|
5317
5996
|
)
|
|
5318
5997
|
] }),
|
|
5319
|
-
state.tab === "send" && /* @__PURE__ */
|
|
5998
|
+
state.tab === "send" && /* @__PURE__ */ jsxs15(Fragment3, { children: [
|
|
5320
5999
|
showMineTab && pageActivityEnabled && /* @__PURE__ */ jsx14(
|
|
5321
6000
|
PageActivityStrip,
|
|
5322
6001
|
{
|
|
@@ -5332,7 +6011,13 @@ function mountWidget(options) {
|
|
|
5332
6011
|
onSubmit: handleSubmit,
|
|
5333
6012
|
onCancel: () => rerender({ ...currentState, open: false, status: "idle" }),
|
|
5334
6013
|
status: state.status,
|
|
5335
|
-
...state.error !== void 0 && { errorMessage: state.error }
|
|
6014
|
+
...state.error !== void 0 && { errorMessage: state.error },
|
|
6015
|
+
...state.submittedSeq !== void 0 && {
|
|
6016
|
+
submittedSeq: state.submittedSeq
|
|
6017
|
+
},
|
|
6018
|
+
onDirtyChange: (d) => {
|
|
6019
|
+
formDirty = d;
|
|
6020
|
+
}
|
|
5336
6021
|
}
|
|
5337
6022
|
)
|
|
5338
6023
|
] }),
|
|
@@ -5342,7 +6027,8 @@ function mountWidget(options) {
|
|
|
5342
6027
|
api: options.api,
|
|
5343
6028
|
externalId,
|
|
5344
6029
|
strings: options.strings,
|
|
5345
|
-
onSelect: (row) => rerender({ ...currentState, selectedReportId: row.id })
|
|
6030
|
+
onSelect: (row) => rerender({ ...currentState, selectedReportId: row.id }),
|
|
6031
|
+
onOpenSeq: openSeq
|
|
5346
6032
|
}
|
|
5347
6033
|
),
|
|
5348
6034
|
(state.tab === "mine" || state.tab === "changelog") && options.api && externalId && state.selectedReportId && /* @__PURE__ */ jsx14(
|
|
@@ -5352,7 +6038,9 @@ function mountWidget(options) {
|
|
|
5352
6038
|
externalId,
|
|
5353
6039
|
reportId: state.selectedReportId,
|
|
5354
6040
|
strings: options.strings,
|
|
5355
|
-
onBack: () => rerender(clearSelected({ ...currentState }))
|
|
6041
|
+
onBack: () => rerender(clearSelected({ ...currentState })),
|
|
6042
|
+
onOpenSeq: openSeq,
|
|
6043
|
+
...buildPermalink && { buildPermalink }
|
|
5356
6044
|
}
|
|
5357
6045
|
),
|
|
5358
6046
|
state.tab === "changelog" && options.api && externalId && !state.selectedReportId && /* @__PURE__ */ jsx14(
|
|
@@ -5382,6 +6070,25 @@ function mountWidget(options) {
|
|
|
5382
6070
|
] });
|
|
5383
6071
|
}
|
|
5384
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
|
+
}
|
|
5385
6092
|
return {
|
|
5386
6093
|
open() {
|
|
5387
6094
|
openWidget(options.getExternalId?.());
|
|
@@ -5468,8 +6175,8 @@ function createFeedback(config) {
|
|
|
5468
6175
|
capture_method: captureMethod,
|
|
5469
6176
|
technical_context
|
|
5470
6177
|
};
|
|
5471
|
-
if ("0.
|
|
5472
|
-
payload.widget_version = "0.
|
|
6178
|
+
if ("0.29.0") {
|
|
6179
|
+
payload.widget_version = "0.29.0";
|
|
5473
6180
|
}
|
|
5474
6181
|
if (manualScreenshots?.length) {
|
|
5475
6182
|
payload.screenshots = manualScreenshots;
|
|
@@ -5504,9 +6211,8 @@ function createFeedback(config) {
|
|
|
5504
6211
|
host,
|
|
5505
6212
|
strings,
|
|
5506
6213
|
showFAB: config.showFAB ?? true,
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
},
|
|
6214
|
+
// Return the created report so the modal can acknowledge it by #seq (#82).
|
|
6215
|
+
onSubmit: (values) => buildAndSubmit(values),
|
|
5510
6216
|
api,
|
|
5511
6217
|
// Keep this a callback (not a snapshot) so the mount picks up identity
|
|
5512
6218
|
// changes that happen after createFeedback() — `notifyIdentityChanged()`
|
|
@@ -5596,4 +6302,4 @@ function createFeedback(config) {
|
|
|
5596
6302
|
export {
|
|
5597
6303
|
createFeedback
|
|
5598
6304
|
};
|
|
5599
|
-
//# sourceMappingURL=chunk-
|
|
6305
|
+
//# sourceMappingURL=chunk-7FGWO6KO.mjs.map
|