@mhosaic/feedback 0.15.6 → 0.16.2
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-6JDT2KWQ.mjs +180 -0
- package/dist/chunk-6JDT2KWQ.mjs.map +1 -0
- package/dist/{chunk-AQW4WVZE.mjs → chunk-I4GWTJ6Q.mjs} +87 -66
- package/dist/chunk-I4GWTJ6Q.mjs.map +1 -0
- package/dist/embed.min.js +24 -13
- package/dist/embed.min.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/loader/react.d.ts +12 -0
- package/dist/loader/react.mjs +30 -0
- package/dist/loader/react.mjs.map +1 -0
- package/dist/loader.d.ts +60 -0
- package/dist/loader.min.js +2 -0
- package/dist/loader.min.js.map +1 -0
- package/dist/loader.mjs +7 -0
- package/dist/loader.mjs.map +1 -0
- package/dist/react.mjs +1 -1
- package/dist/widget.min.js +1694 -0
- package/dist/widget.min.js.map +1 -0
- package/package.json +9 -1
- package/dist/chunk-AQW4WVZE.mjs.map +0 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// src/loader/bundleLoader.ts
|
|
2
|
+
var SCRIPT_DATA_ATTR = "data-mhosaic-feedback-bundle";
|
|
3
|
+
async function injectBundle(opts) {
|
|
4
|
+
if (window.MhosaicFeedback?.createFeedback) {
|
|
5
|
+
return window.MhosaicFeedback;
|
|
6
|
+
}
|
|
7
|
+
const existing = document.querySelector(
|
|
8
|
+
`script[${SCRIPT_DATA_ATTR}]`
|
|
9
|
+
);
|
|
10
|
+
if (existing) {
|
|
11
|
+
return waitForGlobal(opts.timeoutMs ?? 3e4);
|
|
12
|
+
}
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const script = document.createElement("script");
|
|
15
|
+
script.src = opts.bundleUrl;
|
|
16
|
+
script.integrity = opts.sriHash;
|
|
17
|
+
script.crossOrigin = "anonymous";
|
|
18
|
+
script.async = true;
|
|
19
|
+
script.setAttribute(SCRIPT_DATA_ATTR, "1");
|
|
20
|
+
script.onload = () => {
|
|
21
|
+
if (window.MhosaicFeedback?.createFeedback) {
|
|
22
|
+
resolve(window.MhosaicFeedback);
|
|
23
|
+
} else {
|
|
24
|
+
reject(
|
|
25
|
+
new Error(
|
|
26
|
+
"mhosaic-feedback: bundle loaded but window.MhosaicFeedback.createFeedback is missing \u2014 bundle/loader version mismatch?"
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
script.onerror = () => {
|
|
32
|
+
reject(
|
|
33
|
+
new Error(
|
|
34
|
+
`mhosaic-feedback: failed to load bundle from ${opts.bundleUrl} (network, CSP, or SRI mismatch)`
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
document.head.appendChild(script);
|
|
39
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
reject(
|
|
42
|
+
new Error(`mhosaic-feedback: bundle load timeout after ${timeoutMs}ms`)
|
|
43
|
+
);
|
|
44
|
+
}, timeoutMs);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function waitForGlobal(timeoutMs) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const start = Date.now();
|
|
50
|
+
const tick = () => {
|
|
51
|
+
if (window.MhosaicFeedback?.createFeedback) {
|
|
52
|
+
resolve(window.MhosaicFeedback);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (Date.now() - start > timeoutMs) {
|
|
56
|
+
reject(
|
|
57
|
+
new Error(
|
|
58
|
+
"mhosaic-feedback: timed out waiting for bundle global (another loader instance started the fetch but never finished)"
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
setTimeout(tick, 50);
|
|
64
|
+
};
|
|
65
|
+
tick();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/loader/manifest.ts
|
|
70
|
+
async function fetchManifest(endpoint, apiKey, signal) {
|
|
71
|
+
const base = endpoint.replace(/\/$/, "");
|
|
72
|
+
const url = `${base}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(apiKey)}`;
|
|
73
|
+
const init = { credentials: "omit" };
|
|
74
|
+
if (signal) init.signal = signal;
|
|
75
|
+
const res = await fetch(url, init);
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`mhosaic-feedback: manifest fetch failed (HTTP ${res.status})`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const body = await res.json();
|
|
82
|
+
return body;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/loader/deferredApi.ts
|
|
86
|
+
function createDeferredApi() {
|
|
87
|
+
let real = null;
|
|
88
|
+
let failure = null;
|
|
89
|
+
const queue = [];
|
|
90
|
+
function flush(item) {
|
|
91
|
+
if (real) {
|
|
92
|
+
if (item.kind === "void") {
|
|
93
|
+
;
|
|
94
|
+
real[item.method]();
|
|
95
|
+
} else if (item.kind === "arg") {
|
|
96
|
+
;
|
|
97
|
+
real[item.method](item.arg);
|
|
98
|
+
} else {
|
|
99
|
+
Promise.resolve(real.submit(item.payload)).then(
|
|
100
|
+
item.resolve,
|
|
101
|
+
item.reject
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (failure) {
|
|
107
|
+
if (item.kind === "submit") item.reject(failure);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
queue.push(item);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
show() {
|
|
114
|
+
flush({ kind: "void", method: "show" });
|
|
115
|
+
},
|
|
116
|
+
hide() {
|
|
117
|
+
flush({ kind: "void", method: "hide" });
|
|
118
|
+
},
|
|
119
|
+
shutdown() {
|
|
120
|
+
flush({ kind: "void", method: "shutdown" });
|
|
121
|
+
},
|
|
122
|
+
open(arg) {
|
|
123
|
+
flush({ kind: "arg", method: "open", arg });
|
|
124
|
+
},
|
|
125
|
+
identify(arg) {
|
|
126
|
+
flush({ kind: "arg", method: "identify", arg });
|
|
127
|
+
},
|
|
128
|
+
setMetadata(arg) {
|
|
129
|
+
flush({ kind: "arg", method: "setMetadata", arg });
|
|
130
|
+
},
|
|
131
|
+
submit(payload) {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
flush({ kind: "submit", payload, resolve, reject });
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
_attach(impl) {
|
|
137
|
+
real = impl;
|
|
138
|
+
while (queue.length > 0) flush(queue.shift());
|
|
139
|
+
},
|
|
140
|
+
_fail(err) {
|
|
141
|
+
failure = err;
|
|
142
|
+
for (const item of queue) {
|
|
143
|
+
if (item.kind === "submit") item.reject(err);
|
|
144
|
+
}
|
|
145
|
+
queue.length = 0;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/loader/index.ts
|
|
151
|
+
function createFeedback(config) {
|
|
152
|
+
const deferred = createDeferredApi();
|
|
153
|
+
loadAndAttach(config, deferred).catch((err) => {
|
|
154
|
+
console.warn("[mhosaic-feedback] widget did not load:", err.message);
|
|
155
|
+
deferred._fail(err);
|
|
156
|
+
});
|
|
157
|
+
return deferred;
|
|
158
|
+
}
|
|
159
|
+
async function loadAndAttach(config, deferred) {
|
|
160
|
+
const manifest = await fetchManifest(config.endpoint, config.apiKey);
|
|
161
|
+
if (!manifest.enabled || !manifest.bundle_url || !manifest.sri_hash) {
|
|
162
|
+
deferred._fail(
|
|
163
|
+
new Error(
|
|
164
|
+
`mhosaic-feedback: widget disabled for this project${manifest.detail ? ` \u2014 ${manifest.detail}` : ""}`
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const bundle = await injectBundle({
|
|
170
|
+
bundleUrl: manifest.bundle_url,
|
|
171
|
+
sriHash: manifest.sri_hash
|
|
172
|
+
});
|
|
173
|
+
const real = bundle.createFeedback(config);
|
|
174
|
+
deferred._attach(real);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export {
|
|
178
|
+
createFeedback
|
|
179
|
+
};
|
|
180
|
+
//# sourceMappingURL=chunk-6JDT2KWQ.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'\n\ndeclare global {\n interface Window {\n MhosaicFeedback?: {\n createFeedback(config: FeedbackConfig): 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: FeedbackConfig): 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 }\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}\n\nexport function createDeferredApi(): DeferredHandle {\n let real: FeedbackApi | null = null\n let failure: Error | null = null\n const queue: Queued[] = []\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 _attach(impl) {\n real = impl\n while (queue.length > 0) flush(queue.shift()!)\n },\n _fail(err) {\n failure = err\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 { 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\nexport function createFeedback(config: FeedbackConfig): 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: FeedbackConfig,\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(config)\n deferred._attach(real)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1794,29 +1794,39 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
|
|
|
1794
1794
|
}
|
|
1795
1795
|
|
|
1796
1796
|
// src/widget/Fab.tsx
|
|
1797
|
-
import { jsx as jsx6 } from "preact/jsx-runtime";
|
|
1798
|
-
function
|
|
1799
|
-
return /* @__PURE__ */
|
|
1797
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
1798
|
+
function BugIcon() {
|
|
1799
|
+
return /* @__PURE__ */ jsxs6(
|
|
1800
1800
|
"svg",
|
|
1801
1801
|
{
|
|
1802
|
-
width: "
|
|
1803
|
-
height: "
|
|
1802
|
+
width: "20",
|
|
1803
|
+
height: "20",
|
|
1804
1804
|
viewBox: "0 0 24 24",
|
|
1805
1805
|
fill: "none",
|
|
1806
|
+
stroke: "currentColor",
|
|
1807
|
+
"stroke-width": "2",
|
|
1808
|
+
"stroke-linecap": "round",
|
|
1809
|
+
"stroke-linejoin": "round",
|
|
1806
1810
|
"aria-hidden": "true",
|
|
1807
1811
|
focusable: "false",
|
|
1808
|
-
children:
|
|
1809
|
-
"path",
|
|
1810
|
-
{
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1812
|
+
children: [
|
|
1813
|
+
/* @__PURE__ */ jsx6("path", { d: "m8 2 1.88 1.88" }),
|
|
1814
|
+
/* @__PURE__ */ jsx6("path", { d: "M14.12 3.88 16 2" }),
|
|
1815
|
+
/* @__PURE__ */ jsx6("path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }),
|
|
1816
|
+
/* @__PURE__ */ jsx6("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }),
|
|
1817
|
+
/* @__PURE__ */ jsx6("path", { d: "M12 20v-9" }),
|
|
1818
|
+
/* @__PURE__ */ jsx6("path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }),
|
|
1819
|
+
/* @__PURE__ */ jsx6("path", { d: "M6 13H2" }),
|
|
1820
|
+
/* @__PURE__ */ jsx6("path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }),
|
|
1821
|
+
/* @__PURE__ */ jsx6("path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }),
|
|
1822
|
+
/* @__PURE__ */ jsx6("path", { d: "M22 13h-4" }),
|
|
1823
|
+
/* @__PURE__ */ jsx6("path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" })
|
|
1824
|
+
]
|
|
1815
1825
|
}
|
|
1816
1826
|
);
|
|
1817
1827
|
}
|
|
1818
1828
|
function Fab({ label, onClick }) {
|
|
1819
|
-
return /* @__PURE__ */ jsx6("button", { type: "button", class: "fab", "aria-label": label, title: label, onClick, children: /* @__PURE__ */ jsx6(
|
|
1829
|
+
return /* @__PURE__ */ jsx6("button", { type: "button", class: "fab", "aria-label": label, title: label, onClick, children: /* @__PURE__ */ jsx6(BugIcon, {}) });
|
|
1820
1830
|
}
|
|
1821
1831
|
|
|
1822
1832
|
// src/widget/Form.tsx
|
|
@@ -1824,7 +1834,7 @@ import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } fro
|
|
|
1824
1834
|
|
|
1825
1835
|
// src/widget/Annotator.tsx
|
|
1826
1836
|
import { useEffect as useEffect4, useLayoutEffect, useRef as useRef3, useState as useState4 } from "preact/hooks";
|
|
1827
|
-
import { jsx as jsx7, jsxs as
|
|
1837
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
|
|
1828
1838
|
var COLORS = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#ffffff"];
|
|
1829
1839
|
var HIGHLIGHT_ALPHA = 0.35;
|
|
1830
1840
|
function drawShape(ctx, shape, sourceImage) {
|
|
@@ -1950,11 +1960,11 @@ var Icon = {
|
|
|
1950
1960
|
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" }) }),
|
|
1951
1961
|
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" }) }),
|
|
1952
1962
|
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" }) }),
|
|
1953
|
-
highlight: /* @__PURE__ */
|
|
1963
|
+
highlight: /* @__PURE__ */ jsxs7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
|
|
1954
1964
|
/* @__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" }),
|
|
1955
1965
|
/* @__PURE__ */ jsx7("path", { d: "M2 14h12", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" })
|
|
1956
1966
|
] }),
|
|
1957
|
-
blur: /* @__PURE__ */
|
|
1967
|
+
blur: /* @__PURE__ */ jsxs7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
|
|
1958
1968
|
/* @__PURE__ */ jsx7("rect", { x: "2", y: "2", width: "4", height: "4", fill: "currentColor", opacity: "0.85" }),
|
|
1959
1969
|
/* @__PURE__ */ jsx7("rect", { x: "7", y: "2", width: "3", height: "3", fill: "currentColor", opacity: "0.55" }),
|
|
1960
1970
|
/* @__PURE__ */ jsx7("rect", { x: "11", y: "2", width: "3", height: "4", fill: "currentColor", opacity: "0.4" }),
|
|
@@ -2210,8 +2220,8 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2210
2220
|
onClick: (e) => {
|
|
2211
2221
|
if (e.target === e.currentTarget) onCancel();
|
|
2212
2222
|
},
|
|
2213
|
-
children: /* @__PURE__ */
|
|
2214
|
-
/* @__PURE__ */
|
|
2223
|
+
children: /* @__PURE__ */ jsxs7("div", { class: "annotator", role: "dialog", "aria-modal": "true", "aria-label": strings["annotator.title"], children: [
|
|
2224
|
+
/* @__PURE__ */ jsxs7("div", { class: "annotator-header", children: [
|
|
2215
2225
|
/* @__PURE__ */ jsx7("span", { children: strings["annotator.title"] }),
|
|
2216
2226
|
/* @__PURE__ */ jsx7(
|
|
2217
2227
|
"button",
|
|
@@ -2224,7 +2234,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2224
2234
|
}
|
|
2225
2235
|
)
|
|
2226
2236
|
] }),
|
|
2227
|
-
/* @__PURE__ */
|
|
2237
|
+
/* @__PURE__ */ jsxs7("div", { class: "annotator-toolbar", children: [
|
|
2228
2238
|
/* @__PURE__ */ jsx7("div", { class: "annotator-tools", children: tools.map((t) => /* @__PURE__ */ jsx7(
|
|
2229
2239
|
"button",
|
|
2230
2240
|
{
|
|
@@ -2239,7 +2249,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2239
2249
|
t.id
|
|
2240
2250
|
)) }),
|
|
2241
2251
|
/* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
|
|
2242
|
-
/* @__PURE__ */
|
|
2252
|
+
/* @__PURE__ */ jsxs7("div", { class: "annotator-colors", children: [
|
|
2243
2253
|
COLORS.map((c) => /* @__PURE__ */ jsx7(
|
|
2244
2254
|
"button",
|
|
2245
2255
|
{
|
|
@@ -2263,7 +2273,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2263
2273
|
) })
|
|
2264
2274
|
] }),
|
|
2265
2275
|
/* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
|
|
2266
|
-
/* @__PURE__ */
|
|
2276
|
+
/* @__PURE__ */ jsxs7(
|
|
2267
2277
|
"button",
|
|
2268
2278
|
{
|
|
2269
2279
|
type: "button",
|
|
@@ -2277,7 +2287,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2277
2287
|
]
|
|
2278
2288
|
}
|
|
2279
2289
|
),
|
|
2280
|
-
/* @__PURE__ */
|
|
2290
|
+
/* @__PURE__ */ jsxs7(
|
|
2281
2291
|
"button",
|
|
2282
2292
|
{
|
|
2283
2293
|
type: "button",
|
|
@@ -2291,7 +2301,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2291
2301
|
]
|
|
2292
2302
|
}
|
|
2293
2303
|
),
|
|
2294
|
-
/* @__PURE__ */
|
|
2304
|
+
/* @__PURE__ */ jsxs7(
|
|
2295
2305
|
"button",
|
|
2296
2306
|
{
|
|
2297
2307
|
type: "button",
|
|
@@ -2305,7 +2315,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2305
2315
|
}
|
|
2306
2316
|
),
|
|
2307
2317
|
/* @__PURE__ */ jsx7("span", { class: "annotator-spacer" }),
|
|
2308
|
-
/* @__PURE__ */
|
|
2318
|
+
/* @__PURE__ */ jsxs7("span", { class: "annotator-count", children: [
|
|
2309
2319
|
shapes.length,
|
|
2310
2320
|
" ",
|
|
2311
2321
|
strings["annotator.count_suffix"]
|
|
@@ -2322,7 +2332,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2322
2332
|
class: "annotator-canvas"
|
|
2323
2333
|
}
|
|
2324
2334
|
) }),
|
|
2325
|
-
/* @__PURE__ */
|
|
2335
|
+
/* @__PURE__ */ jsxs7("div", { class: "annotator-footer", children: [
|
|
2326
2336
|
/* @__PURE__ */ jsx7("button", { type: "button", class: "btn", onClick: onCancel, children: strings["form.cancel"] }),
|
|
2327
2337
|
/* @__PURE__ */ jsx7(
|
|
2328
2338
|
"button",
|
|
@@ -2341,7 +2351,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
|
|
|
2341
2351
|
}
|
|
2342
2352
|
|
|
2343
2353
|
// src/widget/Form.tsx
|
|
2344
|
-
import { jsx as jsx8, jsxs as
|
|
2354
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
|
|
2345
2355
|
var TYPES2 = ["bug", "feature", "question", "praise", "typo"];
|
|
2346
2356
|
var SEVERITIES2 = ["blocker", "high", "medium", "low"];
|
|
2347
2357
|
function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
@@ -2450,9 +2460,9 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2450
2460
|
}
|
|
2451
2461
|
onSubmit(values);
|
|
2452
2462
|
};
|
|
2453
|
-
return /* @__PURE__ */
|
|
2463
|
+
return /* @__PURE__ */ jsxs8("form", { onSubmit: handleSubmit, children: [
|
|
2454
2464
|
/* @__PURE__ */ jsx8("h2", { children: strings["form.title"] }),
|
|
2455
|
-
/* @__PURE__ */
|
|
2465
|
+
/* @__PURE__ */ jsxs8("div", { class: "field", children: [
|
|
2456
2466
|
/* @__PURE__ */ jsx8("label", { for: "mfb-desc", children: strings["form.description.label"] }),
|
|
2457
2467
|
/* @__PURE__ */ jsx8(
|
|
2458
2468
|
"textarea",
|
|
@@ -2464,8 +2474,8 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2464
2474
|
}
|
|
2465
2475
|
)
|
|
2466
2476
|
] }),
|
|
2467
|
-
/* @__PURE__ */
|
|
2468
|
-
/* @__PURE__ */
|
|
2477
|
+
/* @__PURE__ */ jsxs8("div", { class: "row", children: [
|
|
2478
|
+
/* @__PURE__ */ jsxs8("div", { class: "field", children: [
|
|
2469
2479
|
/* @__PURE__ */ jsx8("label", { for: "mfb-type", children: strings["form.type.label"] }),
|
|
2470
2480
|
/* @__PURE__ */ jsx8(
|
|
2471
2481
|
"select",
|
|
@@ -2477,7 +2487,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2477
2487
|
}
|
|
2478
2488
|
)
|
|
2479
2489
|
] }),
|
|
2480
|
-
/* @__PURE__ */
|
|
2490
|
+
/* @__PURE__ */ jsxs8("div", { class: "field", children: [
|
|
2481
2491
|
/* @__PURE__ */ jsx8("label", { for: "mfb-sev", children: strings["form.severity.label"] }),
|
|
2482
2492
|
/* @__PURE__ */ jsx8(
|
|
2483
2493
|
"select",
|
|
@@ -2490,7 +2500,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2490
2500
|
)
|
|
2491
2501
|
] })
|
|
2492
2502
|
] }),
|
|
2493
|
-
/* @__PURE__ */
|
|
2503
|
+
/* @__PURE__ */ jsxs8("div", { class: "field", children: [
|
|
2494
2504
|
/* @__PURE__ */ jsx8("label", { children: strings["form.screenshot.label"] }),
|
|
2495
2505
|
/* @__PURE__ */ jsx8(
|
|
2496
2506
|
"input",
|
|
@@ -2504,17 +2514,17 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2504
2514
|
tabIndex: -1
|
|
2505
2515
|
}
|
|
2506
2516
|
),
|
|
2507
|
-
screenshotPreview ? /* @__PURE__ */
|
|
2517
|
+
screenshotPreview ? /* @__PURE__ */ jsxs8("div", { class: "screenshot-preview", children: [
|
|
2508
2518
|
/* @__PURE__ */ jsx8("img", { src: screenshotPreview, alt: "" }),
|
|
2509
|
-
/* @__PURE__ */
|
|
2510
|
-
/* @__PURE__ */
|
|
2519
|
+
/* @__PURE__ */ jsxs8("div", { class: "screenshot-preview-actions", children: [
|
|
2520
|
+
/* @__PURE__ */ jsxs8(
|
|
2511
2521
|
"button",
|
|
2512
2522
|
{
|
|
2513
2523
|
type: "button",
|
|
2514
2524
|
class: "btn btn--primary screenshot-annotate",
|
|
2515
2525
|
onClick: () => setAnnotatorOpen(true),
|
|
2516
2526
|
children: [
|
|
2517
|
-
/* @__PURE__ */
|
|
2527
|
+
/* @__PURE__ */ jsxs8("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: [
|
|
2518
2528
|
/* @__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" }),
|
|
2519
2529
|
/* @__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" })
|
|
2520
2530
|
] }),
|
|
@@ -2522,12 +2532,12 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2522
2532
|
]
|
|
2523
2533
|
}
|
|
2524
2534
|
),
|
|
2525
|
-
/* @__PURE__ */
|
|
2535
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", class: "btn", onClick: clearScreenshot, children: [
|
|
2526
2536
|
/* @__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" }) }),
|
|
2527
2537
|
strings["form.screenshot.remove"]
|
|
2528
2538
|
] })
|
|
2529
2539
|
] })
|
|
2530
|
-
] }) : /* @__PURE__ */
|
|
2540
|
+
] }) : /* @__PURE__ */ jsxs8(
|
|
2531
2541
|
"div",
|
|
2532
2542
|
{
|
|
2533
2543
|
ref: dropZoneRef,
|
|
@@ -2546,12 +2556,12 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2546
2556
|
onDragLeave: handleDragLeave,
|
|
2547
2557
|
onDrop: handleDrop,
|
|
2548
2558
|
children: [
|
|
2549
|
-
/* @__PURE__ */
|
|
2559
|
+
/* @__PURE__ */ jsxs8("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: [
|
|
2550
2560
|
/* @__PURE__ */ jsx8("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
2551
2561
|
/* @__PURE__ */ jsx8("polyline", { points: "17 8 12 3 7 8" }),
|
|
2552
2562
|
/* @__PURE__ */ jsx8("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
2553
2563
|
] }),
|
|
2554
|
-
/* @__PURE__ */
|
|
2564
|
+
/* @__PURE__ */ jsxs8("div", { class: "screenshot-cta", children: [
|
|
2555
2565
|
/* @__PURE__ */ jsx8("strong", { children: strings["form.screenshot.cta_click"] }),
|
|
2556
2566
|
", ",
|
|
2557
2567
|
strings["form.screenshot.cta_rest"]
|
|
@@ -2561,14 +2571,14 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2561
2571
|
}
|
|
2562
2572
|
)
|
|
2563
2573
|
] }),
|
|
2564
|
-
pageUrl && /* @__PURE__ */
|
|
2574
|
+
pageUrl && /* @__PURE__ */ jsxs8("div", { class: "page-context", title: pageUrl, children: [
|
|
2565
2575
|
/* @__PURE__ */ jsx8("span", { class: "page-context-label", children: strings["form.context.label"] }),
|
|
2566
2576
|
/* @__PURE__ */ jsx8("span", { class: "page-context-url", children: truncateUrl(pageUrl, 90) })
|
|
2567
2577
|
] }),
|
|
2568
2578
|
localError && /* @__PURE__ */ jsx8("div", { class: "error", children: localError }),
|
|
2569
2579
|
status === "error" && errorMessage && /* @__PURE__ */ jsx8("div", { class: "error", children: errorMessage }),
|
|
2570
2580
|
status === "success" && /* @__PURE__ */ jsx8("div", { class: "success", children: strings["form.success"] }),
|
|
2571
|
-
/* @__PURE__ */
|
|
2581
|
+
/* @__PURE__ */ jsxs8("div", { class: "actions", children: [
|
|
2572
2582
|
/* @__PURE__ */ jsx8("button", { type: "button", class: "btn", onClick: onCancel, disabled: submitting, children: strings["form.cancel"] }),
|
|
2573
2583
|
/* @__PURE__ */ jsx8("button", { type: "submit", class: "btn btn--primary", disabled: submitting, children: submitLabel })
|
|
2574
2584
|
] }),
|
|
@@ -2588,7 +2598,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
2588
2598
|
import { useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "preact/hooks";
|
|
2589
2599
|
|
|
2590
2600
|
// src/widget/KpiStrip.tsx
|
|
2591
|
-
import { jsx as jsx9, jsxs as
|
|
2601
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
|
|
2592
2602
|
function computeKpiCounts(rows) {
|
|
2593
2603
|
const counts = {
|
|
2594
2604
|
new: 0,
|
|
@@ -2658,7 +2668,7 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
|
|
|
2658
2668
|
return /* @__PURE__ */ jsx9("div", { class: "kpi-strip", role: "toolbar", children: cells.map((c) => {
|
|
2659
2669
|
const active = filter === c.key;
|
|
2660
2670
|
const toggleTo = active ? "all" : c.key;
|
|
2661
|
-
return /* @__PURE__ */
|
|
2671
|
+
return /* @__PURE__ */ jsxs9(
|
|
2662
2672
|
"button",
|
|
2663
2673
|
{
|
|
2664
2674
|
type: "button",
|
|
@@ -2675,7 +2685,7 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
|
|
|
2675
2685
|
}
|
|
2676
2686
|
|
|
2677
2687
|
// src/widget/MineList.tsx
|
|
2678
|
-
import { jsx as jsx10, jsxs as
|
|
2688
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
|
|
2679
2689
|
var POLL_MS4 = 3e4;
|
|
2680
2690
|
function MineList({ api, externalId, strings, onSelect }) {
|
|
2681
2691
|
const [rows, setRows] = useState6(null);
|
|
@@ -2713,8 +2723,8 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
2713
2723
|
const isLoading = rows === null && !error;
|
|
2714
2724
|
const visibleRows = rows ? rowsMatchingFilter(rows, filter) : null;
|
|
2715
2725
|
const visibleEmpty = !!rows && rows.length > 0 && (visibleRows?.length ?? 0) === 0;
|
|
2716
|
-
return /* @__PURE__ */
|
|
2717
|
-
/* @__PURE__ */
|
|
2726
|
+
return /* @__PURE__ */ jsxs10("div", { class: "mine-list", children: [
|
|
2727
|
+
/* @__PURE__ */ jsxs10("div", { class: "mine-list-header", children: [
|
|
2718
2728
|
/* @__PURE__ */ jsx10("h2", { children: strings["tab.mine"] }),
|
|
2719
2729
|
/* @__PURE__ */ jsx10(
|
|
2720
2730
|
"button",
|
|
@@ -2732,7 +2742,7 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
2732
2742
|
rows && rows.length > 0 && /* @__PURE__ */ jsx10(KpiStrip, { rows, filter, onFilter: setFilter, strings }),
|
|
2733
2743
|
isLoading && /* @__PURE__ */ jsx10("div", { class: "mine-loading", children: strings["mine.loading"] }),
|
|
2734
2744
|
error && /* @__PURE__ */ jsx10("div", { class: "error", children: error }),
|
|
2735
|
-
isEmpty && /* @__PURE__ */
|
|
2745
|
+
isEmpty && /* @__PURE__ */ jsxs10("div", { class: "mine-empty", children: [
|
|
2736
2746
|
/* @__PURE__ */ jsx10("strong", { children: strings["mine.empty.title"] }),
|
|
2737
2747
|
/* @__PURE__ */ jsx10("p", { children: strings["mine.empty.body"] })
|
|
2738
2748
|
] }),
|
|
@@ -2743,7 +2753,7 @@ function MineList({ api, externalId, strings, onSelect }) {
|
|
|
2743
2753
|
|
|
2744
2754
|
// src/widget/Modal.tsx
|
|
2745
2755
|
import { useEffect as useEffect7, useRef as useRef6 } from "preact/hooks";
|
|
2746
|
-
import { jsx as jsx11, jsxs as
|
|
2756
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "preact/jsx-runtime";
|
|
2747
2757
|
function Modal({ onDismiss, children, closeLabel = "Close", expanded = false }) {
|
|
2748
2758
|
const modalRef = useRef6(null);
|
|
2749
2759
|
const previouslyFocused = useRef6(null);
|
|
@@ -2777,7 +2787,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
|
|
|
2777
2787
|
onClick: (e) => {
|
|
2778
2788
|
if (e.target === e.currentTarget) onDismiss();
|
|
2779
2789
|
},
|
|
2780
|
-
children: /* @__PURE__ */
|
|
2790
|
+
children: /* @__PURE__ */ jsxs11(
|
|
2781
2791
|
"div",
|
|
2782
2792
|
{
|
|
2783
2793
|
ref: modalRef,
|
|
@@ -2808,6 +2818,15 @@ var WIDGET_STYLES = `
|
|
|
2808
2818
|
:host {
|
|
2809
2819
|
--mfb-accent: #3b82f6;
|
|
2810
2820
|
--mfb-accent-contrast: #ffffff;
|
|
2821
|
+
|
|
2822
|
+
/* FAB-specific palette \u2014 Mhosaic brand (steel blue family, per
|
|
2823
|
+
mhosaic-core's design system v4). Soft-black surface + light
|
|
2824
|
+
steel-blue glyph. Kept separate from --mfb-accent on purpose:
|
|
2825
|
+
the accent is used everywhere inside the panel for submit
|
|
2826
|
+
buttons / focus rings, and we don't want changing the FAB chrome
|
|
2827
|
+
to inadvertently restyle the whole widget. */
|
|
2828
|
+
--mfb-fab-bg: #1c2230; /* Mhosaic --black (oklch \u2248 0.22 0.025 258) */
|
|
2829
|
+
--mfb-fab-icon: #8aa9e5; /* Mhosaic --blue-300 (oklch \u2248 0.70 0.13 258) */
|
|
2811
2830
|
--mfb-bg: #ffffff;
|
|
2812
2831
|
--mfb-surface: #f9fafb;
|
|
2813
2832
|
--mfb-surface-2: #f3f4f6;
|
|
@@ -2863,18 +2882,20 @@ var WIDGET_STYLES = `
|
|
|
2863
2882
|
}
|
|
2864
2883
|
}
|
|
2865
2884
|
|
|
2866
|
-
/* FAB \u2014
|
|
2867
|
-
|
|
2868
|
-
|
|
2885
|
+
/* FAB \u2014 48px circle (down from Material's 56px) for a more discrete
|
|
2886
|
+
presence on client surfaces (matches PNR's 48px pattern). Mhosaic
|
|
2887
|
+
soft-black background with a light steel-blue lucide-bug glyph;
|
|
2888
|
+
custom SVG inlined (no emoji \u2014 emoji renders inconsistently across
|
|
2889
|
+
OSes and can't inherit color). Two-layer elevation, scale-on-press. */
|
|
2869
2890
|
.fab {
|
|
2870
2891
|
position: fixed;
|
|
2871
2892
|
bottom: 24px;
|
|
2872
2893
|
right: 24px;
|
|
2873
|
-
width:
|
|
2874
|
-
height:
|
|
2894
|
+
width: 48px;
|
|
2895
|
+
height: 48px;
|
|
2875
2896
|
border-radius: 999px;
|
|
2876
|
-
background: var(--mfb-
|
|
2877
|
-
color: var(--mfb-
|
|
2897
|
+
background: var(--mfb-fab-bg);
|
|
2898
|
+
color: var(--mfb-fab-icon);
|
|
2878
2899
|
border: none;
|
|
2879
2900
|
cursor: pointer;
|
|
2880
2901
|
/* Two-layer elevation: ambient (soft, large) + key (tighter, near). */
|
|
@@ -2900,10 +2921,10 @@ var WIDGET_STYLES = `
|
|
|
2900
2921
|
0 1px 2px rgba(0, 0, 0, 0.14);
|
|
2901
2922
|
}
|
|
2902
2923
|
.fab:focus-visible {
|
|
2903
|
-
outline: 2px solid var(--mfb-
|
|
2924
|
+
outline: 2px solid var(--mfb-fab-icon);
|
|
2904
2925
|
outline-offset: 3px;
|
|
2905
2926
|
box-shadow:
|
|
2906
|
-
0 0 0 4px var(--mfb-
|
|
2927
|
+
0 0 0 4px var(--mfb-fab-bg),
|
|
2907
2928
|
0 4px 12px rgba(0, 0, 0, 0.08),
|
|
2908
2929
|
0 2px 4px rgba(0, 0, 0, 0.12);
|
|
2909
2930
|
}
|
|
@@ -4483,7 +4504,7 @@ var WIDGET_STYLES = `
|
|
|
4483
4504
|
`;
|
|
4484
4505
|
|
|
4485
4506
|
// src/widget/mount.tsx
|
|
4486
|
-
import { Fragment as Fragment3, jsx as jsx12, jsxs as
|
|
4507
|
+
import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
|
|
4487
4508
|
function mountWidget(options) {
|
|
4488
4509
|
const shadow = options.host.attachShadow({ mode: "open" });
|
|
4489
4510
|
const style = document.createElement("style");
|
|
@@ -4534,7 +4555,7 @@ function mountWidget(options) {
|
|
|
4534
4555
|
const externalId = options.getExternalId?.();
|
|
4535
4556
|
const fabVisible = options.showFAB && (options.getExternalId === void 0 ? true : Boolean(externalId));
|
|
4536
4557
|
const showMineTab = Boolean(options.api && externalId);
|
|
4537
|
-
return /* @__PURE__ */
|
|
4558
|
+
return /* @__PURE__ */ jsxs12(Fragment3, { children: [
|
|
4538
4559
|
fabVisible && /* @__PURE__ */ jsx12(
|
|
4539
4560
|
Fab,
|
|
4540
4561
|
{
|
|
@@ -4542,14 +4563,14 @@ function mountWidget(options) {
|
|
|
4542
4563
|
onClick: () => rerender({ ...currentState, open: true })
|
|
4543
4564
|
}
|
|
4544
4565
|
),
|
|
4545
|
-
state.open && /* @__PURE__ */
|
|
4566
|
+
state.open && /* @__PURE__ */ jsxs12(
|
|
4546
4567
|
Modal,
|
|
4547
4568
|
{
|
|
4548
4569
|
onDismiss: () => rerender(clearSelected({ ...currentState, open: false, status: "idle" })),
|
|
4549
4570
|
closeLabel: options.strings["form.close"],
|
|
4550
4571
|
expanded: state.tab === "board",
|
|
4551
4572
|
children: [
|
|
4552
|
-
showMineTab && /* @__PURE__ */
|
|
4573
|
+
showMineTab && /* @__PURE__ */ jsxs12("div", { class: "tab-strip", role: "tablist", children: [
|
|
4553
4574
|
/* @__PURE__ */ jsx12(
|
|
4554
4575
|
"button",
|
|
4555
4576
|
{
|
|
@@ -4731,8 +4752,8 @@ function createFeedback(config) {
|
|
|
4731
4752
|
capture_method: captureMethod,
|
|
4732
4753
|
technical_context
|
|
4733
4754
|
};
|
|
4734
|
-
if ("0.
|
|
4735
|
-
payload.widget_version = "0.
|
|
4755
|
+
if ("0.16.2") {
|
|
4756
|
+
payload.widget_version = "0.16.2";
|
|
4736
4757
|
}
|
|
4737
4758
|
if (manualScreenshot) payload.screenshot = manualScreenshot;
|
|
4738
4759
|
if (values.synthetic) payload.synthetic = true;
|
|
@@ -4818,4 +4839,4 @@ function createFeedback(config) {
|
|
|
4818
4839
|
export {
|
|
4819
4840
|
createFeedback
|
|
4820
4841
|
};
|
|
4821
|
-
//# sourceMappingURL=chunk-
|
|
4842
|
+
//# sourceMappingURL=chunk-I4GWTJ6Q.mjs.map
|