@mhosaic/feedback 0.13.0 → 0.14.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.
@@ -551,10 +551,6 @@ var DEFAULT_STRINGS = {
551
551
  "form.screenshot.annotate": "Annotate",
552
552
  "form.screenshot.error_type": "Unsupported file type. Use PNG, JPEG or WebP.",
553
553
  "form.screenshot.error_size": "File too large (max {max} MB).",
554
- "form.screenshot.or": "or",
555
- "form.screenshot.capture_page": "Capture this page",
556
- "form.screenshot.capture_page_hint": "Pick a tab or window to share",
557
- "form.screenshot.capture_error": "Couldn\u2019t capture the page. Try uploading a screenshot instead.",
558
554
  "form.context.label": "Page",
559
555
  "type.bug": "Bug",
560
556
  "type.feature": "Feature request",
@@ -688,10 +684,6 @@ var FRENCH_STRINGS = {
688
684
  "form.screenshot.annotate": "Annoter",
689
685
  "form.screenshot.error_type": "Format non support\xE9. Utilisez PNG, JPEG ou WebP.",
690
686
  "form.screenshot.error_size": "Fichier trop volumineux (max {max} Mo).",
691
- "form.screenshot.or": "ou",
692
- "form.screenshot.capture_page": "Capturer la page",
693
- "form.screenshot.capture_page_hint": "Choisissez un onglet ou une fen\xEAtre \xE0 partager",
694
- "form.screenshot.capture_error": "Impossible de capturer la page. T\xE9l\xE9versez une capture \xE0 la place.",
695
687
  "form.context.label": "Page",
696
688
  "type.bug": "Bogue",
697
689
  "type.feature": "Suggestion",
@@ -880,65 +872,6 @@ function truncateUrl(url, maxLength = 80) {
880
872
  const keepEnd = maxLength - 1 - keepStart;
881
873
  return `${url.slice(0, keepStart)}\u2026${url.slice(url.length - keepEnd)}`;
882
874
  }
883
- async function captureWithDisplayMedia() {
884
- if (typeof navigator === "undefined" || !navigator.mediaDevices?.getDisplayMedia) {
885
- return null;
886
- }
887
- let stream = null;
888
- try {
889
- stream = await navigator.mediaDevices.getDisplayMedia({
890
- video: { displaySurface: "browser" },
891
- audio: false
892
- // Hint that audio isn't needed; not all browsers honor this but it
893
- // avoids the audio-share toggle being defaulted on in some.
894
- });
895
- } catch {
896
- return null;
897
- }
898
- try {
899
- const track = stream.getVideoTracks()[0];
900
- if (!track) return null;
901
- const ImageCaptureCtor = window.ImageCapture;
902
- let bitmap = null;
903
- if (ImageCaptureCtor) {
904
- try {
905
- const ic = new ImageCaptureCtor(track);
906
- bitmap = await ic.grabFrame();
907
- } catch {
908
- bitmap = null;
909
- }
910
- }
911
- if (!bitmap) {
912
- bitmap = await grabFrameViaVideo(stream);
913
- }
914
- if (!bitmap) return null;
915
- const canvas = document.createElement("canvas");
916
- canvas.width = bitmap.width;
917
- canvas.height = bitmap.height;
918
- const ctx = canvas.getContext("2d");
919
- if (!ctx) return null;
920
- ctx.drawImage(bitmap, 0, 0);
921
- return await new Promise(
922
- (resolve) => canvas.toBlob(resolve, "image/png")
923
- );
924
- } finally {
925
- stream.getTracks().forEach((t) => t.stop());
926
- }
927
- }
928
- async function grabFrameViaVideo(stream) {
929
- const video = document.createElement("video");
930
- video.muted = true;
931
- video.playsInline = true;
932
- video.srcObject = stream;
933
- try {
934
- await video.play();
935
- await new Promise((r) => requestAnimationFrame(() => r()));
936
- if (!video.videoWidth || !video.videoHeight) return null;
937
- return await createImageBitmap(video);
938
- } catch {
939
- return null;
940
- }
941
- }
942
875
 
943
876
  // src/widget/ReportDetailView.tsx
944
877
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
@@ -2347,10 +2280,8 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2347
2280
  const [localError, setLocalError] = useState5("");
2348
2281
  const [screenshotBlob, setScreenshotBlob] = useState5(null);
2349
2282
  const [screenshotPreview, setScreenshotPreview] = useState5(null);
2350
- const [screenshotMethod, setScreenshotMethod] = useState5("manual");
2351
2283
  const [isDragOver, setIsDragOver] = useState5(false);
2352
2284
  const [annotatorOpen, setAnnotatorOpen] = useState5(false);
2353
- const [capturing, setCapturing] = useState5(false);
2354
2285
  const fileInputRef = useRef4(null);
2355
2286
  const dropZoneRef = useRef4(null);
2356
2287
  const submitting = status === "submitting";
@@ -2361,7 +2292,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2361
2292
  if (screenshotPreview) URL.revokeObjectURL(screenshotPreview);
2362
2293
  };
2363
2294
  }, [screenshotPreview]);
