@mhosaic/feedback 0.33.0 → 0.35.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-WROPP3IB.mjs → chunk-557ACRBU.mjs} +7 -2
- package/dist/chunk-557ACRBU.mjs.map +1 -0
- package/dist/{chunk-RR6UB75B.mjs → chunk-A2TMODNI.mjs} +172 -36
- package/dist/chunk-A2TMODNI.mjs.map +1 -0
- package/dist/embed.min.js +21 -5
- package/dist/embed.min.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/loader/react.mjs +1 -1
- package/dist/loader.d.ts +1 -0
- package/dist/loader.min.js +1 -1
- package/dist/loader.min.js.map +1 -1
- package/dist/loader.mjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/vue.d.ts +29 -0
- package/dist/vue.mjs +44 -0
- package/dist/vue.mjs.map +1 -0
- package/dist/widget.min.js +22 -6
- package/dist/widget.min.js.map +1 -1
- package/package.json +16 -7
- package/dist/chunk-RR6UB75B.mjs.map +0 -1
- package/dist/chunk-WROPP3IB.mjs.map +0 -1
|
@@ -197,7 +197,12 @@ async function loadAndAttach(config, deferred) {
|
|
|
197
197
|
// flag > on. The loader provider forwards its `errorTracking` prop here
|
|
198
198
|
// rather than wrapping host-side, so there's exactly one arm point (the
|
|
199
199
|
// real instance) — no deferred/real double-registration.
|
|
200
|
-
errorTracking: config.errorTracking ?? manifest.config?.error_tracking ?? true
|
|
200
|
+
errorTracking: config.errorTracking ?? manifest.config?.error_tracking ?? true,
|
|
201
|
+
// Roadmap #17: operator-flipped behavior toggles ride the manifest;
|
|
202
|
+
// an explicit host value still wins.
|
|
203
|
+
...config.openToCurrentPageFeedback === void 0 && typeof manifest.config?.behavior?.openToCurrentPageFeedback === "boolean" ? {
|
|
204
|
+
openToCurrentPageFeedback: manifest.config.behavior.openToCurrentPageFeedback
|
|
205
|
+
} : {}
|
|
201
206
|
});
|
|
202
207
|
deferred._attach(real);
|
|
203
208
|
}
|
|
@@ -205,4 +210,4 @@ async function loadAndAttach(config, deferred) {
|
|
|
205
210
|
export {
|
|
206
211
|
createFeedback
|
|
207
212
|
};
|
|
208
|
-
//# sourceMappingURL=chunk-
|
|
213
|
+
//# sourceMappingURL=chunk-557ACRBU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/loader/bundleLoader.ts","../src/loader/manifest.ts","../src/loader/deferredApi.ts","../src/loader/index.ts"],"sourcesContent":["/**\n * Inject the widget bundle as an SRI-protected <script> and wait for its\n * window global to appear.\n *\n * Why a <script> tag and not `import()`:\n * `import()` can fetch cross-origin ESM but doesn't honor Subresource\n * Integrity on the dynamic-import call itself. A <script> tag with\n * `integrity=…` is the only path where the browser actually refuses\n * to execute a bundle whose bytes don't match the manifest's hash.\n *\n * The bundle is built with `tsup --format iife --globalName MhosaicFeedback`,\n * so once it parses it sets `window.MhosaicFeedback = { createFeedback }`\n * (per `src/widget.ts`'s exports). We wait for that to appear.\n *\n * Idempotency: if multiple FeedbackProviders mount on the same page (e.g.\n * during a React fast-refresh cycle), we reuse an already-injected bundle\n * instead of fetching a second copy.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\nimport type { ErrorTrackingOptions } from '../modules/error-tracking'\n\n/** What the loader may pass to the bundle's `createFeedback`: the public\n * config plus the bundle-only `errorTracking` knob the loader forwards\n * from the manifest. */\nexport type BundleConfig = FeedbackConfig & {\n errorTracking?: boolean | ErrorTrackingOptions\n}\n\ndeclare global {\n interface Window {\n MhosaicFeedback?: {\n createFeedback(config: BundleConfig): FeedbackApi\n }\n }\n}\n\nconst SCRIPT_DATA_ATTR = 'data-mhosaic-feedback-bundle'\n\nexport interface BundleLoadOptions {\n bundleUrl: string\n sriHash: string\n /** Default 30 s. Long enough for slow networks; short enough to surface\n * CDN outages quickly. */\n timeoutMs?: number\n}\n\nexport interface BundleHandle {\n createFeedback(config: BundleConfig): FeedbackApi\n}\n\nexport async function injectBundle(\n opts: BundleLoadOptions,\n): Promise<BundleHandle> {\n // Reuse a bundle already loaded this page.\n if (window.MhosaicFeedback?.createFeedback) {\n return window.MhosaicFeedback\n }\n\n const existing = document.querySelector<HTMLScriptElement>(\n `script[${SCRIPT_DATA_ATTR}]`,\n )\n if (existing) {\n // Another loader instance started the fetch; wait for the global.\n return waitForGlobal(opts.timeoutMs ?? 30_000)\n }\n\n return new Promise<BundleHandle>((resolve, reject) => {\n const script = document.createElement('script')\n script.src = opts.bundleUrl\n script.integrity = opts.sriHash\n script.crossOrigin = 'anonymous'\n script.async = true\n script.setAttribute(SCRIPT_DATA_ATTR, '1')\n script.onload = () => {\n if (window.MhosaicFeedback?.createFeedback) {\n resolve(window.MhosaicFeedback)\n } else {\n reject(\n new Error(\n 'mhosaic-feedback: bundle loaded but window.MhosaicFeedback.createFeedback is missing — bundle/loader version mismatch?',\n ),\n )\n }\n }\n script.onerror = () => {\n reject(\n new Error(\n `mhosaic-feedback: failed to load bundle from ${opts.bundleUrl} ` +\n '(network, CSP, or SRI mismatch)',\n ),\n )\n }\n document.head.appendChild(script)\n\n const timeoutMs = opts.timeoutMs ?? 30_000\n setTimeout(() => {\n reject(\n new Error(`mhosaic-feedback: bundle load timeout after ${timeoutMs}ms`),\n )\n }, timeoutMs)\n })\n}\n\nfunction waitForGlobal(timeoutMs: number): Promise<BundleHandle> {\n return new Promise((resolve, reject) => {\n const start = Date.now()\n const tick = () => {\n if (window.MhosaicFeedback?.createFeedback) {\n resolve(window.MhosaicFeedback)\n return\n }\n if (Date.now() - start > timeoutMs) {\n reject(\n new Error(\n 'mhosaic-feedback: timed out waiting for bundle global (another loader instance started the fetch but never finished)',\n ),\n )\n return\n }\n setTimeout(tick, 50)\n }\n tick()\n })\n}\n","/**\n * Fetch the widget-manifest from the Mhosaic backend.\n *\n * The loader calls this on every page load. The backend reads `Project.\n * pinned_version` (or current stable) + `Project.widget_enabled` and\n * returns the bundle URL + SRI hash for the version this tenant should\n * receive — or `{enabled: false}` if the widget is killed for the tenant.\n *\n * Network failures are surfaced to the caller. The loader degrades to\n * \"widget not present\" rather than crashing the host page.\n */\n\nexport interface Manifest {\n /** False when the widget is disabled for this project (kill switch) or\n * no stable release exists. Loader bails silently. */\n enabled: boolean\n /** Semver of the bundle being served. Present when enabled=true. */\n version?: string\n /** Fully-qualified URL to the version-pinned bundle (jsDelivr). */\n bundle_url?: string\n /** Subresource integrity hash for the bundle. The loader injects the\n * script tag with `integrity=…` so the browser refuses to run a\n * bundle whose bytes don't match. */\n sri_hash?: string\n /** Static config the manifest endpoint passes through to the widget. */\n config?: {\n endpoint: string\n project_slug: string\n share_reports_with_widget: boolean\n /** Phase 4: project gates the widget per end-user (default false). */\n requires_visibility_check?: boolean\n /** Per-project switch for the bundle's default error capture. When the\n * backend omits it, the bundle falls back to its own default (on). */\n error_tracking?: boolean\n behavior?: Record<string, boolean>\n }\n /** Human-readable detail when `enabled` is false. */\n detail?: string\n}\n\nexport async function fetchManifest(\n endpoint: string,\n apiKey: string,\n signal?: AbortSignal,\n): Promise<Manifest> {\n const base = endpoint.replace(/\\/$/, '')\n const url = `${base}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(apiKey)}`\n // credentials: 'omit' — we authenticate via the public pk_proj_ query\n // param; sending cookies would just trigger CORS preflights for no\n // reason and is unnecessary since the manifest endpoint doesn't read\n // any session.\n const init: RequestInit = { credentials: 'omit' }\n if (signal) init.signal = signal\n const res = await fetch(url, init)\n if (!res.ok) {\n throw new Error(\n `mhosaic-feedback: manifest fetch failed (HTTP ${res.status})`,\n )\n }\n const body = (await res.json()) as Manifest\n return body\n}\n","/**\n * A FeedbackApi-shaped object that queues method calls until the real\n * widget bundle finishes loading, then drains the queue against the real\n * implementation.\n *\n * Why we need this: hosts that already have code like\n * const fb = createFeedback({...})\n * fb.identify({id, email, name})\n * useEffect(() => fb.open(...), [])\n * shouldn't have to refactor for async. The loader's createFeedback\n * returns sync — a deferred handle that records intent, then replays it\n * once the real bundle arrives. Idiomatic queue-then-flush pattern.\n *\n * If the bundle never loads (network failure, SRI mismatch, kill switch),\n * `_fail()` is called instead. Void methods (identify/setMetadata/open/...)\n * silently drop in that case — the widget just doesn't appear. The\n * `submit` Promise rejects so callers can surface real errors.\n */\n\nimport type {\n FeedbackApi,\n ReportPayload,\n SubmittedReport,\n UserIdentity,\n} from '../types'\n\ntype VoidMethod = 'show' | 'hide' | 'shutdown'\ntype ArgMethod = 'open' | 'identify' | 'setMetadata'\n\ntype Queued =\n | { kind: 'void'; method: VoidMethod }\n | { kind: 'arg'; method: 'open'; arg: Parameters<FeedbackApi['open']>[0] }\n | { kind: 'arg'; method: 'identify'; arg: UserIdentity }\n | { kind: 'arg'; method: 'setMetadata'; arg: Record<string, unknown> }\n | {\n kind: 'submit'\n payload: Partial<ReportPayload> & { description: string }\n resolve: (r: SubmittedReport) => void\n reject: (e: unknown) => void\n }\n\nexport interface DeferredHandle extends FeedbackApi {\n /** Called by the loader once the bundle is loaded and a real API exists. */\n _attach(real: FeedbackApi): void\n /** Called by the loader if the bundle fails to load. */\n _fail(err: Error): void\n /** Buffered transformer registry — forwarded to the real instance on\n * attach, so wrapper modules (withReplay) work over loader installs. */\n _registerTransformer(fn: unknown): void\n}\n\nexport function createDeferredApi(): DeferredHandle {\n let real: FeedbackApi | null = null\n let failure: Error | null = null\n const queue: Queued[] = []\n // Transformers (withReplay & friends) registered before the bundle\n // attaches. Forwarded to the real instance's registry on _attach so\n // host-side wrappers work over a loader install too (roadmap #7).\n const pendingTransformers: unknown[] = []\n\n function flush(item: Queued): void {\n if (real) {\n if (item.kind === 'void') {\n ;(real[item.method] as () => void)()\n } else if (item.kind === 'arg') {\n ;(real[item.method] as (a: unknown) => void)(item.arg)\n } else {\n // submit\n Promise.resolve(real.submit(item.payload)).then(\n item.resolve,\n item.reject,\n )\n }\n return\n }\n if (failure) {\n if (item.kind === 'submit') item.reject(failure)\n // Void / arg methods silently dropped — the widget never appeared.\n return\n }\n queue.push(item)\n }\n\n return {\n show() {\n flush({ kind: 'void', method: 'show' })\n },\n hide() {\n flush({ kind: 'void', method: 'hide' })\n },\n shutdown() {\n flush({ kind: 'void', method: 'shutdown' })\n },\n open(arg) {\n flush({ kind: 'arg', method: 'open', arg })\n },\n identify(arg) {\n flush({ kind: 'arg', method: 'identify', arg })\n },\n setMetadata(arg) {\n flush({ kind: 'arg', method: 'setMetadata', arg })\n },\n submit(payload) {\n return new Promise<SubmittedReport>((resolve, reject) => {\n flush({ kind: 'submit', payload, resolve, reject })\n })\n },\n _registerTransformer(fn: unknown) {\n const target = real as { _registerTransformer?: (f: unknown) => void } | null\n if (target && typeof target._registerTransformer === 'function') {\n target._registerTransformer(fn)\n return\n }\n if (failure) return // widget never appeared — nothing to transform\n pendingTransformers.push(fn)\n },\n _attach(impl) {\n real = impl\n const target = impl as { _registerTransformer?: (f: unknown) => void }\n if (typeof target._registerTransformer === 'function') {\n for (const fn of pendingTransformers) target._registerTransformer(fn)\n }\n pendingTransformers.length = 0\n while (queue.length > 0) flush(queue.shift()!)\n },\n _fail(err) {\n failure = err\n pendingTransformers.length = 0\n // Drain any pending submits with the failure; drop void/arg.\n for (const item of queue) {\n if (item.kind === 'submit') item.reject(err)\n }\n queue.length = 0\n },\n }\n}\n","/**\n * `@mhosaic/feedback/loader` — public entry for the loader architecture.\n *\n * Same shape as the direct-import path (`@mhosaic/feedback`), but the\n * widget bundle is fetched at runtime from a CDN URL the Mhosaic backend\n * dictates via the manifest endpoint. Letting hosts switch from the\n * direct-import path to the loader path gives them auto-updates without\n * any further code changes — Mhosaic ships a new Release row, every host\n * picks up the new bundle on next page load.\n *\n * Public surface MUST mirror the direct-import path exactly:\n * - `createFeedback(config)` returns a `FeedbackApi`-shaped handle.\n * - Method calls before the bundle finishes loading queue up and replay.\n * - `submit()` returns a Promise that resolves after the bundle is up\n * and the real submit completes.\n *\n * If the manifest endpoint reports the widget is disabled for this\n * project (`enabled: false` — kill switch or no stable release configured),\n * the deferred handle silently no-ops void calls and rejects `submit()`\n * calls with a clear error. The host page never crashes.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\nimport type { ErrorTrackingOptions } from '../modules/error-tracking'\nimport { injectBundle } from './bundleLoader'\nimport { fetchManifest } from './manifest'\nimport { createDeferredApi, type DeferredHandle } from './deferredApi'\n\nexport type { FeedbackApi, FeedbackConfig } from '../types'\nexport type { Manifest } from './manifest'\n\n/** Loader config = the public widget config plus the loader-only knobs the\n * host can set to override what the manifest dictates. */\nexport type LoaderConfig = FeedbackConfig & {\n /** Override the bundle's default error capture. Omit (or pass `undefined`)\n * to let the project's manifest flag decide (which itself defaults on). */\n errorTracking?: boolean | ErrorTrackingOptions | undefined\n}\n\nexport function createFeedback(config: LoaderConfig): FeedbackApi {\n const deferred = createDeferredApi()\n loadAndAttach(config, deferred).catch((err: Error) => {\n // Surface to console — host devtools is the canonical channel for\n // widget bootstrap errors. Production telemetry is the bundle's job\n // (it can't run if it never loaded), so console.warn is the floor.\n // eslint-disable-next-line no-console\n console.warn('[mhosaic-feedback] widget did not load:', err.message)\n deferred._fail(err)\n })\n return deferred\n}\n\nasync function loadAndAttach(\n config: LoaderConfig,\n deferred: DeferredHandle,\n): Promise<void> {\n const manifest = await fetchManifest(config.endpoint, config.apiKey)\n if (!manifest.enabled || !manifest.bundle_url || !manifest.sri_hash) {\n // Disabled by Mhosaic (kill switch) or no stable release. Surface a\n // clear failure so submit() callers see an error, but no host crash.\n deferred._fail(\n new Error(\n `mhosaic-feedback: widget disabled for this project${\n manifest.detail ? ` — ${manifest.detail}` : ''\n }`,\n ),\n )\n return\n }\n const bundle = await injectBundle({\n bundleUrl: manifest.bundle_url,\n sriHash: manifest.sri_hash,\n })\n const real: FeedbackApi = bundle.createFeedback({\n ...config,\n // Forward the project's per-end-user gating flag from the manifest unless\n // the host set it explicitly. The widget then runs the visibility check.\n requiresVisibilityCheck:\n config.requiresVisibilityCheck ??\n manifest.config?.requires_visibility_check ??\n false,\n // Error capture is armed INSIDE the bundle (the only auto-updating part\n // of a loader install), so already-deployed hosts pick it up without a\n // redeploy. Precedence: explicit host override > per-project manifest\n // flag > on. The loader provider forwards its `errorTracking` prop here\n // rather than wrapping host-side, so there's exactly one arm point (the\n // real instance) — no deferred/real double-registration.\n errorTracking:\n config.errorTracking ?? manifest.config?.error_tracking ?? true,\n // Roadmap #17: operator-flipped behavior toggles ride the manifest;\n // an explicit host value still wins.\n ...(config.openToCurrentPageFeedback === undefined &&\n typeof manifest.config?.behavior?.openToCurrentPageFeedback === 'boolean'\n ? {\n openToCurrentPageFeedback:\n manifest.config.behavior.openToCurrentPageFeedback,\n }\n : {}),\n })\n deferred._attach(real)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -773,6 +773,10 @@ var DEFAULT_STRINGS = {
|
|
|
773
773
|
"mine.empty.title": "No reports yet",
|
|
774
774
|
"mine.empty.body": "Once you send feedback you can follow the thread here.",
|
|
775
775
|
"mine.refresh": "Refresh",
|
|
776
|
+
"mine.validate.banner": "{count} fix(es) awaiting your confirmation",
|
|
777
|
+
"mine.validate.confirm": "Fix works",
|
|
778
|
+
"mine.validate.reopen": "Still broken",
|
|
779
|
+
"mine.validate.view": "Review",
|
|
776
780
|
"diag.run": "Diagnose",
|
|
777
781
|
"diag.running": "Checking the connection\u2026",
|
|
778
782
|
"diag.offline": "You appear to be offline. Reconnect and retry.",
|
|
@@ -969,6 +973,10 @@ var FRENCH_STRINGS = {
|
|
|
969
973
|
"mine.empty.title": "Aucun rapport",
|
|
970
974
|
"mine.empty.body": "Apr\xE8s votre premier envoi vous pourrez suivre la conversation ici.",
|
|
971
975
|
"mine.refresh": "Actualiser",
|
|
976
|
+
"mine.validate.banner": "{count} correctif(s) \xE0 confirmer",
|
|
977
|
+
"mine.validate.confirm": "Corrig\xE9 \u2713",
|
|
978
|
+
"mine.validate.reopen": "Toujours cass\xE9",
|
|
979
|
+
"mine.validate.view": "Voir",
|
|
972
980
|
"diag.run": "Diagnostiquer",
|
|
973
981
|
"diag.running": "V\xE9rification de la connexion\u2026",
|
|
974
982
|
"diag.offline": "Vous semblez hors ligne. Reconnectez-vous puis r\xE9essayez.",
|
|
@@ -3373,26 +3381,20 @@ function Form({
|
|
|
3373
3381
|
const files = Array.from(e.dataTransfer?.files ?? []);
|
|
3374
3382
|
if (files.length) acceptFiles(files);
|
|
3375
3383
|
};
|
|
3376
|
-
|
|
3377
|
-
const
|
|
3378
|
-
if (!
|
|
3379
|
-
const
|
|
3380
|
-
|
|
3381
|
-
if (
|
|
3382
|
-
|
|
3383
|
-
if (
|
|
3384
|
-
const file = item.getAsFile();
|
|
3385
|
-
if (file) {
|
|
3386
|
-
e.preventDefault();
|
|
3387
|
-
acceptFiles([file]);
|
|
3388
|
-
return;
|
|
3389
|
-
}
|
|
3390
|
-
}
|
|
3384
|
+
const handlePaste = (e) => {
|
|
3385
|
+
const items = e.clipboardData?.items;
|
|
3386
|
+
if (!items) return;
|
|
3387
|
+
const files = [];
|
|
3388
|
+
for (const item of Array.from(items)) {
|
|
3389
|
+
if (item.kind === "file" && item.type.startsWith("image/")) {
|
|
3390
|
+
const file = item.getAsFile();
|
|
3391
|
+
if (file) files.push(file);
|
|
3391
3392
|
}
|
|
3392
|
-
}
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3393
|
+
}
|
|
3394
|
+
if (!files.length) return;
|
|
3395
|
+
e.preventDefault();
|
|
3396
|
+
acceptFiles(files);
|
|
3397
|
+
};
|
|
3396
3398
|
const handleAnnotated = (annotated) => {
|
|
3397
3399
|
const index = annotatingIndex;
|
|
3398
3400
|
if (index === null || !shots[index]) {
|
|
@@ -3422,7 +3424,7 @@ function Form({
|
|
|
3422
3424
|
}
|
|
3423
3425
|
onSubmit(values);
|
|
3424
3426
|
};
|
|
3425
|
-
return /* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, children: [
|
|
3427
|
+
return /* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, onPaste: handlePaste, children: [
|
|
3426
3428
|
/* @__PURE__ */ jsx9("h2", { children: strings["form.title"] }),
|
|
3427
3429
|
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
3428
3430
|
/* @__PURE__ */ jsx9("label", { for: "mfb-desc", children: strings["form.description.label"] }),
|
|
@@ -3487,16 +3489,44 @@ function Form({
|
|
|
3487
3489
|
class: "btn btn--primary screenshot-annotate",
|
|
3488
3490
|
onClick: () => setAnnotatingIndex(index),
|
|
3489
3491
|
children: [
|
|
3490
|
-
/* @__PURE__ */ jsxs9(
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3492
|
+
/* @__PURE__ */ jsxs9(
|
|
3493
|
+
"svg",
|
|
3494
|
+
{
|
|
3495
|
+
width: "14",
|
|
3496
|
+
height: "14",
|
|
3497
|
+
viewBox: "0 0 24 24",
|
|
3498
|
+
fill: "none",
|
|
3499
|
+
stroke: "currentColor",
|
|
3500
|
+
"stroke-width": "2",
|
|
3501
|
+
"stroke-linecap": "round",
|
|
3502
|
+
"stroke-linejoin": "round",
|
|
3503
|
+
"aria-hidden": "true",
|
|
3504
|
+
children: [
|
|
3505
|
+
/* @__PURE__ */ jsx9("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
|
|
3506
|
+
/* @__PURE__ */ jsx9("path", { d: "M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
|
|
3507
|
+
]
|
|
3508
|
+
}
|
|
3509
|
+
),
|
|
3494
3510
|
strings["form.screenshot.annotate"]
|
|
3495
3511
|
]
|
|
3496
3512
|
}
|
|
3497
3513
|
),
|
|
3498
3514
|
/* @__PURE__ */ jsxs9("button", { type: "button", class: "btn", onClick: () => removeShot(index), children: [
|
|
3499
|
-
/* @__PURE__ */ jsx9(
|
|
3515
|
+
/* @__PURE__ */ jsx9(
|
|
3516
|
+
"svg",
|
|
3517
|
+
{
|
|
3518
|
+
width: "14",
|
|
3519
|
+
height: "14",
|
|
3520
|
+
viewBox: "0 0 24 24",
|
|
3521
|
+
fill: "none",
|
|
3522
|
+
stroke: "currentColor",
|
|
3523
|
+
"stroke-width": "2",
|
|
3524
|
+
"stroke-linecap": "round",
|
|
3525
|
+
"stroke-linejoin": "round",
|
|
3526
|
+
"aria-hidden": "true",
|
|
3527
|
+
children: /* @__PURE__ */ jsx9("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" })
|
|
3528
|
+
}
|
|
3529
|
+
),
|
|
3500
3530
|
strings["form.screenshot.remove"]
|
|
3501
3531
|
] })
|
|
3502
3532
|
] })
|
|
@@ -3520,11 +3550,26 @@ function Form({
|
|
|
3520
3550
|
onDragLeave: handleDragLeave,
|
|
3521
3551
|
onDrop: handleDrop,
|
|
3522
3552
|
children: [
|
|
3523
|
-
/* @__PURE__ */ jsxs9(
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3553
|
+
/* @__PURE__ */ jsxs9(
|
|
3554
|
+
"svg",
|
|
3555
|
+
{
|
|
3556
|
+
class: "screenshot-icon",
|
|
3557
|
+
width: "28",
|
|
3558
|
+
height: "28",
|
|
3559
|
+
viewBox: "0 0 24 24",
|
|
3560
|
+
fill: "none",
|
|
3561
|
+
stroke: "currentColor",
|
|
3562
|
+
"stroke-width": "1.6",
|
|
3563
|
+
"stroke-linecap": "round",
|
|
3564
|
+
"stroke-linejoin": "round",
|
|
3565
|
+
"aria-hidden": "true",
|
|
3566
|
+
children: [
|
|
3567
|
+
/* @__PURE__ */ jsx9("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
3568
|
+
/* @__PURE__ */ jsx9("polyline", { points: "17 8 12 3 7 8" }),
|
|
3569
|
+
/* @__PURE__ */ jsx9("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
3570
|
+
]
|
|
3571
|
+
}
|
|
3572
|
+
),
|
|
3528
3573
|
/* @__PURE__ */ jsxs9("div", { class: "screenshot-cta", children: [
|
|
3529
3574
|
/* @__PURE__ */ jsx9("strong", { children: strings["form.screenshot.cta_click"] }),
|
|
3530
3575
|
", ",
|
|
@@ -3657,6 +3702,20 @@ function MineList({ api, externalId, strings, onSelect, onOpenSeq }) {
|
|
|
3657
3702
|
const [error, setError] = useState7(null);
|
|
3658
3703
|
const [refreshing, setRefreshing] = useState7(false);
|
|
3659
3704
|
const [filter, setFilter] = useState7("all");
|
|
3705
|
+
const [validating, setValidating] = useState7(null);
|
|
3706
|
+
const validate = async (reportId, kind) => {
|
|
3707
|
+
setValidating(reportId);
|
|
3708
|
+
try {
|
|
3709
|
+
if (kind === "confirm") await api.closeAsResolved(reportId, externalId);
|
|
3710
|
+
else await api.reopenUnresolved(reportId, externalId);
|
|
3711
|
+
await fetchRows();
|
|
3712
|
+
} catch (err) {
|
|
3713
|
+
if (typeof console !== "undefined") console.warn("[mhosaic] validate:", err);
|
|
3714
|
+
if (mountedRef.current) setError(strings["mine.error"]);
|
|
3715
|
+
} finally {
|
|
3716
|
+
if (mountedRef.current) setValidating(null);
|
|
3717
|
+
}
|
|
3718
|
+
};
|
|
3660
3719
|
const [jumpValue, setJumpValue] = useState7("");
|
|
3661
3720
|
const [jumpError, setJumpError] = useState7(false);
|
|
3662
3721
|
const [jumping, setJumping] = useState7(false);
|
|
@@ -3746,6 +3805,21 @@ function MineList({ api, externalId, strings, onSelect, onOpenSeq }) {
|
|
|
3746
3805
|
] })
|
|
3747
3806
|
] }),
|
|
3748
3807
|
jumpError && /* @__PURE__ */ jsx11("div", { class: "mine-jump-error error", role: "alert", children: strings["mine.jump.not_found"] }),
|
|
3808
|
+
rows && rows.some((r) => r.status === "awaiting_validation") && /* @__PURE__ */ jsxs11("div", { class: "validate-banner", role: "status", children: [
|
|
3809
|
+
/* @__PURE__ */ jsx11("span", { children: strings["mine.validate.banner"].replace(
|
|
3810
|
+
"{count}",
|
|
3811
|
+
String(rows.filter((r) => r.status === "awaiting_validation").length)
|
|
3812
|
+
) }),
|
|
3813
|
+
/* @__PURE__ */ jsx11(
|
|
3814
|
+
"button",
|
|
3815
|
+
{
|
|
3816
|
+
type: "button",
|
|
3817
|
+
class: "btn",
|
|
3818
|
+
onClick: () => setFilter("awaiting_validation"),
|
|
3819
|
+
children: strings["mine.validate.view"]
|
|
3820
|
+
}
|
|
3821
|
+
)
|
|
3822
|
+
] }),
|
|
3749
3823
|
rows && rows.length > 0 && /* @__PURE__ */ jsx11(KpiStrip, { rows, filter, onFilter: setFilter, strings }),
|
|
3750
3824
|
isLoading && /* @__PURE__ */ jsx11("div", { class: "mine-loading", children: strings["mine.loading"] }),
|
|
3751
3825
|
error && /* @__PURE__ */ jsxs11("div", { class: "error", children: [
|
|
@@ -3757,7 +3831,35 @@ function MineList({ api, externalId, strings, onSelect, onOpenSeq }) {
|
|
|
3757
3831
|
/* @__PURE__ */ jsx11("p", { children: strings["mine.empty.body"] })
|
|
3758
3832
|
] }),
|
|
3759
3833
|
visibleEmpty && /* @__PURE__ */ jsx11("div", { class: "mine-empty", children: /* @__PURE__ */ jsx11("p", { children: strings["mine.filter.empty"] }) }),
|
|
3760
|
-
visibleRows && visibleRows.length > 0 && /* @__PURE__ */ jsx11("ul", { class: "mine-rows", children: visibleRows.map((row) => /* @__PURE__ */
|
|
3834
|
+
visibleRows && visibleRows.length > 0 && /* @__PURE__ */ jsx11("ul", { class: "mine-rows", children: visibleRows.map((row) => /* @__PURE__ */ jsxs11("li", { children: [
|
|
3835
|
+
/* @__PURE__ */ jsx11(ReportRow, { row, strings, onClick: () => onSelect(row) }),
|
|
3836
|
+
row.status === "awaiting_validation" && /* @__PURE__ */ jsxs11("div", { class: "validate-actions", children: [
|
|
3837
|
+
/* @__PURE__ */ jsx11(
|
|
3838
|
+
"button",
|
|
3839
|
+
{
|
|
3840
|
+
type: "button",
|
|
3841
|
+
class: "btn",
|
|
3842
|
+
disabled: validating === row.id,
|
|
3843
|
+
onClick: () => {
|
|
3844
|
+
void validate(row.id, "confirm");
|
|
3845
|
+
},
|
|
3846
|
+
children: strings["mine.validate.confirm"]
|
|
3847
|
+
}
|
|
3848
|
+
),
|
|
3849
|
+
/* @__PURE__ */ jsx11(
|
|
3850
|
+
"button",
|
|
3851
|
+
{
|
|
3852
|
+
type: "button",
|
|
3853
|
+
class: "btn btn--ghost",
|
|
3854
|
+
disabled: validating === row.id,
|
|
3855
|
+
onClick: () => {
|
|
3856
|
+
void validate(row.id, "reopen");
|
|
3857
|
+
},
|
|
3858
|
+
children: strings["mine.validate.reopen"]
|
|
3859
|
+
}
|
|
3860
|
+
)
|
|
3861
|
+
] })
|
|
3862
|
+
] })) })
|
|
3761
3863
|
] });
|
|
3762
3864
|
}
|
|
3763
3865
|
|
|
@@ -4775,6 +4877,22 @@ var WIDGET_STYLES = `
|
|
|
4775
4877
|
}
|
|
4776
4878
|
.mine-empty strong { display: block; color: var(--mfb-text); margin-bottom: 4px; }
|
|
4777
4879
|
|
|
4880
|
+
.validate-banner {
|
|
4881
|
+
display: flex;
|
|
4882
|
+
align-items: center;
|
|
4883
|
+
justify-content: space-between;
|
|
4884
|
+
gap: 8px;
|
|
4885
|
+
padding: 8px 12px;
|
|
4886
|
+
margin-bottom: 8px;
|
|
4887
|
+
border: 1px solid var(--mfb-accent, #2563eb);
|
|
4888
|
+
border-radius: 8px;
|
|
4889
|
+
font-size: 13px;
|
|
4890
|
+
}
|
|
4891
|
+
.validate-actions {
|
|
4892
|
+
display: flex;
|
|
4893
|
+
gap: 8px;
|
|
4894
|
+
margin: 4px 0 8px;
|
|
4895
|
+
}
|
|
4778
4896
|
.mine-rows {
|
|
4779
4897
|
list-style: none;
|
|
4780
4898
|
margin: 0;
|
|
@@ -6483,8 +6601,8 @@ function createFeedback(config) {
|
|
|
6483
6601
|
capture_method: captureMethod,
|
|
6484
6602
|
technical_context
|
|
6485
6603
|
};
|
|
6486
|
-
if ("0.
|
|
6487
|
-
payload.widget_version = "0.
|
|
6604
|
+
if ("0.35.0") {
|
|
6605
|
+
payload.widget_version = "0.35.0";
|
|
6488
6606
|
}
|
|
6489
6607
|
if (manualScreenshots?.length) {
|
|
6490
6608
|
payload.screenshots = manualScreenshots;
|
|
@@ -6515,7 +6633,7 @@ function createFeedback(config) {
|
|
|
6515
6633
|
throw error;
|
|
6516
6634
|
}
|
|
6517
6635
|
}
|
|
6518
|
-
const
|
|
6636
|
+
const mountOptions = {
|
|
6519
6637
|
host,
|
|
6520
6638
|
strings,
|
|
6521
6639
|
showFAB: config.showFAB ?? true,
|
|
@@ -6543,7 +6661,25 @@ function createFeedback(config) {
|
|
|
6543
6661
|
...config.showPageActivity !== void 0 && {
|
|
6544
6662
|
showPageActivity: config.showPageActivity
|
|
6545
6663
|
}
|
|
6546
|
-
}
|
|
6664
|
+
};
|
|
6665
|
+
const handle = mountWidget(mountOptions);
|
|
6666
|
+
if (config.openToCurrentPageFeedback === void 0 && config.showFAB !== false) {
|
|
6667
|
+
const behaviorFetch = config.fetchImpl ?? globalThis.fetch;
|
|
6668
|
+
void (async () => {
|
|
6669
|
+
try {
|
|
6670
|
+
const r = await behaviorFetch(
|
|
6671
|
+
`${config.endpoint}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(config.apiKey)}`
|
|
6672
|
+
);
|
|
6673
|
+
if (!r.ok) return;
|
|
6674
|
+
const m = await r.json();
|
|
6675
|
+
const v = m?.config?.behavior?.openToCurrentPageFeedback;
|
|
6676
|
+
if (typeof v === "boolean" && mountOptions.openToCurrentPageFeedback === void 0) {
|
|
6677
|
+
mountOptions.openToCurrentPageFeedback = v;
|
|
6678
|
+
}
|
|
6679
|
+
} catch {
|
|
6680
|
+
}
|
|
6681
|
+
})();
|
|
6682
|
+
}
|
|
6547
6683
|
let qaHandle;
|
|
6548
6684
|
let qaDisposed = false;
|
|
6549
6685
|
if (config.qaMeter?.source) {
|
|
@@ -6610,4 +6746,4 @@ function createFeedback(config) {
|
|
|
6610
6746
|
export {
|
|
6611
6747
|
createFeedback
|
|
6612
6748
|
};
|
|
6613
|
-
//# sourceMappingURL=chunk-
|
|
6749
|
+
//# sourceMappingURL=chunk-A2TMODNI.mjs.map
|