@mhosaic/feedback 0.27.0 → 0.28.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-BXUX5UTI.mjs → chunk-FD3CJBMR.mjs} +406 -177
- package/dist/chunk-FD3CJBMR.mjs.map +1 -0
- package/dist/embed.min.js +45 -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 +46 -6
- package/dist/widget.min.js.map +1 -1
- package/package.json +31 -1
- package/dist/chunk-BXUX5UTI.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,22 +151,21 @@ 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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
161
|
+
return readJsonObject(response, "getReport");
|
|
162
|
+
}
|
|
163
|
+
async function getReportBySeq(seq, externalId) {
|
|
164
|
+
const response = await fetcher(
|
|
165
|
+
`${endpoint}/api/feedback/v1/reports/widget/by-seq/${seq}/`,
|
|
166
|
+
{ method: "GET", headers: widgetHeaders(externalId) }
|
|
167
|
+
);
|
|
168
|
+
return readJsonObject(response, "getReportBySeq");
|
|
138
169
|
}
|
|
139
170
|
async function addComment(reportId, externalId, body, clientNonce) {
|
|
140
171
|
const response = await fetcher(
|
|
@@ -215,11 +246,11 @@ function createApiClient(options) {
|
|
|
215
246
|
if (response.status === 404) {
|
|
216
247
|
return { count: 0, next: null, previous: null, results: [] };
|
|
217
248
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
throw new Error(
|
|
249
|
+
const page = await readJsonObject(response, "listBoard");
|
|
250
|
+
if (!Array.isArray(page.results)) {
|
|
251
|
+
throw new Error("listBoard: unexpected response shape (missing results[])");
|
|
221
252
|
}
|
|
222
|
-
return
|
|
253
|
+
return page;
|
|
223
254
|
}
|
|
224
255
|
async function fetchBoardKpis(externalId, filters) {
|
|
225
256
|
const response = await fetcher(
|
|
@@ -229,11 +260,7 @@ function createApiClient(options) {
|
|
|
229
260
|
if (response.status === 404) {
|
|
230
261
|
return { total: 0, by_status: {}, resolution_rate: 0, scope: "mine" };
|
|
231
262
|
}
|
|
232
|
-
|
|
233
|
-
const text = await response.text().catch(() => "");
|
|
234
|
-
throw new Error(`fetchBoardKpis failed: ${response.status} ${text}`);
|
|
235
|
-
}
|
|
236
|
-
return response.json();
|
|
263
|
+
return readJsonObject(response, "fetchBoardKpis");
|
|
237
264
|
}
|
|
238
265
|
async function checkVisibility(externalId, email) {
|
|
239
266
|
const response = await fetcher(
|
|
@@ -262,6 +289,7 @@ function createApiClient(options) {
|
|
|
262
289
|
listMine,
|
|
263
290
|
listChangelog,
|
|
264
291
|
getReport,
|
|
292
|
+
getReportBySeq,
|
|
265
293
|
addComment,
|
|
266
294
|
closeAsResolved,
|
|
267
295
|
reopenUnresolved,
|
|
@@ -573,6 +601,11 @@ var DEFAULT_STRINGS = {
|
|
|
573
601
|
"form.close": "Close",
|
|
574
602
|
"form.submitting": "Sending\u2026",
|
|
575
603
|
"form.success": "Thanks \u2014 your feedback was sent.",
|
|
604
|
+
"form.success.seq": "Thanks \u2014 report #{seq} was sent. \u2713",
|
|
605
|
+
"form.discard.title": "Unsaved changes",
|
|
606
|
+
"form.discard.body": "Discard your feedback?",
|
|
607
|
+
"form.discard.keep": "Keep editing",
|
|
608
|
+
"form.discard.confirm": "Discard",
|
|
576
609
|
"form.error": "Could not send. Please try again.",
|
|
577
610
|
"form.description.required": "Please describe the issue before sending.",
|
|
578
611
|
"form.screenshot.label": "Screenshot",
|
|
@@ -585,6 +618,7 @@ var DEFAULT_STRINGS = {
|
|
|
585
618
|
"form.screenshot.error_size": "File too large (max {max} MB).",
|
|
586
619
|
"form.screenshot.error_count": "Too many screenshots (max {max}).",
|
|
587
620
|
"form.context.label": "Page",
|
|
621
|
+
"form.capture.notice": "Console, network activity and errors are captured automatically to help us debug.",
|
|
588
622
|
"type.bug": "Bug",
|
|
589
623
|
"type.feature": "Feature request",
|
|
590
624
|
"type.question": "Question",
|
|
@@ -663,6 +697,8 @@ var DEFAULT_STRINGS = {
|
|
|
663
697
|
"mine.refresh": "Refresh",
|
|
664
698
|
"mine.loading": "Loading\u2026",
|
|
665
699
|
"mine.error": "Could not load your reports.",
|
|
700
|
+
"error.rate_limited": "Too many requests \u2014 retry in {seconds}s.",
|
|
701
|
+
"error.rate_limited_generic": "Too many requests \u2014 try again in a moment.",
|
|
666
702
|
"mine.replies_one": "1 reply",
|
|
667
703
|
"mine.replies_many": "{count} replies",
|
|
668
704
|
"mine.filter.empty": "No reports match this filter.",
|
|
@@ -737,6 +773,11 @@ var FRENCH_STRINGS = {
|
|
|
737
773
|
"form.close": "Fermer",
|
|
738
774
|
"form.submitting": "Envoi\u2026",
|
|
739
775
|
"form.success": "Merci \u2014 votre commentaire a \xE9t\xE9 envoy\xE9.",
|
|
776
|
+
"form.success.seq": "Merci \u2014 rapport n\xB0{seq} envoy\xE9. \u2713",
|
|
777
|
+
"form.discard.title": "Modifications non enregistr\xE9es",
|
|
778
|
+
"form.discard.body": "Abandonner votre commentaire ?",
|
|
779
|
+
"form.discard.keep": "Continuer la saisie",
|
|
780
|
+
"form.discard.confirm": "Abandonner",
|
|
740
781
|
"form.error": "\xC9chec d\u2019envoi. Veuillez r\xE9essayer.",
|
|
741
782
|
"form.description.required": "D\xE9crivez le probl\xE8me avant d\u2019envoyer.",
|
|
742
783
|
"form.screenshot.label": "Capture d\u2019\xE9cran",
|
|
@@ -749,6 +790,7 @@ var FRENCH_STRINGS = {
|
|
|
749
790
|
"form.screenshot.error_size": "Fichier trop volumineux (max {max} Mo).",
|
|
750
791
|
"form.screenshot.error_count": "Trop de captures d\u2019\xE9cran (max {max}).",
|
|
751
792
|
"form.context.label": "Page",
|
|
793
|
+
"form.capture.notice": "La console, l\u2019activit\xE9 r\xE9seau et les erreurs sont captur\xE9es automatiquement pour faciliter le diagnostic.",
|
|
752
794
|
"type.bug": "Bogue",
|
|
753
795
|
"type.feature": "Suggestion",
|
|
754
796
|
"type.question": "Question",
|
|
@@ -827,6 +869,8 @@ var FRENCH_STRINGS = {
|
|
|
827
869
|
"mine.refresh": "Actualiser",
|
|
828
870
|
"mine.loading": "Chargement\u2026",
|
|
829
871
|
"mine.error": "Impossible de charger vos rapports.",
|
|
872
|
+
"error.rate_limited": "Trop de requ\xEAtes \u2014 r\xE9essaie dans {seconds} s.",
|
|
873
|
+
"error.rate_limited_generic": "Trop de requ\xEAtes \u2014 r\xE9essaie dans un instant.",
|
|
830
874
|
"mine.replies_one": "1 r\xE9ponse",
|
|
831
875
|
"mine.replies_many": "{count} r\xE9ponses",
|
|
832
876
|
"mine.filter.empty": "Aucun rapport ne correspond \xE0 ce filtre.",
|
|
@@ -995,21 +1039,64 @@ function startPoll(tick, intervalMs) {
|
|
|
995
1039
|
};
|
|
996
1040
|
}
|
|
997
1041
|
|
|
1042
|
+
// src/widget/rateLimit.ts
|
|
1043
|
+
function rateLimitMessage(err, strings) {
|
|
1044
|
+
if (!(err instanceof RateLimitError)) return null;
|
|
1045
|
+
return err.retryAfterSeconds > 0 ? strings["error.rate_limited"].replace(
|
|
1046
|
+
"{seconds}",
|
|
1047
|
+
String(err.retryAfterSeconds)
|
|
1048
|
+
) : strings["error.rate_limited_generic"];
|
|
1049
|
+
}
|
|
1050
|
+
|
|
998
1051
|
// src/widget/ReportDetailView.tsx
|
|
999
1052
|
import { useEffect, useRef, useState } from "preact/hooks";
|
|
1000
1053
|
|
|
1054
|
+
// src/widget/linkifySeq.tsx
|
|
1055
|
+
import { jsxs } from "preact/jsx-runtime";
|
|
1056
|
+
var SEQ_RE = /#(\d+)/g;
|
|
1057
|
+
function linkifySeq(text, onOpenSeq) {
|
|
1058
|
+
const value = text ?? "";
|
|
1059
|
+
if (!value || !onOpenSeq) return [value];
|
|
1060
|
+
const parts = [];
|
|
1061
|
+
let last = 0;
|
|
1062
|
+
SEQ_RE.lastIndex = 0;
|
|
1063
|
+
let m;
|
|
1064
|
+
while ((m = SEQ_RE.exec(value)) !== null) {
|
|
1065
|
+
if (m.index > last) parts.push(value.slice(last, m.index));
|
|
1066
|
+
const seq = Number(m[1]);
|
|
1067
|
+
parts.push(
|
|
1068
|
+
/* @__PURE__ */ jsxs(
|
|
1069
|
+
"button",
|
|
1070
|
+
{
|
|
1071
|
+
type: "button",
|
|
1072
|
+
class: "seq-link",
|
|
1073
|
+
onClick: () => onOpenSeq(seq),
|
|
1074
|
+
"aria-label": `Open report #${seq}`,
|
|
1075
|
+
children: [
|
|
1076
|
+
"#",
|
|
1077
|
+
seq
|
|
1078
|
+
]
|
|
1079
|
+
}
|
|
1080
|
+
)
|
|
1081
|
+
);
|
|
1082
|
+
last = m.index + m[0].length;
|
|
1083
|
+
}
|
|
1084
|
+
if (last < value.length) parts.push(value.slice(last));
|
|
1085
|
+
return parts.length ? parts : [value];
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1001
1088
|
// src/widget/CommentBubble.tsx
|
|
1002
|
-
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
1003
|
-
function CommentBubble({ comment, strings }) {
|
|
1089
|
+
import { jsx, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
1090
|
+
function CommentBubble({ comment, strings, onOpenSeq }) {
|
|
1004
1091
|
const isMine = comment.is_mine;
|
|
1005
1092
|
const isAgent = !isMine && comment.author_source === "mcp";
|
|
1006
1093
|
const isSystem = !isMine && comment.author_source === "system";
|
|
1007
1094
|
const variant = isAgent ? "mcp" : isSystem ? "system" : "staff";
|
|
1008
1095
|
const labelKey = variant === "mcp" ? "detail.author.mcp" : variant === "system" ? "detail.author.system" : "detail.author.staff";
|
|
1009
1096
|
const label = comment.author_label || strings[labelKey];
|
|
1010
|
-
return /* @__PURE__ */
|
|
1097
|
+
return /* @__PURE__ */ jsxs2("div", { class: `comment-bubble ${isMine ? "is-mine" : "is-other"}`, children: [
|
|
1011
1098
|
!isMine && label && /* @__PURE__ */ jsx("div", { class: `comment-author comment-author--${variant}`, children: label }),
|
|
1012
|
-
/* @__PURE__ */ jsx("div", { class: "comment-body", children: comment.body }),
|
|
1099
|
+
/* @__PURE__ */ jsx("div", { class: "comment-body", children: linkifySeq(comment.body, onOpenSeq) }),
|
|
1013
1100
|
/* @__PURE__ */ jsx("div", { class: "comment-time", children: formatTime(comment.created_at) })
|
|
1014
1101
|
] });
|
|
1015
1102
|
}
|
|
@@ -1065,7 +1152,7 @@ function safeExternalHref(url) {
|
|
|
1065
1152
|
}
|
|
1066
1153
|
|
|
1067
1154
|
// src/widget/ReportDetailView.tsx
|
|
1068
|
-
import { Fragment, jsx as jsx2, jsxs as
|
|
1155
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
1069
1156
|
var POLL_MS = 3e4;
|
|
1070
1157
|
function ReportDetailView({
|
|
1071
1158
|
api,
|
|
@@ -1074,7 +1161,8 @@ function ReportDetailView({
|
|
|
1074
1161
|
strings,
|
|
1075
1162
|
onBack,
|
|
1076
1163
|
canModerate = true,
|
|
1077
|
-
variant = "modal"
|
|
1164
|
+
variant = "modal",
|
|
1165
|
+
onOpenSeq
|
|
1078
1166
|
}) {
|
|
1079
1167
|
const [detail, setDetail] = useState(null);
|
|
1080
1168
|
const [error, setError] = useState(null);
|
|
@@ -1093,7 +1181,8 @@ function ReportDetailView({
|
|
|
1093
1181
|
} catch (err) {
|
|
1094
1182
|
if (typeof console !== "undefined") console.warn("[mhosaic] getReport:", err);
|
|
1095
1183
|
if (!mountedRef.current) return false;
|
|
1096
|
-
|
|
1184
|
+
const rl = rateLimitMessage(err, strings);
|
|
1185
|
+
setError(rl ?? "load_failed");
|
|
1097
1186
|
return false;
|
|
1098
1187
|
}
|
|
1099
1188
|
};
|
|
@@ -1161,10 +1250,20 @@ function ReportDetailView({
|
|
|
1161
1250
|
return /* @__PURE__ */ jsx2("div", { class: "mine-loading", children: strings["mine.loading"] });
|
|
1162
1251
|
}
|
|
1163
1252
|
if (!detail) {
|
|
1164
|
-
|
|
1253
|
+
if (error && error !== "load_failed") {
|
|
1254
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-empty-state", role: "alert", children: [
|
|
1255
|
+
/* @__PURE__ */ jsx2("p", { class: "report-detail-empty-state-body", children: error }),
|
|
1256
|
+
/* @__PURE__ */ jsx2("button", { type: "button", class: "btn", onClick: () => void fetchDetail(), children: strings["board.retry"] }),
|
|
1257
|
+
/* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
|
|
1258
|
+
"\u2190 ",
|
|
1259
|
+
strings["detail.load_failed.cta"]
|
|
1260
|
+
] })
|
|
1261
|
+
] });
|
|
1262
|
+
}
|
|
1263
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-empty-state", role: "alert", children: [
|
|
1165
1264
|
/* @__PURE__ */ jsx2("p", { class: "report-detail-empty-state-title", children: strings["detail.load_failed.title"] }),
|
|
1166
1265
|
/* @__PURE__ */ jsx2("p", { class: "report-detail-empty-state-body", children: strings["detail.load_failed.body"] }),
|
|
1167
|
-
/* @__PURE__ */
|
|
1266
|
+
/* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
|
|
1168
1267
|
"\u2190 ",
|
|
1169
1268
|
strings["detail.load_failed.cta"]
|
|
1170
1269
|
] })
|
|
@@ -1174,16 +1273,16 @@ function ReportDetailView({
|
|
|
1174
1273
|
const canAct = detail.can_moderate ?? isMine;
|
|
1175
1274
|
const showCloseCta = canAct && detail.status === "awaiting_validation";
|
|
1176
1275
|
const showComposeBox = canAct;
|
|
1177
|
-
return /* @__PURE__ */
|
|
1178
|
-
variant === "modal" && /* @__PURE__ */
|
|
1179
|
-
/* @__PURE__ */
|
|
1276
|
+
return /* @__PURE__ */ jsxs3("div", { class: `report-detail variant-${variant}`, children: [
|
|
1277
|
+
variant === "modal" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header", children: [
|
|
1278
|
+
/* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
|
|
1180
1279
|
"\u2190 ",
|
|
1181
1280
|
strings["detail.back"]
|
|
1182
1281
|
] }),
|
|
1183
1282
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
|
|
1184
1283
|
] }),
|
|
1185
|
-
variant === "board" && /* @__PURE__ */
|
|
1186
|
-
/* @__PURE__ */
|
|
1284
|
+
variant === "board" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header report-detail-header--board", children: [
|
|
1285
|
+
/* @__PURE__ */ jsxs3(
|
|
1187
1286
|
"button",
|
|
1188
1287
|
{
|
|
1189
1288
|
type: "button",
|
|
@@ -1198,9 +1297,9 @@ function ReportDetailView({
|
|
|
1198
1297
|
),
|
|
1199
1298
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
|
|
1200
1299
|
] }),
|
|
1201
|
-
/* @__PURE__ */
|
|
1300
|
+
/* @__PURE__ */ jsxs3("div", { class: "report-detail-body", children: [
|
|
1202
1301
|
/* @__PURE__ */ jsx2(ContextBlock, { detail, strings }),
|
|
1203
|
-
/* @__PURE__ */ jsx2("p", { class: "report-detail-description", children: detail.description }),
|
|
1302
|
+
/* @__PURE__ */ jsx2("p", { class: "report-detail-description", children: linkifySeq(detail.description, onOpenSeq) }),
|
|
1204
1303
|
(detail.screenshots?.length ? detail.screenshots.map((s) => s.url) : [detail.screenshot_url]).filter((url) => Boolean(url)).map((url) => {
|
|
1205
1304
|
const safeHref = safeExternalHref(url);
|
|
1206
1305
|
return safeHref ? /* @__PURE__ */ jsx2(
|
|
@@ -1215,10 +1314,17 @@ function ReportDetailView({
|
|
|
1215
1314
|
) : /* @__PURE__ */ jsx2("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy" }) });
|
|
1216
1315
|
}),
|
|
1217
1316
|
/* @__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(
|
|
1317
|
+
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(
|
|
1318
|
+
CommentBubble,
|
|
1319
|
+
{
|
|
1320
|
+
comment: c,
|
|
1321
|
+
strings,
|
|
1322
|
+
...onOpenSeq && { onOpenSeq }
|
|
1323
|
+
}
|
|
1324
|
+
) })) }),
|
|
1219
1325
|
detail.status_history && detail.status_history.length > 0 && /* @__PURE__ */ jsx2(StatusHistorySection, { rows: detail.status_history, strings }),
|
|
1220
1326
|
detail.technical_context && /* @__PURE__ */ jsx2(TechnicalContextDrawer, { ctx: detail.technical_context, strings }),
|
|
1221
|
-
showComposeBox ? /* @__PURE__ */
|
|
1327
|
+
showComposeBox ? /* @__PURE__ */ jsxs3("div", { class: "report-compose", children: [
|
|
1222
1328
|
/* @__PURE__ */ jsx2(
|
|
1223
1329
|
"textarea",
|
|
1224
1330
|
{
|
|
@@ -1228,7 +1334,7 @@ function ReportDetailView({
|
|
|
1228
1334
|
disabled: sending
|
|
1229
1335
|
}
|
|
1230
1336
|
),
|
|
1231
|
-
/* @__PURE__ */
|
|
1337
|
+
/* @__PURE__ */ jsxs3("div", { class: "report-compose-actions", children: [
|
|
1232
1338
|
showCloseCta && /* @__PURE__ */ jsx2(
|
|
1233
1339
|
"button",
|
|
1234
1340
|
{
|
|
@@ -1278,19 +1384,19 @@ function appendComment(current, next) {
|
|
|
1278
1384
|
function ContextBlock({ detail, strings }) {
|
|
1279
1385
|
const captureKey = `detail.context.capture.${detail.capture_method}`;
|
|
1280
1386
|
const captureLabel = strings[captureKey] ?? detail.capture_method;
|
|
1281
|
-
return /* @__PURE__ */
|
|
1282
|
-
/* @__PURE__ */
|
|
1387
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-context", children: [
|
|
1388
|
+
/* @__PURE__ */ jsxs3("div", { class: "report-detail-context-pills", children: [
|
|
1283
1389
|
/* @__PURE__ */ jsx2("span", { class: "pill pill-type", children: strings[`type.${detail.feedback_type}`] }),
|
|
1284
1390
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-severity pill-severity--${detail.severity}`, children: strings[`severity.${detail.severity}`] }),
|
|
1285
1391
|
/* @__PURE__ */ jsx2("span", { class: "pill pill-capture", children: captureLabel })
|
|
1286
1392
|
] }),
|
|
1287
|
-
/* @__PURE__ */
|
|
1393
|
+
/* @__PURE__ */ jsxs3("div", { class: "report-detail-context-line", children: [
|
|
1288
1394
|
/* @__PURE__ */ jsx2("span", { class: "report-detail-context-label", children: strings["detail.context.submitted_at"] }),
|
|
1289
1395
|
/* @__PURE__ */ jsx2("span", { children: formatSubmittedAt(detail.created_at) })
|
|
1290
1396
|
] }),
|
|
1291
1397
|
detail.page_url && (() => {
|
|
1292
1398
|
const safeHref = safeExternalHref(detail.page_url);
|
|
1293
|
-
return /* @__PURE__ */
|
|
1399
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-context-line", title: detail.page_url, children: [
|
|
1294
1400
|
/* @__PURE__ */ jsx2("span", { class: "report-detail-context-label", children: strings["detail.context.page"] }),
|
|
1295
1401
|
safeHref ? /* @__PURE__ */ jsx2(
|
|
1296
1402
|
"a",
|
|
@@ -1327,9 +1433,9 @@ function TechnicalContextDrawer({ ctx, strings }) {
|
|
|
1327
1433
|
if (!hasAny) return null;
|
|
1328
1434
|
const errorCount = errors.length;
|
|
1329
1435
|
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__ */
|
|
1436
|
+
return /* @__PURE__ */ jsxs3("details", { class: "report-detail-tech", children: [
|
|
1331
1437
|
/* @__PURE__ */ jsx2("summary", { children: summary }),
|
|
1332
|
-
/* @__PURE__ */
|
|
1438
|
+
/* @__PURE__ */ jsxs3("div", { class: "tech-body", children: [
|
|
1333
1439
|
device && /* @__PURE__ */ jsx2(DeviceSection, { device, strings }),
|
|
1334
1440
|
errors.length > 0 && /* @__PURE__ */ jsx2(ErrorsSection, { errors, strings }),
|
|
1335
1441
|
consoleLogs.length > 0 && /* @__PURE__ */ jsx2(ConsoleSection, { logs: consoleLogs, strings }),
|
|
@@ -1354,9 +1460,9 @@ function DeviceSection({
|
|
|
1354
1460
|
if (device?.timezone) parts.push({ label: strings["detail.tech.device.timezone"], value: device.timezone });
|
|
1355
1461
|
if (device?.connection) parts.push({ label: strings["detail.tech.device.connection"], value: device.connection });
|
|
1356
1462
|
if (parts.length === 0) return null;
|
|
1357
|
-
return /* @__PURE__ */
|
|
1463
|
+
return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
|
|
1358
1464
|
/* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.device"] }),
|
|
1359
|
-
/* @__PURE__ */ jsx2("dl", { class: "tech-device", children: parts.map((p) => /* @__PURE__ */
|
|
1465
|
+
/* @__PURE__ */ jsx2("dl", { class: "tech-device", children: parts.map((p) => /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1360
1466
|
/* @__PURE__ */ jsx2("dt", { children: p.label }),
|
|
1361
1467
|
/* @__PURE__ */ jsx2("dd", { children: p.value })
|
|
1362
1468
|
] })) })
|
|
@@ -1366,9 +1472,9 @@ function ErrorsSection({
|
|
|
1366
1472
|
errors,
|
|
1367
1473
|
strings
|
|
1368
1474
|
}) {
|
|
1369
|
-
return /* @__PURE__ */
|
|
1475
|
+
return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
|
|
1370
1476
|
/* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.errors"] }),
|
|
1371
|
-
/* @__PURE__ */ jsx2("ul", { class: "tech-errors", children: errors.map((e) => /* @__PURE__ */
|
|
1477
|
+
/* @__PURE__ */ jsx2("ul", { class: "tech-errors", children: errors.map((e) => /* @__PURE__ */ jsxs3("li", { children: [
|
|
1372
1478
|
/* @__PURE__ */ jsx2("div", { class: "tech-errors-msg", children: e.message }),
|
|
1373
1479
|
e.stack && /* @__PURE__ */ jsx2("pre", { class: "tech-errors-stack", children: truncateStack(e.stack) })
|
|
1374
1480
|
] })) })
|
|
@@ -1378,9 +1484,9 @@ function ConsoleSection({
|
|
|
1378
1484
|
logs,
|
|
1379
1485
|
strings
|
|
1380
1486
|
}) {
|
|
1381
|
-
return /* @__PURE__ */
|
|
1487
|
+
return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
|
|
1382
1488
|
/* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.console"] }),
|
|
1383
|
-
/* @__PURE__ */ jsx2("ul", { class: "tech-console", children: logs.map((l) => /* @__PURE__ */
|
|
1489
|
+
/* @__PURE__ */ jsx2("ul", { class: "tech-console", children: logs.map((l) => /* @__PURE__ */ jsxs3("li", { class: `tech-console-row tech-console-row--${l.level}`, children: [
|
|
1384
1490
|
/* @__PURE__ */ jsx2("span", { class: "tech-console-level", children: l.level }),
|
|
1385
1491
|
/* @__PURE__ */ jsx2("span", { class: "tech-console-msg", children: l.message })
|
|
1386
1492
|
] })) })
|
|
@@ -1390,15 +1496,15 @@ function NetworkSection({
|
|
|
1390
1496
|
rows,
|
|
1391
1497
|
strings
|
|
1392
1498
|
}) {
|
|
1393
|
-
return /* @__PURE__ */
|
|
1499
|
+
return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
|
|
1394
1500
|
/* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.network"] }),
|
|
1395
1501
|
/* @__PURE__ */ jsx2("ul", { class: "tech-network", children: rows.map((n) => {
|
|
1396
1502
|
const failed = n.status >= 400 || n.status === 0;
|
|
1397
|
-
return /* @__PURE__ */
|
|
1503
|
+
return /* @__PURE__ */ jsxs3("li", { class: `tech-network-row${failed ? " tech-network-row--fail" : ""}`, children: [
|
|
1398
1504
|
/* @__PURE__ */ jsx2("span", { class: "tech-network-status", children: n.status || "\u2014" }),
|
|
1399
1505
|
/* @__PURE__ */ jsx2("span", { class: "tech-network-method", children: n.method }),
|
|
1400
1506
|
/* @__PURE__ */ jsx2("span", { class: "tech-network-url", title: n.url, children: truncateUrl(n.url, 56) }),
|
|
1401
|
-
/* @__PURE__ */
|
|
1507
|
+
/* @__PURE__ */ jsxs3("span", { class: "tech-network-time", children: [
|
|
1402
1508
|
Math.round(n.durationMs),
|
|
1403
1509
|
"ms"
|
|
1404
1510
|
] })
|
|
@@ -1412,15 +1518,15 @@ function truncateStack(stack) {
|
|
|
1412
1518
|
return lines.slice(0, 12).join("\n") + "\n \u2026";
|
|
1413
1519
|
}
|
|
1414
1520
|
function StatusHistorySection({ rows, strings }) {
|
|
1415
|
-
return /* @__PURE__ */
|
|
1521
|
+
return /* @__PURE__ */ jsxs3("div", { class: "report-detail-history", children: [
|
|
1416
1522
|
/* @__PURE__ */ jsx2("h3", { class: "report-detail-section", children: strings["detail.history"] }),
|
|
1417
1523
|
/* @__PURE__ */ jsx2("ol", { class: "status-history", children: rows.map((r) => {
|
|
1418
1524
|
const from = strings[`status.${r.from_status}`] ?? r.from_status;
|
|
1419
1525
|
const to = strings[`status.${r.to_status}`] ?? r.to_status;
|
|
1420
1526
|
const sourceKey = r.changed_by_source === "mcp" ? "detail.author.mcp" : r.changed_by_source === "system" ? "detail.author.system" : "detail.author.staff";
|
|
1421
|
-
return /* @__PURE__ */
|
|
1527
|
+
return /* @__PURE__ */ jsxs3("li", { class: "status-history-row", children: [
|
|
1422
1528
|
/* @__PURE__ */ jsx2("span", { class: "status-history-time", children: formatSubmittedAt(r.created_at) }),
|
|
1423
|
-
/* @__PURE__ */
|
|
1529
|
+
/* @__PURE__ */ jsxs3("span", { class: "status-history-transition", children: [
|
|
1424
1530
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${r.from_status}`, children: from }),
|
|
1425
1531
|
/* @__PURE__ */ jsx2("span", { class: "status-history-arrow", "aria-hidden": "true", children: "\u2192" }),
|
|
1426
1532
|
/* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${r.to_status}`, children: to })
|
|
@@ -1432,7 +1538,7 @@ function StatusHistorySection({ rows, strings }) {
|
|
|
1432
1538
|
}
|
|
1433
1539
|
|
|
1434
1540
|
// src/widget/BoardView.tsx
|
|
1435
|
-
import { Fragment as Fragment2, jsx as jsx3, jsxs as
|
|
1541
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
1436
1542
|
var POLL_MS2 = 3e4;
|
|
1437
1543
|
var SORT_OPTIONS = ["recent", "oldest", "updated", "severity", "status"];
|
|
1438
1544
|
var STATUSES = [
|
|
@@ -1470,11 +1576,10 @@ function BoardView({
|
|
|
1470
1576
|
if (!thisPage) return;
|
|
1471
1577
|
return onLocationChange(() => setReloadTick((t) => t + 1));
|
|
1472
1578
|
}, [thisPage]);
|
|
1473
|
-
const fetchFilters = useMemo(
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
);
|
|
1579
|
+
const fetchFilters = useMemo(() => {
|
|
1580
|
+
const searching = !!filters.q?.trim();
|
|
1581
|
+
return thisPage && !searching ? { ...filters, pagePath: currentPagePath(getCurrentPage !== void 0 ? { getCurrentPage } : {}) } : filters;
|
|
1582
|
+
}, [filtersHash(filters), thisPage, reloadTick]);
|
|
1478
1583
|
useEffect2(() => {
|
|
1479
1584
|
let cancelled = false;
|
|
1480
1585
|
setLoading(true);
|
|
@@ -1491,7 +1596,7 @@ function BoardView({
|
|
|
1491
1596
|
return true;
|
|
1492
1597
|
} catch (e) {
|
|
1493
1598
|
if (cancelled) return false;
|
|
1494
|
-
setError(e
|
|
1599
|
+
setError(rateLimitMessage(e, strings) ?? strings["board.list.error"]);
|
|
1495
1600
|
return false;
|
|
1496
1601
|
} finally {
|
|
1497
1602
|
if (!cancelled) setLoading(false);
|
|
@@ -1510,7 +1615,7 @@ function BoardView({
|
|
|
1510
1615
|
setSelectedId(row.id);
|
|
1511
1616
|
setDetailKey((k) => k + 1);
|
|
1512
1617
|
};
|
|
1513
|
-
return /* @__PURE__ */
|
|
1618
|
+
return /* @__PURE__ */ jsxs4("div", { class: "board-view", children: [
|
|
1514
1619
|
/* @__PURE__ */ jsx3(BoardHeader, { strings, kpis, thisPage }),
|
|
1515
1620
|
/* @__PURE__ */ jsx3(
|
|
1516
1621
|
BoardFilters,
|
|
@@ -1523,10 +1628,10 @@ function BoardView({
|
|
|
1523
1628
|
onToggleThisPage: () => setThisPage((v) => !v)
|
|
1524
1629
|
}
|
|
1525
1630
|
),
|
|
1526
|
-
/* @__PURE__ */
|
|
1527
|
-
/* @__PURE__ */
|
|
1528
|
-
error && /* @__PURE__ */
|
|
1529
|
-
/* @__PURE__ */ jsx3("span", { children:
|
|
1631
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-body", children: [
|
|
1632
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-list-wrap", "aria-busy": loading && !page, children: [
|
|
1633
|
+
error && /* @__PURE__ */ jsxs4("div", { class: "board-error", role: "alert", children: [
|
|
1634
|
+
/* @__PURE__ */ jsx3("span", { children: error }),
|
|
1530
1635
|
/* @__PURE__ */ jsx3(
|
|
1531
1636
|
"button",
|
|
1532
1637
|
{
|
|
@@ -1571,10 +1676,17 @@ function BoardView({
|
|
|
1571
1676
|
strings,
|
|
1572
1677
|
onBack: () => setSelectedId(null),
|
|
1573
1678
|
canModerate: selectedRow.can_moderate ?? selectedRow.is_mine,
|
|
1574
|
-
variant: "board"
|
|
1679
|
+
variant: "board",
|
|
1680
|
+
onOpenSeq: (seq) => {
|
|
1681
|
+
void api.getReportBySeq(seq, externalId).then((r) => {
|
|
1682
|
+
setSelectedId(r.id);
|
|
1683
|
+
setDetailKey((k) => k + 1);
|
|
1684
|
+
}).catch(() => {
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1575
1687
|
},
|
|
1576
1688
|
detailKey
|
|
1577
|
-
) : /* @__PURE__ */
|
|
1689
|
+
) : /* @__PURE__ */ jsxs4("div", { class: "board-detail-empty", children: [
|
|
1578
1690
|
/* @__PURE__ */ jsx3(DetailEmptyIllustration, {}),
|
|
1579
1691
|
/* @__PURE__ */ jsx3("p", { children: strings["board.detail.empty"] })
|
|
1580
1692
|
] }) })
|
|
@@ -1587,10 +1699,10 @@ function BoardHeader({
|
|
|
1587
1699
|
thisPage
|
|
1588
1700
|
}) {
|
|
1589
1701
|
const scopeLabel = thisPage ? strings["board.scope.onThisPage"] : kpis?.scope === "project" ? strings["board.scope.project"] : strings["board.scope.mine"];
|
|
1590
|
-
return /* @__PURE__ */
|
|
1591
|
-
/* @__PURE__ */
|
|
1702
|
+
return /* @__PURE__ */ jsxs4("header", { class: "board-header", children: [
|
|
1703
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-header-title", children: [
|
|
1592
1704
|
/* @__PURE__ */ jsx3("span", { class: "board-header-emoji", "aria-hidden": "true", children: "\u{1F4CB}" }),
|
|
1593
|
-
/* @__PURE__ */
|
|
1705
|
+
/* @__PURE__ */ jsxs4("div", { children: [
|
|
1594
1706
|
/* @__PURE__ */ jsx3("h2", { class: "board-header-h", children: strings["tab.board"] }),
|
|
1595
1707
|
/* @__PURE__ */ jsx3("p", { class: "board-header-sub", children: kpis ? `${kpis.total} \xB7 ${scopeLabel}` : scopeLabel })
|
|
1596
1708
|
] })
|
|
@@ -1628,7 +1740,7 @@ function BoardKpiStrip({
|
|
|
1628
1740
|
tone: "tone-rate"
|
|
1629
1741
|
}
|
|
1630
1742
|
];
|
|
1631
|
-
return /* @__PURE__ */ jsx3("div", { class: `board-kpi-strip ${kpis ? "is-ready" : "is-loading"}`, "aria-live": "polite", children: cells.map((c) => /* @__PURE__ */
|
|
1743
|
+
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
1744
|
/* @__PURE__ */ jsx3("div", { class: "board-kpi-value", children: c.value }),
|
|
1633
1745
|
/* @__PURE__ */ jsx3("div", { class: "board-kpi-label", children: c.label })
|
|
1634
1746
|
] }, c.key)) });
|
|
@@ -1680,8 +1792,8 @@ function BoardFilters({
|
|
|
1680
1792
|
}
|
|
1681
1793
|
};
|
|
1682
1794
|
const clear = () => onChange({});
|
|
1683
|
-
return /* @__PURE__ */
|
|
1684
|
-
/* @__PURE__ */
|
|
1795
|
+
return /* @__PURE__ */ jsxs4("div", { class: "board-filters", children: [
|
|
1796
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-scope", role: "group", "aria-label": strings["board.scope.thisPage"], children: [
|
|
1685
1797
|
/* @__PURE__ */ jsx3(
|
|
1686
1798
|
"button",
|
|
1687
1799
|
{
|
|
@@ -1713,7 +1825,7 @@ function BoardFilters({
|
|
|
1713
1825
|
children: SORT_OPTIONS.map((o) => /* @__PURE__ */ jsx3("option", { value: o, children: strings[`board.sort.${o}`] }, o))
|
|
1714
1826
|
}
|
|
1715
1827
|
),
|
|
1716
|
-
/* @__PURE__ */
|
|
1828
|
+
/* @__PURE__ */ jsxs4(
|
|
1717
1829
|
"select",
|
|
1718
1830
|
{
|
|
1719
1831
|
class: "board-filter-select",
|
|
@@ -1726,7 +1838,7 @@ function BoardFilters({
|
|
|
1726
1838
|
]
|
|
1727
1839
|
}
|
|
1728
1840
|
),
|
|
1729
|
-
/* @__PURE__ */
|
|
1841
|
+
/* @__PURE__ */ jsxs4(
|
|
1730
1842
|
"select",
|
|
1731
1843
|
{
|
|
1732
1844
|
class: "board-filter-select",
|
|
@@ -1739,7 +1851,7 @@ function BoardFilters({
|
|
|
1739
1851
|
]
|
|
1740
1852
|
}
|
|
1741
1853
|
),
|
|
1742
|
-
/* @__PURE__ */
|
|
1854
|
+
/* @__PURE__ */ jsxs4(
|
|
1743
1855
|
"select",
|
|
1744
1856
|
{
|
|
1745
1857
|
class: "board-filter-select",
|
|
@@ -1762,7 +1874,7 @@ function BoardFilters({
|
|
|
1762
1874
|
onInput: (e) => setSearch(e.target.value)
|
|
1763
1875
|
}
|
|
1764
1876
|
),
|
|
1765
|
-
/* @__PURE__ */
|
|
1877
|
+
/* @__PURE__ */ jsxs4("label", { class: "board-filter-toggle", children: [
|
|
1766
1878
|
/* @__PURE__ */ jsx3(
|
|
1767
1879
|
"input",
|
|
1768
1880
|
{
|
|
@@ -1773,7 +1885,7 @@ function BoardFilters({
|
|
|
1773
1885
|
),
|
|
1774
1886
|
/* @__PURE__ */ jsx3("span", { children: strings["board.filter.mine"] })
|
|
1775
1887
|
] }),
|
|
1776
|
-
activeCount > 0 && /* @__PURE__ */
|
|
1888
|
+
activeCount > 0 && /* @__PURE__ */ jsxs4("button", { type: "button", class: "board-filter-clear", onClick: clear, children: [
|
|
1777
1889
|
/* @__PURE__ */ jsx3(CloseIcon, {}),
|
|
1778
1890
|
strings["board.filter.clear"]
|
|
1779
1891
|
] })
|
|
@@ -1787,7 +1899,7 @@ function BoardList({
|
|
|
1787
1899
|
total,
|
|
1788
1900
|
thisPage
|
|
1789
1901
|
}) {
|
|
1790
|
-
return /* @__PURE__ */
|
|
1902
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1791
1903
|
/* @__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
1904
|
/* @__PURE__ */ jsx3("ul", { class: "board-list", role: "list", children: rows.map((row) => /* @__PURE__ */ jsx3(
|
|
1793
1905
|
BoardRowCard,
|
|
@@ -1808,7 +1920,7 @@ function BoardRowCard({
|
|
|
1808
1920
|
strings
|
|
1809
1921
|
}) {
|
|
1810
1922
|
const author = row.is_mine ? strings["board.list.you"] : row.author_label || "\u2014";
|
|
1811
|
-
return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */
|
|
1923
|
+
return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs4(
|
|
1812
1924
|
"button",
|
|
1813
1925
|
{
|
|
1814
1926
|
type: "button",
|
|
@@ -1816,14 +1928,14 @@ function BoardRowCard({
|
|
|
1816
1928
|
onClick: () => onPick(row),
|
|
1817
1929
|
"aria-pressed": selected,
|
|
1818
1930
|
children: [
|
|
1819
|
-
/* @__PURE__ */
|
|
1931
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-row-badges", children: [
|
|
1820
1932
|
/* @__PURE__ */ jsx3("span", { class: `badge status-${row.status}`, children: strings[`status.${row.status}`] ?? row.status }),
|
|
1821
1933
|
/* @__PURE__ */ jsx3("span", { class: `badge severity-${row.severity}`, children: strings[`severity.${row.severity}`] ?? row.severity })
|
|
1822
1934
|
] }),
|
|
1823
1935
|
/* @__PURE__ */ jsx3("div", { class: "board-row-description", children: row.description }),
|
|
1824
|
-
/* @__PURE__ */
|
|
1936
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-row-meta", children: [
|
|
1825
1937
|
/* @__PURE__ */ jsx3("span", { class: "board-row-author", children: author }),
|
|
1826
|
-
row.comment_count > 0 && /* @__PURE__ */
|
|
1938
|
+
row.comment_count > 0 && /* @__PURE__ */ jsxs4("span", { class: "board-row-comments", children: [
|
|
1827
1939
|
/* @__PURE__ */ jsx3(CommentIcon, {}),
|
|
1828
1940
|
" ",
|
|
1829
1941
|
row.comment_count
|
|
@@ -1839,7 +1951,7 @@ function BoardEmpty({
|
|
|
1839
1951
|
thisPage,
|
|
1840
1952
|
onAllPages
|
|
1841
1953
|
}) {
|
|
1842
|
-
return /* @__PURE__ */
|
|
1954
|
+
return /* @__PURE__ */ jsxs4("div", { class: "board-empty", children: [
|
|
1843
1955
|
/* @__PURE__ */ jsx3(EmptyIllustration, {}),
|
|
1844
1956
|
/* @__PURE__ */ jsx3("h3", { children: strings[thisPage ? "board.list.empty.page.title" : "board.list.empty.title"] }),
|
|
1845
1957
|
/* @__PURE__ */ jsx3("p", { children: strings[thisPage ? "board.list.empty.page.description" : "board.list.empty.description"] }),
|
|
@@ -1847,7 +1959,7 @@ function BoardEmpty({
|
|
|
1847
1959
|
] });
|
|
1848
1960
|
}
|
|
1849
1961
|
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__ */
|
|
1962
|
+
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
1963
|
/* @__PURE__ */ jsx3("div", { class: "skeleton-line skeleton-badges" }),
|
|
1852
1964
|
/* @__PURE__ */ jsx3("div", { class: "skeleton-line skeleton-text" }),
|
|
1853
1965
|
/* @__PURE__ */ jsx3("div", { class: "skeleton-line skeleton-text short" })
|
|
@@ -1857,19 +1969,19 @@ function CommentIcon() {
|
|
|
1857
1969
|
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
1970
|
}
|
|
1859
1971
|
function CloseIcon() {
|
|
1860
|
-
return /* @__PURE__ */
|
|
1972
|
+
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
1973
|
/* @__PURE__ */ jsx3("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1862
1974
|
/* @__PURE__ */ jsx3("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1863
1975
|
] });
|
|
1864
1976
|
}
|
|
1865
1977
|
function EmptyIllustration() {
|
|
1866
|
-
return /* @__PURE__ */
|
|
1978
|
+
return /* @__PURE__ */ jsxs4("svg", { width: "64", height: "64", viewBox: "0 0 64 64", fill: "none", "aria-hidden": "true", children: [
|
|
1867
1979
|
/* @__PURE__ */ jsx3("circle", { cx: "32", cy: "32", r: "28", stroke: "currentColor", "stroke-opacity": "0.18", "stroke-width": "2" }),
|
|
1868
1980
|
/* @__PURE__ */ jsx3("path", { d: "M22 32h20M32 22v20", stroke: "currentColor", "stroke-opacity": "0.35", "stroke-width": "2", "stroke-linecap": "round" })
|
|
1869
1981
|
] });
|
|
1870
1982
|
}
|
|
1871
1983
|
function DetailEmptyIllustration() {
|
|
1872
|
-
return /* @__PURE__ */
|
|
1984
|
+
return /* @__PURE__ */ jsxs4("svg", { width: "80", height: "80", viewBox: "0 0 64 64", fill: "none", "aria-hidden": "true", children: [
|
|
1873
1985
|
/* @__PURE__ */ jsx3("rect", { x: "10", y: "12", width: "44", height: "36", rx: "4", stroke: "currentColor", "stroke-opacity": "0.22", "stroke-width": "2" }),
|
|
1874
1986
|
/* @__PURE__ */ jsx3("line", { x1: "18", y1: "22", x2: "46", y2: "22", stroke: "currentColor", "stroke-opacity": "0.22", "stroke-width": "2", "stroke-linecap": "round" }),
|
|
1875
1987
|
/* @__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 +2017,7 @@ function formatRelative(iso) {
|
|
|
1905
2017
|
import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "preact/hooks";
|
|
1906
2018
|
|
|
1907
2019
|
// src/widget/ReportRow.tsx
|
|
1908
|
-
import { jsx as jsx4, jsxs as
|
|
2020
|
+
import { jsx as jsx4, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
1909
2021
|
function statusClassName(status) {
|
|
1910
2022
|
return `pill pill-status pill-status--${status}`;
|
|
1911
2023
|
}
|
|
@@ -1933,16 +2045,16 @@ function repliesLabel(count, strings) {
|
|
|
1933
2045
|
}
|
|
1934
2046
|
function ReportRow({ row, strings, onClick }) {
|
|
1935
2047
|
const preview = row.description.length > 120 ? row.description.slice(0, 117) + "\u2026" : row.description;
|
|
1936
|
-
return /* @__PURE__ */
|
|
1937
|
-
/* @__PURE__ */
|
|
2048
|
+
return /* @__PURE__ */ jsxs5("button", { type: "button", class: "mine-row", onClick, children: [
|
|
2049
|
+
/* @__PURE__ */ jsxs5("div", { class: "mine-row-pills", children: [
|
|
1938
2050
|
/* @__PURE__ */ jsx4("span", { class: statusClassName(row.status), children: strings[`status.${row.status}`] ?? row.status }),
|
|
1939
2051
|
/* @__PURE__ */ jsx4("span", { class: typeClassName(), children: strings[`type.${row.feedback_type}`] }),
|
|
1940
2052
|
/* @__PURE__ */ jsx4("span", { class: severityClassName(row.severity), children: strings[`severity.${row.severity}`] })
|
|
1941
2053
|
] }),
|
|
1942
2054
|
/* @__PURE__ */ jsx4("div", { class: "mine-row-preview", children: preview }),
|
|
1943
|
-
/* @__PURE__ */
|
|
2055
|
+
/* @__PURE__ */ jsxs5("div", { class: "mine-row-meta", children: [
|
|
1944
2056
|
/* @__PURE__ */ jsx4("span", { children: formatRelative2(row.updated_at || row.created_at) }),
|
|
1945
|
-
row.comment_count > 0 && /* @__PURE__ */
|
|
2057
|
+
row.comment_count > 0 && /* @__PURE__ */ jsxs5("span", { children: [
|
|
1946
2058
|
"\xB7 ",
|
|
1947
2059
|
repliesLabel(row.comment_count, strings)
|
|
1948
2060
|
] })
|
|
@@ -1951,7 +2063,7 @@ function ReportRow({ row, strings, onClick }) {
|
|
|
1951
2063
|
}
|
|
1952
2064
|
|
|
1953
2065
|
// src/widget/ChangelogList.tsx
|
|
1954
|
-
import { jsx as jsx5, jsxs as
|
|
2066
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
1955
2067
|
var POLL_MS3 = 3e4;
|
|
1956
2068
|
function isoWeekKey(iso) {
|
|
1957
2069
|
const d = new Date(iso);
|
|
@@ -2020,8 +2132,8 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
|
|
|
2020
2132
|
const groups = useMemo2(() => rows ? groupRowsByWeek(rows) : [], [rows]);
|
|
2021
2133
|
const isLoading = rows === null && !error;
|
|
2022
2134
|
const isEmpty = rows !== null && rows.length === 0;
|
|
2023
|
-
return /* @__PURE__ */
|
|
2024
|
-
/* @__PURE__ */
|
|
2135
|
+
return /* @__PURE__ */ jsxs6("div", { class: "mine-list", children: [
|
|
2136
|
+
/* @__PURE__ */ jsxs6("div", { class: "mine-list-header", children: [
|
|
2025
2137
|
/* @__PURE__ */ jsx5("h2", { children: strings["tab.changelog"] }),
|
|
2026
2138
|
/* @__PURE__ */ jsx5(
|
|
2027
2139
|
"button",
|
|
@@ -2038,12 +2150,12 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
|
|
|
2038
2150
|
] }),
|
|
2039
2151
|
isLoading && /* @__PURE__ */ jsx5("div", { class: "mine-loading", children: strings["mine.loading"] }),
|
|
2040
2152
|
error && /* @__PURE__ */ jsx5("div", { class: "error", children: error }),
|
|
2041
|
-
isEmpty && /* @__PURE__ */
|
|
2153
|
+
isEmpty && /* @__PURE__ */ jsxs6("div", { class: "mine-empty", children: [
|
|
2042
2154
|
/* @__PURE__ */ jsx5("strong", { children: strings["changelog.empty.title"] }),
|
|
2043
2155
|
/* @__PURE__ */ jsx5("p", { children: strings["changelog.empty.body"] })
|
|
2044
2156
|
] }),
|
|
2045
|
-
groups.length > 0 && /* @__PURE__ */ jsx5("div", { class: "changelog-groups", children: groups.map((g) => /* @__PURE__ */
|
|
2046
|
-
/* @__PURE__ */
|
|
2157
|
+
groups.length > 0 && /* @__PURE__ */ jsx5("div", { class: "changelog-groups", children: groups.map((g) => /* @__PURE__ */ jsxs6("section", { class: "changelog-group", children: [
|
|
2158
|
+
/* @__PURE__ */ jsxs6("header", { class: "changelog-group-header", children: [
|
|
2047
2159
|
/* @__PURE__ */ jsx5("span", { class: "changelog-group-marker", "aria-hidden": "true", children: "\u25CF" }),
|
|
2048
2160
|
/* @__PURE__ */ jsx5("span", { class: "changelog-group-label", children: strings["changelog.week_of"].replace("{date}", g.label) }),
|
|
2049
2161
|
/* @__PURE__ */ jsx5("span", { class: "changelog-group-rule", "aria-hidden": "true" }),
|
|
@@ -2055,9 +2167,9 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
|
|
|
2055
2167
|
}
|
|
2056
2168
|
|
|
2057
2169
|
// src/widget/Fab.tsx
|
|
2058
|
-
import { jsx as jsx6, jsxs as
|
|
2170
|
+
import { jsx as jsx6, jsxs as jsxs7 } from "preact/jsx-runtime";
|
|
2059
2171
|
function BugIcon() {
|
|
2060
|
-
return /* @__PURE__ */
|
|
2172
|
+
return /* @__PURE__ */ jsxs7(
|
|
2061
2173
|
"svg",
|
|
2062
2174
|
{
|
|
2063
2175
|
width: "20",
|
|
@@ -2087,7 +2199,7 @@ function BugIcon() {
|
|
|
2087
2199
|
);
|
|
2088
2200
|
}
|
|
2089
2201
|
function Fab({ label, onClick, count }) {
|
|
2090
|
-
return /* @__PURE__ */
|
|
2202
|
+
return /* @__PURE__ */ jsxs7("button", { type: "button", class: "fab", "aria-label": label, title: label, onClick, children: [
|
|
2091
2203
|
/* @__PURE__ */ jsx6(BugIcon, {}),
|
|
2092
2204
|
count !== void 0 && count > 0 && /* @__PURE__ */ jsx6("span", { class: "fab-badge", "aria-hidden": "true", children: count > 9 ? "9+" : String(count) })
|
|
2093
2205
|
] });
|
|
@@ -2098,7 +2210,7 @@ import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } fro
|
|
|
2098
2210
|
|
|
2099
2211
|
// src/widget/Annotator.tsx
|
|
2100
2212
|
import { useEffect as useEffect4, useLayoutEffect, useRef as useRef3, useState as useState4 } from "preact/hooks";
|
|
2101
|
-
import { jsx as jsx7, jsxs as
|
|
2213
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "preact/jsx-runtime";
|
|
2102
2214
|
var COLORS = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#ffffff"];
|
|
2103
2215
|
var HIGHLIGHT_ALPHA = 0.35;
|
|
2104
2216
|
function drawShape(ctx, shape, sourceImage) {
|
|
@@ -2224,11 +2336,11 @@ var Icon = {
|
|
|
2224
2336
|
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
2337
|
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
2338
|
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__ */
|
|
2339
|
+
highlight: /* @__PURE__ */ jsxs8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
|
|
2228
2340
|
/* @__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
2341
|
/* @__PURE__ */ jsx7("path", { d: "M2 14h12", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" })
|
|
2230
2342
|
] }),
|
|
2231
|
-
blur: /* @__PURE__ */
|
|
2343
|
+
blur: /* @__PURE__ */ jsxs8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
|
|
2232
2344
|
/* @__PURE__ */ jsx7("rect", { x: "2", y: "2", width: "4", height: "4", fill: "currentColor", opacity: "0.85" }),
|
|
2233
2345
|
/* @__PURE__ */ jsx7("rect", { x: "7", y: "2", width: "3", height: "3", fill: "currentColor", opacity: "0.55" }),
|
|
2234
2346
|
/* @__PURE__ */ jsx7("rect", { x: "11", y: "2", width: "3", height: "4", fill: "currentColor", opacity: "0.4" }),
|
|
@@ -2484,8 +2596,8 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2484
2596
|
onClick: (e) => {
|
|
2485
2597
|
if (e.target === e.currentTarget) onCancel();
|
|
2486
2598
|
},
|
|
2487
|
-
children: /* @__PURE__ */
|
|
2488
|
-
/* @__PURE__ */
|
|
2599
|
+
children: /* @__PURE__ */ jsxs8("div", { class: "annotator", role: "dialog", "aria-modal": "true", "aria-label": strings["annotator.title"], children: [
|
|
2600
|
+
/* @__PURE__ */ jsxs8("div", { class: "annotator-header", children: [
|
|
2489
2601
|
/* @__PURE__ */ jsx7("span", { children: strings["annotator.title"] }),
|
|
2490
2602
|
/* @__PURE__ */ jsx7(
|
|
2491
2603
|
"button",
|
|
@@ -2498,7 +2610,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2498
2610
|
}
|
|
2499
2611
|
)
|
|
2500
2612
|
] }),
|
|
2501
|
-
/* @__PURE__ */
|
|
2613
|
+
/* @__PURE__ */ jsxs8("div", { class: "annotator-toolbar", children: [
|
|
2502
2614
|
/* @__PURE__ */ jsx7("div", { class: "annotator-tools", children: tools.map((t) => /* @__PURE__ */ jsx7(
|
|
2503
2615
|
"button",
|
|
2504
2616
|
{
|
|
@@ -2513,7 +2625,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2513
2625
|
t.id
|
|
2514
2626
|
)) }),
|
|
2515
2627
|
/* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
|
|
2516
|
-
/* @__PURE__ */
|
|
2628
|
+
/* @__PURE__ */ jsxs8("div", { class: "annotator-colors", children: [
|
|
2517
2629
|
COLORS.map((c) => /* @__PURE__ */ jsx7(
|
|
2518
2630
|
"button",
|
|
2519
2631
|
{
|
|
@@ -2537,7 +2649,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2537
2649
|
) })
|
|
2538
2650
|
] }),
|
|
2539
2651
|
/* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
|
|
2540
|
-
/* @__PURE__ */
|
|
2652
|
+
/* @__PURE__ */ jsxs8(
|
|
2541
2653
|
"button",
|
|
2542
2654
|
{
|
|
2543
2655
|
type: "button",
|
|
@@ -2551,7 +2663,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2551
2663
|
]
|
|
2552
2664
|
}
|
|
2553
2665
|
),
|
|
2554
|
-
/* @__PURE__ */
|
|
2666
|
+
/* @__PURE__ */ jsxs8(
|
|
2555
2667
|
"button",
|
|
2556
2668
|
{
|
|
2557
2669
|
type: "button",
|
|
@@ -2565,7 +2677,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2565
2677
|
]
|
|
2566
2678
|
}
|
|
2567
2679
|
),
|
|
2568
|
-
/* @__PURE__ */
|
|
2680
|
+
/* @__PURE__ */ jsxs8(
|
|
2569
2681
|
"button",
|
|
2570
2682
|
{
|
|
2571
2683
|
type: "button",
|
|
@@ -2579,7 +2691,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2579
2691
|
}
|
|
2580
2692
|
),
|
|
2581
2693
|
/* @__PURE__ */ jsx7("span", { class: "annotator-spacer" }),
|
|
2582
|
-
/* @__PURE__ */
|
|
2694
|
+
/* @__PURE__ */ jsxs8("span", { class: "annotator-count", children: [
|
|
2583
2695
|
shapes.length,
|
|
2584
2696
|
" ",
|
|
2585
2697
|
strings["annotator.count_suffix"]
|
|
@@ -2596,7 +2708,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2596
2708
|
class: "annotator-canvas"
|
|
2597
2709
|
}
|
|
2598
2710
|
) }),
|
|
2599
|
-
/* @__PURE__ */
|
|
2711
|
+
/* @__PURE__ */ jsxs8("div", { class: "annotator-footer", children: [
|
|
2600
2712
|
/* @__PURE__ */ jsx7("button", { type: "button", class: "btn", onClick: onCancel, children: strings["form.cancel"] }),
|
|
2601
2713
|
/* @__PURE__ */ jsx7(
|
|
2602
2714
|
"button",
|
|
@@ -2615,10 +2727,18 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2615
2727
|
}
|
|
2616
2728
|
|
|
2617
2729
|
// src/widget/Form.tsx
|
|
2618
|
-
import { jsx as jsx8, jsxs as
|
|
2730
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "preact/jsx-runtime";
|
|
2619
2731
|
var TYPES2 = ["bug", "feature", "question", "praise", "typo"];
|
|
2620
2732
|
var SEVERITIES2 = ["blocker", "high", "medium", "low"];
|
|
2621
|
-
function Form({
|
|
2733
|
+
function Form({
|
|
2734
|
+
strings,
|
|
2735
|
+
onSubmit,
|
|
2736
|
+
onCancel,
|
|
2737
|
+
status,
|
|
2738
|
+
errorMessage,
|
|
2739
|
+
submittedSeq,
|
|
2740
|
+
onDirtyChange
|
|
2741
|
+
}) {
|
|
2622
2742
|
const [description, setDescription] = useState5("");
|
|
2623
2743
|
const [feedbackType, setFeedbackType] = useState5("bug");
|
|
2624
2744
|
const [severity, setSeverity] = useState5("medium");
|
|
@@ -2631,6 +2751,10 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2631
2751
|
const submitting = status === "submitting";
|
|
2632
2752
|
const submitLabel = submitting ? strings["form.submitting"] : strings["form.submit"];
|
|
2633
2753
|
const pageUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
2754
|
+
const isDirty = description.trim() !== "" || shots.length > 0;
|
|
2755
|
+
useEffect5(() => {
|
|
2756
|
+
onDirtyChange?.(isDirty);
|
|
2757
|
+
}, [isDirty]);
|
|
2634
2758
|
const shotsRef = useRef4(shots);
|
|
2635
2759
|
shotsRef.current = shots;
|
|
2636
2760
|
useEffect5(() => {
|
|
@@ -2742,9 +2866,9 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2742
2866
|
}
|
|
2743
2867
|
onSubmit(values);
|
|
2744
2868
|
};
|
|
2745
|
-
return /* @__PURE__ */
|
|
2869
|
+
return /* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, children: [
|
|
2746
2870
|
/* @__PURE__ */ jsx8("h2", { children: strings["form.title"] }),
|
|
2747
|
-
/* @__PURE__ */
|
|
2871
|
+
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
2748
2872
|
/* @__PURE__ */ jsx8("label", { for: "mfb-desc", children: strings["form.description.label"] }),
|
|
2749
2873
|
/* @__PURE__ */ jsx8(
|
|
2750
2874
|
"textarea",
|
|
@@ -2756,8 +2880,8 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2756
2880
|
}
|
|
2757
2881
|
)
|
|
2758
2882
|
] }),
|
|
2759
|
-
/* @__PURE__ */
|
|
2760
|
-
/* @__PURE__ */
|
|
2883
|
+
/* @__PURE__ */ jsxs9("div", { class: "row", children: [
|
|
2884
|
+
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
2761
2885
|
/* @__PURE__ */ jsx8("label", { for: "mfb-type", children: strings["form.type.label"] }),
|
|
2762
2886
|
/* @__PURE__ */ jsx8(
|
|
2763
2887
|
"select",
|
|
@@ -2769,7 +2893,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2769
2893
|
}
|
|
2770
2894
|
)
|
|
2771
2895
|
] }),
|
|
2772
|
-
/* @__PURE__ */
|
|
2896
|
+
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
2773
2897
|
/* @__PURE__ */ jsx8("label", { for: "mfb-sev", children: strings["form.severity.label"] }),
|
|
2774
2898
|
/* @__PURE__ */ jsx8(
|
|
2775
2899
|
"select",
|
|
@@ -2782,7 +2906,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2782
2906
|
)
|
|
2783
2907
|
] })
|
|
2784
2908
|
] }),
|
|
2785
|
-
/* @__PURE__ */
|
|
2909
|
+
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
2786
2910
|
/* @__PURE__ */ jsx8("label", { children: strings["form.screenshot.label"] }),
|
|
2787
2911
|
/* @__PURE__ */ jsx8(
|
|
2788
2912
|
"input",
|
|
@@ -2797,17 +2921,17 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2797
2921
|
tabIndex: -1
|
|
2798
2922
|
}
|
|
2799
2923
|
),
|
|
2800
|
-
shots.length > 0 && /* @__PURE__ */ jsx8("div", { class: "screenshot-strip", children: shots.map((shot, index) => /* @__PURE__ */
|
|
2924
|
+
shots.length > 0 && /* @__PURE__ */ jsx8("div", { class: "screenshot-strip", children: shots.map((shot, index) => /* @__PURE__ */ jsxs9("div", { class: "screenshot-preview", children: [
|
|
2801
2925
|
/* @__PURE__ */ jsx8("img", { src: shot.preview, alt: "" }),
|
|
2802
|
-
/* @__PURE__ */
|
|
2803
|
-
/* @__PURE__ */
|
|
2926
|
+
/* @__PURE__ */ jsxs9("div", { class: "screenshot-preview-actions", children: [
|
|
2927
|
+
/* @__PURE__ */ jsxs9(
|
|
2804
2928
|
"button",
|
|
2805
2929
|
{
|
|
2806
2930
|
type: "button",
|
|
2807
2931
|
class: "btn btn--primary screenshot-annotate",
|
|
2808
2932
|
onClick: () => setAnnotatingIndex(index),
|
|
2809
2933
|
children: [
|
|
2810
|
-
/* @__PURE__ */
|
|
2934
|
+
/* @__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
2935
|
/* @__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
2936
|
/* @__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
2937
|
] }),
|
|
@@ -2815,13 +2939,13 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2815
2939
|
]
|
|
2816
2940
|
}
|
|
2817
2941
|
),
|
|
2818
|
-
/* @__PURE__ */
|
|
2942
|
+
/* @__PURE__ */ jsxs9("button", { type: "button", class: "btn", onClick: () => removeShot(index), children: [
|
|
2819
2943
|
/* @__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
2944
|
strings["form.screenshot.remove"]
|
|
2821
2945
|
] })
|
|
2822
2946
|
] })
|
|
2823
2947
|
] }, shot.preview)) }),
|
|
2824
|
-
shots.length < MAX_SCREENSHOTS && /* @__PURE__ */
|
|
2948
|
+
shots.length < MAX_SCREENSHOTS && /* @__PURE__ */ jsxs9(
|
|
2825
2949
|
"div",
|
|
2826
2950
|
{
|
|
2827
2951
|
ref: dropZoneRef,
|
|
@@ -2840,12 +2964,12 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2840
2964
|
onDragLeave: handleDragLeave,
|
|
2841
2965
|
onDrop: handleDrop,
|
|
2842
2966
|
children: [
|
|
2843
|
-
/* @__PURE__ */
|
|
2967
|
+
/* @__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
2968
|
/* @__PURE__ */ jsx8("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
2845
2969
|
/* @__PURE__ */ jsx8("polyline", { points: "17 8 12 3 7 8" }),
|
|
2846
2970
|
/* @__PURE__ */ jsx8("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
2847
2971
|
] }),
|
|
2848
|
-
/* @__PURE__ */
|
|
2972
|
+
/* @__PURE__ */ jsxs9("div", { class: "screenshot-cta", children: [
|
|
2849
2973
|
/* @__PURE__ */ jsx8("strong", { children: strings["form.screenshot.cta_click"] }),
|
|
2850
2974
|
", ",
|
|
2851
2975
|
strings["form.screenshot.cta_rest"]
|
|
@@ -2855,14 +2979,15 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2855
2979
|
}
|
|
2856
2980
|
)
|
|
2857
2981
|
] }),
|
|
2858
|
-
pageUrl && /* @__PURE__ */
|
|
2982
|
+
pageUrl && /* @__PURE__ */ jsxs9("div", { class: "page-context", title: pageUrl, children: [
|
|
2859
2983
|
/* @__PURE__ */ jsx8("span", { class: "page-context-label", children: strings["form.context.label"] }),
|
|
2860
2984
|
/* @__PURE__ */ jsx8("span", { class: "page-context-url", children: truncateUrl(pageUrl, 90) })
|
|
2861
2985
|
] }),
|
|
2986
|
+
/* @__PURE__ */ jsx8("div", { class: "capture-notice", children: strings["form.capture.notice"] }),
|
|
2862
2987
|
localError && /* @__PURE__ */ jsx8("div", { class: "error", children: localError }),
|
|
2863
2988
|
status === "error" && errorMessage && /* @__PURE__ */ jsx8("div", { class: "error", children: errorMessage }),
|
|
2864
|
-
status === "success" && /* @__PURE__ */ jsx8("div", { class: "success", children: strings["form.success"] }),
|
|
2865
|
-
/* @__PURE__ */
|
|
2989
|
+
status === "success" && /* @__PURE__ */ jsx8("div", { class: "success", children: submittedSeq !== void 0 ? strings["form.success.seq"].replace("{seq}", String(submittedSeq)) : strings["form.success"] }),
|
|
2990
|
+
/* @__PURE__ */ jsxs9("div", { class: "actions", children: [
|
|
2866
2991
|
/* @__PURE__ */ jsx8("button", { type: "button", class: "btn", onClick: onCancel, disabled: submitting, children: strings["form.cancel"] }),
|
|
2867
2992
|
/* @__PURE__ */ jsx8("button", { type: "submit", class: "btn btn--primary", disabled: submitting, children: submitLabel })
|
|
2868
2993
|
] }),
|
|
@@ -2882,7 +3007,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2882
3007
|
import { useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "preact/hooks";
|
|
2883
3008
|
|
|
2884
3009
|
// src/widget/KpiStrip.tsx
|
|
2885
|
-
import { jsx as jsx9, jsxs as
|
|
3010
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "preact/jsx-runtime";
|
|
2886
3011
|
function computeKpiCounts(rows) {
|
|
2887
3012
|
const counts = {
|
|
2888
3013
|
new: 0,
|
|
@@ -2952,7 +3077,7 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
|
|
|
2952
3077
|
return /* @__PURE__ */ jsx9("div", { class: "kpi-strip", role: "toolbar", children: cells.map((c) => {
|
|
2953
3078
|
const active = filter === c.key;
|
|
2954
3079
|
const toggleTo = active ? "all" : c.key;
|
|
2955
|
-
return /* @__PURE__ */
|
|
3080
|
+
return /* @__PURE__ */ jsxs10(
|
|
2956
3081
|
"button",
|
|
2957
3082
|
{
|
|
2958
3083
|
type: "button",
|
|
@@ -2969,7 +3094,7 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
|
|
|
2969
3094
|
}
|
|
2970
3095
|
|
|
2971
3096
|
// src/widget/MineList.tsx
|
|
2972
|
-
import { jsx as jsx10, jsxs as
|
|
3097
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "preact/jsx-runtime";
|
|
2973
3098
|
var POLL_MS4 = 3e4;
|
|
2974
3099
|
function MineList({ api, externalId, strings, onSelect }) {
|
|
2975
3100
|
const [rows, setRows] = useState6(null);
|
|
@@ -2988,7 +3113,7 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
2988
3113
|
} catch (err) {
|
|
2989
3114
|
if (typeof console !== "undefined") console.warn("[mhosaic] listMine:", err);
|
|
2990
3115
|
if (!mountedRef.current) return false;
|
|
2991
|
-
setError(strings["mine.error"]);
|
|
3116
|
+
setError(rateLimitMessage(err, strings) ?? strings["mine.error"]);
|
|
2992
3117
|
return false;
|
|
2993
3118
|
} finally {
|
|
2994
3119
|
if (mountedRef.current) setRefreshing(false);
|
|
@@ -3006,8 +3131,8 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
3006
3131
|
const isLoading = rows === null && !error;
|
|
3007
3132
|
const visibleRows = rows ? rowsMatchingFilter(rows, filter) : null;
|
|
3008
3133
|
const visibleEmpty = !!rows && rows.length > 0 && (visibleRows?.length ?? 0) === 0;
|
|
3009
|
-
return /* @__PURE__ */
|
|
3010
|
-
/* @__PURE__ */
|
|
3134
|
+
return /* @__PURE__ */ jsxs11("div", { class: "mine-list", children: [
|
|
3135
|
+
/* @__PURE__ */ jsxs11("div", { class: "mine-list-header", children: [
|
|
3011
3136
|
/* @__PURE__ */ jsx10("h2", { children: strings["tab.mine"] }),
|
|
3012
3137
|
/* @__PURE__ */ jsx10(
|
|
3013
3138
|
"button",
|
|
@@ -3025,7 +3150,7 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
3025
3150
|
rows && rows.length > 0 && /* @__PURE__ */ jsx10(KpiStrip, { rows, filter, onFilter: setFilter, strings }),
|
|
3026
3151
|
isLoading && /* @__PURE__ */ jsx10("div", { class: "mine-loading", children: strings["mine.loading"] }),
|
|
3027
3152
|
error && /* @__PURE__ */ jsx10("div", { class: "error", children: error }),
|
|
3028
|
-
isEmpty && /* @__PURE__ */
|
|
3153
|
+
isEmpty && /* @__PURE__ */ jsxs11("div", { class: "mine-empty", children: [
|
|
3029
3154
|
/* @__PURE__ */ jsx10("strong", { children: strings["mine.empty.title"] }),
|
|
3030
3155
|
/* @__PURE__ */ jsx10("p", { children: strings["mine.empty.body"] })
|
|
3031
3156
|
] }),
|
|
@@ -3036,7 +3161,7 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
3036
3161
|
|
|
3037
3162
|
// src/widget/Modal.tsx
|
|
3038
3163
|
import { useEffect as useEffect7, useRef as useRef6 } from "preact/hooks";
|
|
3039
|
-
import { jsx as jsx11, jsxs as
|
|
3164
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "preact/jsx-runtime";
|
|
3040
3165
|
function Modal({ onDismiss, children, closeLabel = "Close", expanded = false }) {
|
|
3041
3166
|
const modalRef = useRef6(null);
|
|
3042
3167
|
const previouslyFocused = useRef6(null);
|
|
@@ -3049,7 +3174,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
|
|
|
3049
3174
|
return;
|
|
3050
3175
|
}
|
|
3051
3176
|
e.stopPropagation();
|
|
3052
|
-
onDismiss();
|
|
3177
|
+
onDismiss("escape");
|
|
3053
3178
|
};
|
|
3054
3179
|
window.addEventListener("keydown", onKey);
|
|
3055
3180
|
const first = modalRef.current?.querySelector(
|
|
@@ -3068,9 +3193,9 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
|
|
|
3068
3193
|
class: `backdrop ${expanded ? "is-expanded" : ""}`,
|
|
3069
3194
|
role: "presentation",
|
|
3070
3195
|
onClick: (e) => {
|
|
3071
|
-
if (e.target === e.currentTarget) onDismiss();
|
|
3196
|
+
if (e.target === e.currentTarget) onDismiss("backdrop");
|
|
3072
3197
|
},
|
|
3073
|
-
children: /* @__PURE__ */
|
|
3198
|
+
children: /* @__PURE__ */ jsxs12(
|
|
3074
3199
|
"div",
|
|
3075
3200
|
{
|
|
3076
3201
|
ref: modalRef,
|
|
@@ -3084,7 +3209,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
|
|
|
3084
3209
|
type: "button",
|
|
3085
3210
|
class: "modal-close",
|
|
3086
3211
|
"aria-label": closeLabel,
|
|
3087
|
-
onClick: onDismiss,
|
|
3212
|
+
onClick: () => onDismiss("button"),
|
|
3088
3213
|
children: "\xD7"
|
|
3089
3214
|
}
|
|
3090
3215
|
),
|
|
@@ -3097,7 +3222,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
|
|
|
3097
3222
|
}
|
|
3098
3223
|
|
|
3099
3224
|
// src/widget/PageActivityStrip.tsx
|
|
3100
|
-
import { jsx as jsx12, jsxs as
|
|
3225
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "preact/jsx-runtime";
|
|
3101
3226
|
function PageActivityStrip({
|
|
3102
3227
|
open,
|
|
3103
3228
|
strings,
|
|
@@ -3105,7 +3230,7 @@ function PageActivityStrip({
|
|
|
3105
3230
|
}) {
|
|
3106
3231
|
if (open === 0) return null;
|
|
3107
3232
|
const text = open === 1 ? strings["page.strip.text.one"] : strings["page.strip.text"].replace("{n}", String(open));
|
|
3108
|
-
return /* @__PURE__ */
|
|
3233
|
+
return /* @__PURE__ */ jsxs13("div", { class: "page-strip", children: [
|
|
3109
3234
|
/* @__PURE__ */ jsx12("span", { children: text }),
|
|
3110
3235
|
/* @__PURE__ */ jsx12("button", { type: "button", class: "page-strip-view", onClick: onView, children: strings["page.strip.view"] })
|
|
3111
3236
|
] });
|
|
@@ -3113,7 +3238,7 @@ function PageActivityStrip({
|
|
|
3113
3238
|
|
|
3114
3239
|
// src/widget/PagePeekPanel.tsx
|
|
3115
3240
|
import { useEffect as useEffect8, useState as useState7 } from "preact/hooks";
|
|
3116
|
-
import { jsx as jsx13, jsxs as
|
|
3241
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "preact/jsx-runtime";
|
|
3117
3242
|
function PagePeekPanel({
|
|
3118
3243
|
api,
|
|
3119
3244
|
externalId,
|
|
@@ -3141,14 +3266,14 @@ function PagePeekPanel({
|
|
|
3141
3266
|
cancelled = true;
|
|
3142
3267
|
};
|
|
3143
3268
|
}, [api, externalId]);
|
|
3144
|
-
return /* @__PURE__ */
|
|
3269
|
+
return /* @__PURE__ */ jsxs14("div", { class: "page-peek", role: "region", "aria-label": strings["page.peek.title"], children: [
|
|
3145
3270
|
/* @__PURE__ */ jsx13("div", { class: "page-peek-title", children: strings["page.peek.title"] }),
|
|
3146
|
-
rows === null && !failed && /* @__PURE__ */
|
|
3271
|
+
rows === null && !failed && /* @__PURE__ */ jsxs14("div", { class: "page-peek-skeleton", "aria-hidden": "true", children: [
|
|
3147
3272
|
/* @__PURE__ */ jsx13("div", {}),
|
|
3148
3273
|
" ",
|
|
3149
3274
|
/* @__PURE__ */ jsx13("div", {})
|
|
3150
3275
|
] }),
|
|
3151
|
-
rows !== null && rows.map((row) => /* @__PURE__ */
|
|
3276
|
+
rows !== null && rows.map((row) => /* @__PURE__ */ jsxs14(
|
|
3152
3277
|
"button",
|
|
3153
3278
|
{
|
|
3154
3279
|
type: "button",
|
|
@@ -3161,7 +3286,7 @@ function PagePeekPanel({
|
|
|
3161
3286
|
},
|
|
3162
3287
|
row.id
|
|
3163
3288
|
)),
|
|
3164
|
-
/* @__PURE__ */
|
|
3289
|
+
/* @__PURE__ */ jsxs14("div", { class: "page-peek-footer", children: [
|
|
3165
3290
|
/* @__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
3291
|
/* @__PURE__ */ jsx13("button", { type: "button", class: "page-peek-send", onClick: onSend, children: strings["fab.label"] })
|
|
3167
3292
|
] })
|
|
@@ -3621,6 +3746,46 @@ var WIDGET_STYLES = `
|
|
|
3621
3746
|
.error { color: #dc2626; font-size: 13px; }
|
|
3622
3747
|
.success { color: #059669; font-size: 13px; }
|
|
3623
3748
|
|
|
3749
|
+
/* REQ-A7: quiet transparency line under the form. */
|
|
3750
|
+
.capture-notice { color: var(--mfb-muted, #6b7280); font-size: 11px; line-height: 1.4; margin-top: 6px; }
|
|
3751
|
+
|
|
3752
|
+
/* #85: inline "#NN" report reference rendered as a link (a button for a11y). */
|
|
3753
|
+
.seq-link {
|
|
3754
|
+
display: inline;
|
|
3755
|
+
padding: 0;
|
|
3756
|
+
border: none;
|
|
3757
|
+
background: none;
|
|
3758
|
+
color: var(--mfb-accent);
|
|
3759
|
+
font: inherit;
|
|
3760
|
+
cursor: pointer;
|
|
3761
|
+
}
|
|
3762
|
+
.seq-link:hover { text-decoration: underline; }
|
|
3763
|
+
|
|
3764
|
+
/* ---- #89: discard-unsaved-changes confirmation over the form -------- */
|
|
3765
|
+
.discard-confirm {
|
|
3766
|
+
position: absolute;
|
|
3767
|
+
inset: 0;
|
|
3768
|
+
z-index: 5;
|
|
3769
|
+
display: flex;
|
|
3770
|
+
align-items: center;
|
|
3771
|
+
justify-content: center;
|
|
3772
|
+
padding: 16px;
|
|
3773
|
+
background: rgba(15, 23, 42, 0.45);
|
|
3774
|
+
border-radius: inherit;
|
|
3775
|
+
}
|
|
3776
|
+
.discard-confirm-card {
|
|
3777
|
+
max-width: 260px;
|
|
3778
|
+
text-align: center;
|
|
3779
|
+
background: var(--mfb-bg);
|
|
3780
|
+
color: var(--mfb-text);
|
|
3781
|
+
border-radius: var(--mfb-radius-lg);
|
|
3782
|
+
padding: 18px 16px;
|
|
3783
|
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.18);
|
|
3784
|
+
}
|
|
3785
|
+
.discard-confirm-title { font-weight: 600; font-size: 15px; margin: 0 0 4px; }
|
|
3786
|
+
.discard-confirm-body { font-size: 13px; margin: 0 0 14px; opacity: 0.8; }
|
|
3787
|
+
.discard-confirm-actions { display: flex; gap: 8px; justify-content: center; }
|
|
3788
|
+
|
|
3624
3789
|
/* ---- v0.6.0: manual screenshot upload + annotator -------------------- */
|
|
3625
3790
|
|
|
3626
3791
|
.mfb-sr-only {
|
|
@@ -5104,7 +5269,7 @@ var WIDGET_STYLES = `
|
|
|
5104
5269
|
`;
|
|
5105
5270
|
|
|
5106
5271
|
// src/widget/mount.tsx
|
|
5107
|
-
import { Fragment as Fragment3, jsx as jsx14, jsxs as
|
|
5272
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs15 } from "preact/jsx-runtime";
|
|
5108
5273
|
function computeFabVisible(opts, externalId, visibilityAllowed) {
|
|
5109
5274
|
if (!opts.showFAB) return false;
|
|
5110
5275
|
if (opts.getExternalId !== void 0 && !externalId) return false;
|
|
@@ -5120,6 +5285,7 @@ function mountWidget(options) {
|
|
|
5120
5285
|
shadow.appendChild(mountPoint);
|
|
5121
5286
|
let currentState = { open: false, status: "idle", tab: "send" };
|
|
5122
5287
|
let postSubmitTimer = null;
|
|
5288
|
+
let formDirty = false;
|
|
5123
5289
|
function rerender(state) {
|
|
5124
5290
|
currentState = state;
|
|
5125
5291
|
render(h(Root, { state }), mountPoint);
|
|
@@ -5145,9 +5311,13 @@ function mountWidget(options) {
|
|
|
5145
5311
|
const handleSubmit = useCallback2(async (values) => {
|
|
5146
5312
|
rerender({ ...currentState, status: "submitting" });
|
|
5147
5313
|
try {
|
|
5148
|
-
await options.onSubmit(values);
|
|
5314
|
+
const result = await options.onSubmit(values);
|
|
5149
5315
|
pageSignal.refresh();
|
|
5150
|
-
rerender({
|
|
5316
|
+
rerender({
|
|
5317
|
+
...currentState,
|
|
5318
|
+
status: "success",
|
|
5319
|
+
...result && result.seq !== void 0 && { submittedSeq: result.seq }
|
|
5320
|
+
});
|
|
5151
5321
|
if (postSubmitTimer !== null) clearTimeout(postSubmitTimer);
|
|
5152
5322
|
postSubmitTimer = setTimeout(() => {
|
|
5153
5323
|
postSubmitTimer = null;
|
|
@@ -5214,8 +5384,8 @@ function mountWidget(options) {
|
|
|
5214
5384
|
if (peekTimers.current.open) clearTimeout(peekTimers.current.open);
|
|
5215
5385
|
peekTimers.current.close = setTimeout(() => setPeekOpen(false), 140);
|
|
5216
5386
|
};
|
|
5217
|
-
return /* @__PURE__ */
|
|
5218
|
-
fabVisible && /* @__PURE__ */
|
|
5387
|
+
return /* @__PURE__ */ jsxs15(Fragment3, { children: [
|
|
5388
|
+
fabVisible && /* @__PURE__ */ jsxs15(
|
|
5219
5389
|
"div",
|
|
5220
5390
|
{
|
|
5221
5391
|
class: "fab-area",
|
|
@@ -5263,14 +5433,62 @@ function mountWidget(options) {
|
|
|
5263
5433
|
]
|
|
5264
5434
|
}
|
|
5265
5435
|
),
|
|
5266
|
-
state.open && /* @__PURE__ */
|
|
5436
|
+
state.open && /* @__PURE__ */ jsxs15(
|
|
5267
5437
|
Modal,
|
|
5268
5438
|
{
|
|
5269
|
-
onDismiss: () =>
|
|
5439
|
+
onDismiss: (reason) => {
|
|
5440
|
+
if (reason !== "button" && formDirty && currentState.tab === "send" && currentState.status !== "submitting") {
|
|
5441
|
+
rerender({ ...currentState, confirmingDiscard: true });
|
|
5442
|
+
return;
|
|
5443
|
+
}
|
|
5444
|
+
formDirty = false;
|
|
5445
|
+
rerender(
|
|
5446
|
+
clearSelected({
|
|
5447
|
+
...currentState,
|
|
5448
|
+
open: false,
|
|
5449
|
+
status: "idle",
|
|
5450
|
+
confirmingDiscard: false
|
|
5451
|
+
})
|
|
5452
|
+
);
|
|
5453
|
+
},
|
|
5270
5454
|
closeLabel: options.strings["form.close"],
|
|
5271
5455
|
expanded: state.tab === "board",
|
|
5272
5456
|
children: [
|
|
5273
|
-
|
|
5457
|
+
state.confirmingDiscard && /* @__PURE__ */ jsx14("div", { class: "discard-confirm", role: "alertdialog", "aria-modal": "true", children: /* @__PURE__ */ jsxs15("div", { class: "discard-confirm-card", children: [
|
|
5458
|
+
/* @__PURE__ */ jsx14("p", { class: "discard-confirm-title", children: options.strings["form.discard.title"] }),
|
|
5459
|
+
/* @__PURE__ */ jsx14("p", { class: "discard-confirm-body", children: options.strings["form.discard.body"] }),
|
|
5460
|
+
/* @__PURE__ */ jsxs15("div", { class: "discard-confirm-actions", children: [
|
|
5461
|
+
/* @__PURE__ */ jsx14(
|
|
5462
|
+
"button",
|
|
5463
|
+
{
|
|
5464
|
+
type: "button",
|
|
5465
|
+
class: "btn",
|
|
5466
|
+
onClick: () => rerender({ ...currentState, confirmingDiscard: false }),
|
|
5467
|
+
children: options.strings["form.discard.keep"]
|
|
5468
|
+
}
|
|
5469
|
+
),
|
|
5470
|
+
/* @__PURE__ */ jsx14(
|
|
5471
|
+
"button",
|
|
5472
|
+
{
|
|
5473
|
+
type: "button",
|
|
5474
|
+
class: "btn btn--primary",
|
|
5475
|
+
onClick: () => {
|
|
5476
|
+
formDirty = false;
|
|
5477
|
+
rerender(
|
|
5478
|
+
clearSelected({
|
|
5479
|
+
...currentState,
|
|
5480
|
+
open: false,
|
|
5481
|
+
status: "idle",
|
|
5482
|
+
confirmingDiscard: false
|
|
5483
|
+
})
|
|
5484
|
+
);
|
|
5485
|
+
},
|
|
5486
|
+
children: options.strings["form.discard.confirm"]
|
|
5487
|
+
}
|
|
5488
|
+
)
|
|
5489
|
+
] })
|
|
5490
|
+
] }) }),
|
|
5491
|
+
showMineTab && /* @__PURE__ */ jsxs15("div", { class: "tab-strip", role: "tablist", children: [
|
|
5274
5492
|
/* @__PURE__ */ jsx14(
|
|
5275
5493
|
"button",
|
|
5276
5494
|
{
|
|
@@ -5316,7 +5534,7 @@ function mountWidget(options) {
|
|
|
5316
5534
|
}
|
|
5317
5535
|
)
|
|
5318
5536
|
] }),
|
|
5319
|
-
state.tab === "send" && /* @__PURE__ */
|
|
5537
|
+
state.tab === "send" && /* @__PURE__ */ jsxs15(Fragment3, { children: [
|
|
5320
5538
|
showMineTab && pageActivityEnabled && /* @__PURE__ */ jsx14(
|
|
5321
5539
|
PageActivityStrip,
|
|
5322
5540
|
{
|
|
@@ -5332,7 +5550,13 @@ function mountWidget(options) {
|
|
|
5332
5550
|
onSubmit: handleSubmit,
|
|
5333
5551
|
onCancel: () => rerender({ ...currentState, open: false, status: "idle" }),
|
|
5334
5552
|
status: state.status,
|
|
5335
|
-
...state.error !== void 0 && { errorMessage: state.error }
|
|
5553
|
+
...state.error !== void 0 && { errorMessage: state.error },
|
|
5554
|
+
...state.submittedSeq !== void 0 && {
|
|
5555
|
+
submittedSeq: state.submittedSeq
|
|
5556
|
+
},
|
|
5557
|
+
onDirtyChange: (d) => {
|
|
5558
|
+
formDirty = d;
|
|
5559
|
+
}
|
|
5336
5560
|
}
|
|
5337
5561
|
)
|
|
5338
5562
|
] }),
|
|
@@ -5352,7 +5576,13 @@ function mountWidget(options) {
|
|
|
5352
5576
|
externalId,
|
|
5353
5577
|
reportId: state.selectedReportId,
|
|
5354
5578
|
strings: options.strings,
|
|
5355
|
-
onBack: () => rerender(clearSelected({ ...currentState }))
|
|
5579
|
+
onBack: () => rerender(clearSelected({ ...currentState })),
|
|
5580
|
+
onOpenSeq: (seq) => {
|
|
5581
|
+
void options.api?.getReportBySeq(seq, externalId).then(
|
|
5582
|
+
(r) => rerender({ ...currentState, selectedReportId: r.id })
|
|
5583
|
+
).catch(() => {
|
|
5584
|
+
});
|
|
5585
|
+
}
|
|
5356
5586
|
}
|
|
5357
5587
|
),
|
|
5358
5588
|
state.tab === "changelog" && options.api && externalId && !state.selectedReportId && /* @__PURE__ */ jsx14(
|
|
@@ -5468,8 +5698,8 @@ function createFeedback(config) {
|
|
|
5468
5698
|
capture_method: captureMethod,
|
|
5469
5699
|
technical_context
|
|
5470
5700
|
};
|
|
5471
|
-
if ("0.
|
|
5472
|
-
payload.widget_version = "0.
|
|
5701
|
+
if ("0.28.0") {
|
|
5702
|
+
payload.widget_version = "0.28.0";
|
|
5473
5703
|
}
|
|
5474
5704
|
if (manualScreenshots?.length) {
|
|
5475
5705
|
payload.screenshots = manualScreenshots;
|
|
@@ -5504,9 +5734,8 @@ function createFeedback(config) {
|
|
|
5504
5734
|
host,
|
|
5505
5735
|
strings,
|
|
5506
5736
|
showFAB: config.showFAB ?? true,
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
},
|
|
5737
|
+
// Return the created report so the modal can acknowledge it by #seq (#82).
|
|
5738
|
+
onSubmit: (values) => buildAndSubmit(values),
|
|
5510
5739
|
api,
|
|
5511
5740
|
// Keep this a callback (not a snapshot) so the mount picks up identity
|
|
5512
5741
|
// changes that happen after createFeedback() — `notifyIdentityChanged()`
|
|
@@ -5596,4 +5825,4 @@ function createFeedback(config) {
|
|
|
5596
5825
|
export {
|
|
5597
5826
|
createFeedback
|
|
5598
5827
|
};
|
|
5599
|
-
//# sourceMappingURL=chunk-
|
|
5828
|
+
//# sourceMappingURL=chunk-FD3CJBMR.mjs.map
|