2364
- const acceptFile = (file, method = "manual") => {
2295
+ const acceptFile = (file) => {
2365
2296
  setLocalError("");
2366
2297
  if (file instanceof File) {
2367
2298
  const err = validateScreenshotFile(file);
@@ -2375,41 +2306,14 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2375
2306
  if (screenshotPreview) URL.revokeObjectURL(screenshotPreview);
2376
2307
  setScreenshotBlob(file);
2377
2308
  setScreenshotPreview(URL.createObjectURL(file));
2378
- setScreenshotMethod(method);
2379
2309
  };
2380
2310
  const clearScreenshot = () => {
2381
2311
  if (screenshotPreview) URL.revokeObjectURL(screenshotPreview);
2382
2312
  setScreenshotPreview(null);
2383
2313
  setScreenshotBlob(null);
2384
- setScreenshotMethod("manual");
2385
2314
  setLocalError("");
2386
2315
  if (fileInputRef.current) fileInputRef.current.value = "";
2387
2316
  };
2388
- const handleCapturePage = async () => {
2389
- if (capturing) return;
2390
- setLocalError("");
2391
- setCapturing(true);
2392
- try {
2393
- const blob = await captureWithDisplayMedia();
2394
- if (!blob) {
2395
- if (typeof navigator === "undefined" || !navigator.mediaDevices?.getDisplayMedia) {
2396
- setLocalError(strings["form.screenshot.capture_error"]);
2397
- }
2398
- return;
2399
- }
2400
- if (blob.size > 10 * 1024 * 1024) {
2401
- setLocalError(
2402
- strings["form.screenshot.error_size"].replace("{max}", "10")
2403
- );
2404
- return;
2405
- }
2406
- acceptFile(blob, "display_media");
2407
- } catch {
2408
- setLocalError(strings["form.screenshot.capture_error"]);
2409
- } finally {
2410
- setCapturing(false);
2411
- }
2412
- };
2413
2317
  const handleFileInputChange = (e) => {
2414
2318
  const file = e.target.files?.[0];
2415
2319
  if (file) acceptFile(file);
@@ -2471,7 +2375,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2471
2375
  };
2472
2376
  if (screenshotBlob) {
2473
2377
  values.screenshot = screenshotBlob;
2474
- values.capture_method = screenshotMethod;
2378
+ values.capture_method = "manual";
2475
2379
  }
2476
2380
  onSubmit(values);
2477
2381
  };
@@ -2584,43 +2488,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2584
2488
  /* @__PURE__ */ jsx8("div", { class: "screenshot-formats", children: strings["form.screenshot.formats"] })
2585
2489
  ]
2586
2490
  }
2587
- ),
2588
- !screenshotPreview && /* @__PURE__ */ jsxs7("div", { class: "screenshot-alt", children: [
2589
- /* @__PURE__ */ jsx8("span", { class: "screenshot-or", children: strings["form.screenshot.or"] }),
2590
- /* @__PURE__ */ jsxs7(
2591
- "button",
2592
- {
2593
- type: "button",
2594
- class: "btn btn--ghost screenshot-capture-page",
2595
- onClick: handleCapturePage,
2596
- disabled: capturing || submitting,
2597
- "aria-label": strings["form.screenshot.capture_page"],
2598
- children: [
2599
- /* @__PURE__ */ jsxs7(
2600
- "svg",
2601
- {
2602
- width: "14",
2603
- height: "14",
2604
- viewBox: "0 0 24 24",
2605
- fill: "none",
2606
- stroke: "currentColor",
2607
- "stroke-width": "2",
2608
- "stroke-linecap": "round",
2609
- "stroke-linejoin": "round",
2610
- "aria-hidden": "true",
2611
- children: [
2612
- /* @__PURE__ */ jsx8("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2", ry: "2" }),
2613
- /* @__PURE__ */ jsx8("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
2614
- /* @__PURE__ */ jsx8("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
2615
- ]
2616
- }
2617
- ),
2618
- capturing ? strings["form.submitting"] : strings["form.screenshot.capture_page"]
2619
- ]
2620
- }
2621
- ),
2622
- /* @__PURE__ */ jsx8("span", { class: "screenshot-capture-hint", children: strings["form.screenshot.capture_page_hint"] })
2623
- ] })
2491
+ )
2624
2492
  ] }),
