@mhosaic/feedback 0.22.0 → 0.23.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.
@@ -58,6 +58,9 @@ function createApiClient(options) {
58
58
  if (payload.widget_version) {
59
59
  form.append("widget_version", payload.widget_version);
60
60
  }
61
+ if (payload.page_path) {
62
+ form.append("page_path", payload.page_path);
63
+ }
61
64
  const headers = {
62
65
  Authorization: `Bearer ${options.apiKey}`
63
66
  };
@@ -197,6 +200,7 @@ function createApiClient(options) {
197
200
  if (filters.mine) params.set("mine", "1");
198
201
  if (filters.ordering) params.set("ordering", filters.ordering);
199
202
  if (filters.page && filters.page > 1) params.set("page", String(filters.page));
203
+ if (filters.pagePath) params.set("page_path", filters.pagePath);
200
204
  const qs = params.toString();
201
205
  return qs ? `?${qs}` : "";
202
206
  }
@@ -323,7 +327,7 @@ function installConsolePatch(buffer) {
323
327
  const original = console[level];
324
328
  if (typeof original !== "function") continue;
325
329
  originals[level] = original;
326
- console[level] = function patched(...args) {
330
+ console[level] = function patched2(...args) {
327
331
  try {
328
332
  const rawMessage = args.map(safeStringify).join(" ");
329
333
  const message = scrubCredentials(rawMessage).slice(0, 2e3);
@@ -350,7 +354,7 @@ function installFetchPatch(buffer, sanitize) {
350
354
  if (typeof window === "undefined" || typeof window.fetch !== "function") return () => {
351
355
  };
352
356
  const original = window.fetch.bind(window);
353
- window.fetch = async function patched(input, init) {
357
+ window.fetch = async function patched2(input, init) {
354
358
  const start = performance.now();
355
359
  const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
356
360
  const method = (init?.method || (input instanceof Request ? input.method : "GET")).toUpperCase();
@@ -630,6 +634,7 @@ var DEFAULT_STRINGS = {
630
634
  "board.detail.status_history": "History",
631
635
  "board.scope.project": "Project reports",
632
636
  "board.scope.mine": "Your reports",
637
+ "board.scope.thisPage": "This page",
633
638
  "board.back": "Back",
634
639
  "changelog.empty.title": "Nothing resolved yet",
635
640
  "changelog.empty.body": "Once a report you sent is fixed it will appear here, grouped by week.",
@@ -779,6 +784,7 @@ var FRENCH_STRINGS = {
779
784
  "board.detail.status_history": "Historique",
780
785
  "board.scope.project": "Rapports du projet",
781
786
  "board.scope.mine": "Vos rapports",
787
+ "board.scope.thisPage": "Cette page",
782
788
  "board.back": "Retour",
783
789
  "changelog.empty.title": "Rien de r\xE9solu pour l\u2019instant",
784
790
  "changelog.empty.body": "Quand un rapport que vous avez envoy\xE9 est corrig\xE9, il appara\xEEtra ici, regroup\xE9 par semaine.",
@@ -865,6 +871,36 @@ function resolveStrings(overrides, options = {}) {
865
871
  import { h, render } from "preact";
866
872
  import { useCallback, useEffect as useEffect8, useState as useState7 } from "preact/hooks";
867
873
 
874
+ // src/widget/currentPage.ts
875
+ function currentPagePath(opts) {
876
+ const override = opts.getCurrentPage?.();
877
+ if (override) return override;
878
+ return typeof window !== "undefined" ? window.location.pathname : "/";
879
+ }
880
+ var LOCATION_CHANGE = "mfb:locationchange";
881
+ var patched = false;
882
+ function patchHistory() {
883
+ if (patched || typeof history === "undefined") return;
884
+ patched = true;
885
+ for (const m of ["pushState", "replaceState"]) {
886
+ const orig = history[m];
887
+ history[m] = function(...args) {
888
+ const r = orig.apply(this, args);
889
+ window.dispatchEvent(new Event(LOCATION_CHANGE));
890
+ return r;
891
+ };
892
+ }
893
+ }
894
+ function onLocationChange(cb) {
895
+ patchHistory();
896
+ window.addEventListener("popstate", cb);
897
+ window.addEventListener(LOCATION_CHANGE, cb);
898
+ return () => {
899
+ window.removeEventListener("popstate", cb);
900
+ window.removeEventListener(LOCATION_CHANGE, cb);
901
+ };
902
+ }
903
+
868
904
  // src/widget/BoardView.tsx
869
905
  import { useEffect as useEffect2, useMemo, useState as useState2 } from "preact/hooks";
870
906
 
@@ -1335,8 +1371,9 @@ var STATUSES = [
1335
1371
  ];
1336
1372
  var TYPES = ["bug", "feature", "question", "praise", "typo"];
1337
1373
  var SEVERITIES = ["blocker", "high", "medium", "low"];
1338
- function BoardView({ api, externalId, strings }) {
1374
+ function BoardView({ api, externalId, strings, getCurrentPage }) {
1339
1375
  const [filters, setFilters] = useState2(() => loadBoardView(externalId));
1376
+ const [thisPage, setThisPage] = useState2(true);
1340
1377
  useEffect2(() => {
1341
1378
  saveBoardView(externalId, filters);
1342
1379
  }, [externalId, filtersHash(filters)]);
@@ -1350,14 +1387,23 @@ function BoardView({ api, externalId, strings }) {
1350
1387
  const activeFilterCount = useMemo(() => {
1351
1388
  return (filters.status?.length ?? 0) + (filters.type?.length ?? 0) + (filters.severity?.length ?? 0) + (filters.q ? 1 : 0) + (filters.mine ? 1 : 0);
1352
1389
  }, [filters]);
1390
+ useEffect2(() => {
1391
+ if (!thisPage) return;
1392
+ return onLocationChange(() => setReloadTick((t) => t + 1));
1393
+ }, [thisPage]);
1394
+ const fetchFilters = useMemo(
1395
+ () => thisPage ? { ...filters, pagePath: currentPagePath(getCurrentPage !== void 0 ? { getCurrentPage } : {}) } : filters,
1396
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1397
+ [filtersHash(filters), thisPage, reloadTick]
1398
+ );
1353
1399
  useEffect2(() => {
1354
1400
  let cancelled = false;
1355
1401
  let timer = null;
1356
1402
  const tick = async () => {
1357
1403
  try {
1358
1404
  const [list, k] = await Promise.all([
1359
- api.listBoard(externalId, filters),
1360
- api.fetchBoardKpis(externalId, filters)
1405
+ api.listBoard(externalId, fetchFilters),
1406
+ api.fetchBoardKpis(externalId, fetchFilters)
1361
1407
  ]);
1362
1408
  if (cancelled) return;
1363
1409
  setPage(list);
@@ -1377,7 +1423,7 @@ function BoardView({ api, externalId, strings }) {
1377
1423
  cancelled = true;
1378
1424
  if (timer) clearTimeout(timer);
1379
1425
  };
1380
- }, [api, externalId, filtersHash(filters), reloadTick]);
1426
+ }, [api, externalId, JSON.stringify(fetchFilters)]);
1381
1427
  const selectedRow = useMemo(() => {
1382
1428
  if (!selectedId || !page) return null;
1383
1429
  return page.results.find((r) => r.id === selectedId) ?? null;
@@ -1394,7 +1440,9 @@ function BoardView({ api, externalId, strings }) {
1394
1440
  filters,
1395
1441
  onChange: setFilters,
1396
1442
  activeCount: activeFilterCount,
1397
- strings
1443
+ strings,
1444
+ thisPage,
1445
+ onToggleThisPage: () => setThisPage((v) => !v)
1398
1446
  }
1399
1447
  ),
1400
1448
  /* @__PURE__ */ jsxs3("div", { class: "board-body", children: [
@@ -1502,7 +1550,9 @@ function BoardFilters({
1502
1550
  filters,
1503
1551
  onChange,
1504
1552
  activeCount,
1505
- strings
1553
+ strings,
1554
+ thisPage,
1555
+ onToggleThisPage
1506
1556
  }) {
1507
1557
  const setStatus = (value) => {
1508
1558
  if (value === "") {
@@ -1614,6 +1664,19 @@ function BoardFilters({
1614
1664
  ),
1615
1665
  /* @__PURE__ */ jsx3("span", { children: strings["board.filter.mine"] })
1616
1666
  ] }),
1667
+ /* @__PURE__ */ jsxs3(
1668
+ "button",
1669
+ {
1670
+ type: "button",
1671
+ class: `board-chip ${thisPage ? "board-chip--on" : ""}`,
1672
+ onClick: onToggleThisPage,
1673
+ children: [
1674
+ "\u{1F4CD} ",
1675
+ strings["board.scope.thisPage"],
1676
+ thisPage ? " \u2715" : ""
1677
+ ]
1678
+ }
1679
+ ),
1617
1680
  activeCount > 0 && /* @__PURE__ */ jsxs3("button", { type: "button", class: "board-filter-clear", onClick: clear, children: [
1618
1681
  /* @__PURE__ */ jsx3(CloseIcon, {}),
1619
1682
  strings["board.filter.clear"]
@@ -4626,6 +4689,17 @@ function mountWidget(options) {
4626
4689
  void _drop;
4627
4690
  return rest;
4628
4691
  }
4692
+ function openWidget(externalId) {
4693
+ rerender({ ...currentState, open: true, tab: "send" });
4694
+ if (options.openToCurrentPageFeedback && options.api && externalId) {
4695
+ options.api.fetchBoardKpis(externalId, { pagePath: currentPagePath(options) }).then((kpis) => {
4696
+ if (kpis.total > 0 && currentState.open && currentState.tab === "send") {
4697
+ rerender({ ...currentState, tab: "board" });
4698
+ }
4699
+ }).catch(() => {
4700
+ });
4701
+ }
4702
+ }
4629
4703
  function Root({ state }) {
4630
4704
  const handleSubmit = useCallback(async (values) => {
4631
4705
  rerender({ ...currentState, status: "submitting" });
@@ -4685,7 +4759,7 @@ function mountWidget(options) {
4685
4759
  Fab,
4686
4760
  {
4687
4761
  label: options.strings["fab.label"],
4688
- onClick: () => rerender({ ...currentState, open: true })
4762
+ onClick: () => openWidget(externalId)
4689
4763
  }
4690
4764
  ),
4691
4765
  state.open && /* @__PURE__ */ jsxs12(
@@ -4786,7 +4860,8 @@ function mountWidget(options) {
4786
4860
  {
4787
4861
  api: options.api,
4788
4862
  externalId,
4789
- strings: options.strings
4863
+ strings: options.strings,
4864
+ ...options.getCurrentPage !== void 0 && { getCurrentPage: options.getCurrentPage }
4790
4865
  }
4791
4866
  )
4792
4867
  ]
@@ -4797,7 +4872,7 @@ function mountWidget(options) {
4797
4872
  rerender(currentState);
4798
4873
  return {
4799
4874
  open() {
4800
- rerender({ ...currentState, open: true });
4875
+ openWidget(options.getExternalId?.());
4801
4876
  },
4802
4877
  close() {
4803
4878
  rerender({ ...currentState, open: false, status: "idle" });
@@ -4880,11 +4955,13 @@ function createFeedback(config) {
4880
4955
  capture_method: captureMethod,
4881
4956
  technical_context
4882
4957
  };
4883
- if ("0.22.0") {
4884
- payload.widget_version = "0.22.0";
4958
+ if ("0.23.0") {
4959
+ payload.widget_version = "0.23.0";
4885
4960
  }
4886
4961
  if (manualScreenshot) payload.screenshot = manualScreenshot;
4887
4962
  if (values.synthetic) payload.synthetic = true;
4963
+ const pagePath = currentPagePath(config);
4964
+ if (pagePath) payload.page_path = pagePath;
4888
4965
  if (user?.id !== void 0 && user.id !== null && user.id !== "") {
4889
4966
  payload.user = {
4890
4967
  // The host can pass `id` as a string or number; the backend
@@ -4925,7 +5002,14 @@ function createFeedback(config) {
4925
5002
  // Send the identified email alongside the id so an email-based allowlist
4926
5003
  // can match (the backend matches external_id OR email). Read from `user`
4927
5004
  // live so identity set after createFeedback() is reflected.
4928
- checkVisibility: (externalId) => api.checkVisibility(externalId, user?.email)
5005
+ checkVisibility: (externalId) => api.checkVisibility(externalId, user?.email),
5006
+ // Thread the host's getCurrentPage override so the Board chip resolves
5007
+ // the page identically to what the submit side records (§ currentPage).
5008
+ ...config.getCurrentPage !== void 0 && { getCurrentPage: config.getCurrentPage },
5009
+ // Opt-in: FAB opens to the page-scoped Board (default off → Send-first).
5010
+ ...config.openToCurrentPageFeedback !== void 0 && {
5011
+ openToCurrentPageFeedback: config.openToCurrentPageFeedback
5012
+ }
4929
5013
  });
4930
5014
  let qaHandle;
4931
5015
  let qaDisposed = false;
@@ -4992,4 +5076,4 @@ function createFeedback(config) {
4992
5076
  export {
4993
5077
  createFeedback
4994
5078
  };
4995
- //# sourceMappingURL=chunk-6OAG72JW.mjs.map
5079
+ //# sourceMappingURL=chunk-NCNRU3SR.mjs.map