2625
2493
  pageUrl && /* @__PURE__ */ jsxs7("div", { class: "page-context", title: pageUrl, children: [
2626
2494
  /* @__PURE__ */ jsx8("span", { class: "page-context-label", children: strings["form.context.label"] }),
@@ -3261,40 +3129,6 @@ var WIDGET_STYLES = `
3261
3129
  .screenshot-cta strong { color: var(--mfb-accent); font-weight: 600; }
3262
3130
  .screenshot-formats { font-size: var(--mfb-text-xs); color: var(--mfb-text-muted); }
3263
3131
 
3264
- /* "or \u2014 Capture this page" row sits below the dropzone. The thin "or"
3265
- * separator borrows the pattern from auth forms so the second option
3266
- * reads as an alternative, not a follow-up step. v0.12. */
3267
- .screenshot-alt {
3268
- display: flex;
3269
- align-items: center;
3270
- gap: 12px;
3271
- margin-top: 8px;
3272
- flex-wrap: wrap;
3273
- }
3274
- .screenshot-or {
3275
- position: relative;
3276
- font-size: var(--mfb-text-xs);
3277
- color: var(--mfb-text-muted);
3278
- text-transform: uppercase;
3279
- letter-spacing: 0.08em;
3280
- padding: 0 4px;
3281
- }
3282
- .screenshot-capture-page {
3283
- display: inline-flex;
3284
- align-items: center;
3285
- gap: 6px;
3286
- }
3287
- .screenshot-capture-page:disabled {
3288
- opacity: 0.55;
3289
- cursor: progress;
3290
- }
3291
- .screenshot-capture-hint {
3292
- font-size: var(--mfb-text-xs);
3293
- color: var(--mfb-text-muted);
3294
- flex: 1 1 100%;
3295
- margin-top: 4px;
3296
- }
3297
-
3298
3132
  .screenshot-preview {
3299
3133
  position: relative;
3300
3134
  border: 1px solid var(--mfb-border);
@@ -4530,6 +4364,7 @@ function mountWidget(options) {
4530
4364
  const mountPoint = document.createElement("div");
4531
4365
  shadow.appendChild(mountPoint);
4532
4366
  let currentState = { open: false, status: "idle", tab: "send" };
4367
+ let postSubmitTimer = null;
4533
4368
  function rerender(state) {
4534
4369
  currentState = state;
4535
4370
  render(h(Root, { state }), mountPoint);
@@ -4545,8 +4380,10 @@ function mountWidget(options) {
4545
4380
  try {
4546
4381
  await options.onSubmit(values);
4547
4382
  rerender({ ...currentState, status: "success" });
4548
- setTimeout(
4549
- () => rerender({
4383
+ if (postSubmitTimer !== null) clearTimeout(postSubmitTimer);
4384
+ postSubmitTimer = setTimeout(() => {
4385
+ postSubmitTimer = null;
4386
+ rerender({
4550
4387
  ...currentState,
4551
4388
  open: false,
4552
4389
  status: "idle",
@@ -4554,9 +4391,8 @@ function mountWidget(options) {
4554
4391
  // they immediately see their just-filed report in the list
4555
4392
  // (and the conversation that's about to start).
4556
4393
  tab: options.api ? "mine" : "send"
4557
- }),
4558
- 1200
4559
- );
4394
+ });
4395
+ }, 1200);
4560
4396
  } catch (err) {
4561
4397
  rerender({
4562
4398
  ...currentState,
@@ -4691,6 +4527,10 @@ function mountWidget(options) {
4691
4527
  rerender({ ...currentState, open: false, status: "idle" });
4692
4528
  },
4693
4529
  dispose() {
4530
+ if (postSubmitTimer !== null) {
4531
+ clearTimeout(postSubmitTimer);
4532
+ postSubmitTimer = null;
4533
+ }
4694
4534
  render(null, mountPoint);
4695
4535
  options.host.innerHTML = "";
4696
4536
  },
@@ -4758,8 +4598,8 @@ function createFeedback(config) {
4758
4598
  capture_method: captureMethod,
4759
4599
  technical_context
4760
4600
  };
4761
- if ("0.13.0") {
4762
- payload.widget_version = "0.13.0";
4601
+ if ("0.14.0") {
4602
+ payload.widget_version = "0.14.0";
4763
4603
  }
4764
4604
  if (manualScreenshot) payload.screenshot = manualScreenshot;
4765
4605
  if (values.synthetic) payload.synthetic = true;
@@ -4845,4 +4685,4 @@ function createFeedback(config) {
4845
4685
  export {
4846
4686
  createFeedback
4847
4687
  };
4848
- //# sourceMappingURL=chunk-64HXWJCH.mjs.map
4688
+ //# sourceMappingURL=chunk-A4MBOFRR.mjs.map