@mediansh/widget 0.1.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/README.md +86 -0
- package/dist/chunk-6E3NBXLE.js +30786 -0
- package/dist/chunk-6E3NBXLE.js.map +1 -0
- package/dist/highlighted-body-OFNGDK62-UN447WAU.js +28 -0
- package/dist/highlighted-body-OFNGDK62-UN447WAU.js.map +1 -0
- package/dist/index.d.ts +525 -0
- package/dist/index.js +13883 -0
- package/dist/index.js.map +1 -0
- package/dist/mermaid-GHXKKRXX-5WPKRFDI.js +9 -0
- package/dist/mermaid-GHXKKRXX-5WPKRFDI.js.map +1 -0
- package/dist/widget.css +2 -0
- package/package.json +48 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import {
|
|
4
|
+
At,
|
|
5
|
+
Li,
|
|
6
|
+
R
|
|
7
|
+
} from "./chunk-6E3NBXLE.js";
|
|
8
|
+
|
|
9
|
+
// ../../node_modules/.bun/streamdown@2.5.0+005eabf3d8b6ef06/node_modules/streamdown/dist/highlighted-body-OFNGDK62.js
|
|
10
|
+
import { useContext, useState, useEffect } from "react";
|
|
11
|
+
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
var R2 = ({ code: s, language: e, raw: t, className: h, startLine: d, lineNumbers: m, ...p }) => {
|
|
13
|
+
let { shikiTheme: l } = useContext(R), o = Li(), [a, i] = useState(t);
|
|
14
|
+
return useEffect(() => {
|
|
15
|
+
if (!o) {
|
|
16
|
+
i(t);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let r = o.highlight({ code: s, language: e, themes: l }, (c) => {
|
|
20
|
+
i(c);
|
|
21
|
+
});
|
|
22
|
+
r && i(r);
|
|
23
|
+
}, [s, e, l, o, t]), jsx(At, { className: h, language: e, lineNumbers: m, result: a, startLine: d, ...p });
|
|
24
|
+
};
|
|
25
|
+
export {
|
|
26
|
+
R2 as HighlightedCodeBlockBody
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=highlighted-body-OFNGDK62-UN447WAU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../node_modules/.bun/streamdown@2.5.0+005eabf3d8b6ef06/node_modules/streamdown/dist/highlighted-body-OFNGDK62.js"],"sourcesContent":["\"use client\";\nimport {A,f,c}from'./chunk-BO2N2NFS.js';import {useContext,useState,useEffect}from'react';import {jsx}from'react/jsx-runtime';var R=({code:s,language:e,raw:t,className:h,startLine:d,lineNumbers:m,...p})=>{let{shikiTheme:l}=useContext(A),o=f(),[a,i]=useState(t);return useEffect(()=>{if(!o){i(t);return}let r=o.highlight({code:s,language:e,themes:l},c=>{i(c);});r&&i(r);},[s,e,l,o,t]),jsx(c,{className:h,language:e,lineNumbers:m,result:a,startLine:d,...p})};export{R as HighlightedCodeBlockBody};"],"mappings":";;;;;;;;;AACwC,SAAQ,YAAW,UAAS,iBAAc;AAAQ,SAAQ,WAAQ;AAAoB,IAAIA,KAAE,CAAC,EAAC,MAAK,GAAE,UAAS,GAAE,KAAI,GAAE,WAAU,GAAE,WAAU,GAAE,aAAY,GAAE,GAAG,EAAC,MAAI;AAAC,MAAG,EAAC,YAAW,EAAC,IAAE,WAAW,CAAC,GAAE,IAAE,GAAE,GAAE,CAAC,GAAE,CAAC,IAAE,SAAS,CAAC;AAAE,SAAO,UAAU,MAAI;AAAC,QAAG,CAAC,GAAE;AAAC,QAAE,CAAC;AAAE;AAAA,IAAM;AAAC,QAAI,IAAE,EAAE,UAAU,EAAC,MAAK,GAAE,UAAS,GAAE,QAAO,EAAC,GAAE,OAAG;AAAC,QAAE,CAAC;AAAA,IAAE,CAAC;AAAE,SAAG,EAAE,CAAC;AAAA,EAAE,GAAE,CAAC,GAAE,GAAE,GAAE,GAAE,CAAC,CAAC,GAAE,IAAI,IAAE,EAAC,WAAU,GAAE,UAAS,GAAE,aAAY,GAAE,QAAO,GAAE,WAAU,GAAE,GAAG,EAAC,CAAC;AAAC;","names":["R"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The customer facing side of Median. These types are the shape the SDK will
|
|
6
|
+
* hand the widget, so they stay free of anything Convex or Clerk specific: a
|
|
7
|
+
* host app should be able to build one of these from its own data.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What is on the page while the panel is closed.
|
|
12
|
+
*
|
|
13
|
+
* `bubble` is the button in the corner, and is what a widget is. `hidden`
|
|
14
|
+
* leaves the page alone entirely, for a host who opens support from their own
|
|
15
|
+
* nav, their own button, or their own keyboard shortcut. Either way the panel
|
|
16
|
+
* is one call away, and comes up out of the same corner.
|
|
17
|
+
*/
|
|
18
|
+
type SupportLauncherKind = "bubble" | "hidden";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What has been going wrong on the page, kept ready in case somebody asks
|
|
22
|
+
* for help about it.
|
|
23
|
+
*
|
|
24
|
+
* A support agent's first three questions are always the same: what were you
|
|
25
|
+
* doing, what did it say, and what does the console show. The customer can
|
|
26
|
+
* answer the first one. This answers the other two, so a bug report arrives
|
|
27
|
+
* with the error already attached rather than after four messages of asking.
|
|
28
|
+
*
|
|
29
|
+
* Off unless a host switches it on. Everything here reads their program's
|
|
30
|
+
* error stream, which is theirs to offer rather than ours to take.
|
|
31
|
+
*
|
|
32
|
+
* A bounded ring buffer and nothing else. Nothing is sent on its own, nothing
|
|
33
|
+
* is stored, and nothing is polled: the newest snapshot rides the next message
|
|
34
|
+
* the customer sends, and only when there is something new in it.
|
|
35
|
+
*/
|
|
36
|
+
/** What a host can switch on. `true` is the passive half. */
|
|
37
|
+
type MedianDiagnostics = boolean | {
|
|
38
|
+
/** Uncaught errors and rejected promises. On whenever diagnostics are. */
|
|
39
|
+
errors?: boolean;
|
|
40
|
+
/** Requests that failed or came back unhappy. Wraps window.fetch. */
|
|
41
|
+
network?: boolean;
|
|
42
|
+
/** console.error calls. Wraps console.error. */
|
|
43
|
+
console?: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** What a host can tell us about who is in the widget. All of it optional. */
|
|
47
|
+
type MedianUser = {
|
|
48
|
+
/**
|
|
49
|
+
* Your own id for this person, and that id signed from MEDIAN_KEY.
|
|
50
|
+
*
|
|
51
|
+
* The pair is what makes a conversation follow somebody rather than a
|
|
52
|
+
* browser: sign in on a phone and the thread from the laptop is already
|
|
53
|
+
* there. Both or neither, and the signing belongs on your server, because
|
|
54
|
+
* whoever holds the key can claim to be any of your customers.
|
|
55
|
+
*
|
|
56
|
+
* Without them the widget still works. It is just this browser's thread,
|
|
57
|
+
* which is what it has always been.
|
|
58
|
+
*/
|
|
59
|
+
id?: string;
|
|
60
|
+
hash?: string;
|
|
61
|
+
name?: string;
|
|
62
|
+
email?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Their profile picture, so the team sees who they are talking to rather
|
|
65
|
+
* than a letter. Has to be an https URL; anything else is ignored.
|
|
66
|
+
*/
|
|
67
|
+
avatarUrl?: string;
|
|
68
|
+
/** Anything else worth showing the team: plan, company, whatever. */
|
|
69
|
+
metadata?: Record<string, string>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type MedianSupportProps = {
|
|
73
|
+
apiKey: string;
|
|
74
|
+
/** Who is signed in on the host's site, if anyone. Shown to the team. */
|
|
75
|
+
user?: MedianUser;
|
|
76
|
+
/**
|
|
77
|
+
* Whether a message carries what the browser already knows: time zone,
|
|
78
|
+
* language, browser, OS, device, screen, and the page it was sent from.
|
|
79
|
+
* On by default, because a team that has to ask for all six spends its
|
|
80
|
+
* first reply asking. Pass `false` and none of it is read at all.
|
|
81
|
+
*/
|
|
82
|
+
telemetry?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Take questions from signed in people only.
|
|
85
|
+
*
|
|
86
|
+
* Until `user` says somebody is there, the panel asks them to sign in
|
|
87
|
+
* instead of offering a composer, and no conversation is opened or read.
|
|
88
|
+
* Pass `user` the moment you have it and the thread takes over.
|
|
89
|
+
*
|
|
90
|
+
* A rule about your own page, kept on your own page: it decides what this
|
|
91
|
+
* widget offers, not what the API accepts. `user.id` with a signed `hash`
|
|
92
|
+
* is the part nobody can talk their way around.
|
|
93
|
+
*/
|
|
94
|
+
requireSignIn?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Where your sign in lives, for the button on that panel.
|
|
97
|
+
*
|
|
98
|
+
* <MedianSupport
|
|
99
|
+
* apiKey="median_pk_..."
|
|
100
|
+
* requireSignIn
|
|
101
|
+
* onSignIn={() => router.push("/login")}
|
|
102
|
+
* />
|
|
103
|
+
*
|
|
104
|
+
* Without it the panel says the same thing and offers no button, which is
|
|
105
|
+
* right for a site whose sign in is already the first thing on the page.
|
|
106
|
+
*/
|
|
107
|
+
onSignIn?: () => void;
|
|
108
|
+
/**
|
|
109
|
+
* Ask for an email before a visitor's first message. On by default: an
|
|
110
|
+
* anonymous thread cannot be followed up, and half of support is getting
|
|
111
|
+
* back to somebody. Pass `false` and the composer opens straight away.
|
|
112
|
+
*
|
|
113
|
+
* The composer asks once per browser; the answer rides their first message
|
|
114
|
+
* and lands with their details on your side. A visitor your `user` already
|
|
115
|
+
* names an email for is never asked, and `medianSupport.reset()` asks the
|
|
116
|
+
* next person instead.
|
|
117
|
+
*
|
|
118
|
+
* Your team can turn the question off from the dashboard without a
|
|
119
|
+
* redeploy, and either side saying no is enough to keep it away.
|
|
120
|
+
*/
|
|
121
|
+
requireEmail?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Watch what goes wrong on the page, so a bug report arrives with the
|
|
124
|
+
* error already attached instead of after four messages of asking.
|
|
125
|
+
*
|
|
126
|
+
* <MedianSupport apiKey="median_pk_..." diagnostics />
|
|
127
|
+
*
|
|
128
|
+
* `true` listens for uncaught errors and rejected promises, which changes
|
|
129
|
+
* nothing about how your page runs. The object form adds the two that wrap
|
|
130
|
+
* something of yours:
|
|
131
|
+
*
|
|
132
|
+
* diagnostics={{ network: true, console: true }}
|
|
133
|
+
*
|
|
134
|
+
* `network` wraps window.fetch to note requests that failed, `console`
|
|
135
|
+
* wraps console.error. Both call through to what they replaced, and both
|
|
136
|
+
* are put back when the widget leaves the page.
|
|
137
|
+
*
|
|
138
|
+
* Off by default: this reads your program's error stream, which is yours
|
|
139
|
+
* to offer rather than ours to take. Nothing is sent on its own. The
|
|
140
|
+
* newest snapshot rides the next message the customer sends, and only when
|
|
141
|
+
* something new is in it.
|
|
142
|
+
*/
|
|
143
|
+
diagnostics?: MedianDiagnostics;
|
|
144
|
+
/**
|
|
145
|
+
* Anything about your own app worth having next to a bug report: a build,
|
|
146
|
+
* a route, a flag. Read when a message is sent, so it can change as they
|
|
147
|
+
* move around.
|
|
148
|
+
*
|
|
149
|
+
* <MedianSupport
|
|
150
|
+
* apiKey="median_pk_..."
|
|
151
|
+
* diagnostics
|
|
152
|
+
* appState={{ version: BUILD_SHA, plan: user.plan }}
|
|
153
|
+
* />
|
|
154
|
+
*
|
|
155
|
+
* Twenty entries at most, and each one a line rather than a document.
|
|
156
|
+
*/
|
|
157
|
+
appState?: Record<string, string | number | boolean>;
|
|
158
|
+
/** Our button in the corner while it is closed, or nothing at all. */
|
|
159
|
+
launcher?: SupportLauncherKind;
|
|
160
|
+
/** Pass it, with `onOpenChange`, to hold the open state yourself. */
|
|
161
|
+
open?: boolean;
|
|
162
|
+
/** Every open and close, whoever asked for it. */
|
|
163
|
+
onOpenChange?: (open: boolean) => void;
|
|
164
|
+
/**
|
|
165
|
+
* Take the visitor to a page, when one of your own tools asks for it.
|
|
166
|
+
*
|
|
167
|
+
* <MedianSupport apiKey="median_pk_..." onNavigate={router.push} />
|
|
168
|
+
*
|
|
169
|
+
* Called with a path on your own site, already checked against this page's
|
|
170
|
+
* origin. Your router decides what happens: pass `router.push` and the
|
|
171
|
+
* conversation stays open across the move.
|
|
172
|
+
*
|
|
173
|
+
* Without it a tool that asks to navigate cannot, and says so in the
|
|
174
|
+
* console while you are building. Nothing else needs it.
|
|
175
|
+
*/
|
|
176
|
+
onNavigate?: (path: string) => void;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* The whole SDK, as one component. Drop it on a page with your publishable API
|
|
180
|
+
* key and the launcher, the thread, the AI and the inbox on the other side are
|
|
181
|
+
* all wired up:
|
|
182
|
+
*
|
|
183
|
+
* <MedianSupport apiKey="median_pk_..." />
|
|
184
|
+
*
|
|
185
|
+
* Optionally say who is signed in, and the team sees a person rather than
|
|
186
|
+
* another anonymous visitor:
|
|
187
|
+
*
|
|
188
|
+
* <MedianSupport
|
|
189
|
+
* apiKey="median_pk_..."
|
|
190
|
+
* user={{ name: "Ada", email: "ada@example.com" }}
|
|
191
|
+
* />
|
|
192
|
+
*
|
|
193
|
+
* There are three states rather than two: open, closed as a button in the
|
|
194
|
+
* corner, and not on the page at all. Take the corner back and open it from
|
|
195
|
+
* your own nav, from anywhere in your app, with no state to thread through:
|
|
196
|
+
*
|
|
197
|
+
* <MedianSupport apiKey="median_pk_..." launcher="hidden" />
|
|
198
|
+
*
|
|
199
|
+
* const support = useMedianSupport();
|
|
200
|
+
* <button onClick={support.open}>Contact us</button>
|
|
201
|
+
*
|
|
202
|
+
* On a site nobody uses signed out, say so and the panel asks them to sign in
|
|
203
|
+
* rather than opening a conversation with a stranger:
|
|
204
|
+
*
|
|
205
|
+
* <MedianSupport
|
|
206
|
+
* apiKey="median_pk_..."
|
|
207
|
+
* user={session?.user}
|
|
208
|
+
* requireSignIn
|
|
209
|
+
* onSignIn={() => router.push("/login")}
|
|
210
|
+
* />
|
|
211
|
+
*
|
|
212
|
+
* When somebody signs out, `medianSupport.reset()` forgets the conversation
|
|
213
|
+
* along with them, so the next person at the keyboard starts fresh.
|
|
214
|
+
*
|
|
215
|
+
* That is the surface, on purpose. Everything else a support widget could be
|
|
216
|
+
* asked to do is either our decision or a setting on the dashboard, so it can
|
|
217
|
+
* change without every host having to redeploy.
|
|
218
|
+
*
|
|
219
|
+
* It brings its own connection, so it works on pages (and sites) that have
|
|
220
|
+
* never heard of Convex.
|
|
221
|
+
*/
|
|
222
|
+
declare function MedianSupport(props: MedianSupportProps): react.JSX.Element;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* What a mounted widget hands back: the two things a host without React has
|
|
226
|
+
* no re-render to do for them. Everything else goes through `medianSupport`,
|
|
227
|
+
* same as anywhere.
|
|
228
|
+
*/
|
|
229
|
+
type MedianSupportInstance = {
|
|
230
|
+
/**
|
|
231
|
+
* Change any option after mount. Only the keys passed change, so
|
|
232
|
+
* `update({ user })` when somebody signs in leaves the rest alone.
|
|
233
|
+
*/
|
|
234
|
+
update: (options: Partial<MedianSupportProps>) => void;
|
|
235
|
+
/** Take the widget off the page: panel, launcher, connection, all of it. */
|
|
236
|
+
unmount: () => void;
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* The same widget, for a page that has never heard of React: Vue, Svelte,
|
|
240
|
+
* Rails, a script on plain HTML. One call puts the launcher in the corner,
|
|
241
|
+
* wired to the same panel, thread and inbox as the component:
|
|
242
|
+
*
|
|
243
|
+
* const support = mountMedianSupport({ apiKey: "median_pk_..." });
|
|
244
|
+
*
|
|
245
|
+
* It takes the component's props, as one options object, and the rest of the
|
|
246
|
+
* SDK does not know the difference: `medianSupport.open()` opens this widget
|
|
247
|
+
* exactly as it opens the component.
|
|
248
|
+
*
|
|
249
|
+
* What would have been new props on a re-render is `update`:
|
|
250
|
+
*
|
|
251
|
+
* support.update({ user: { name: "Ada", email: "ada@example.com" } });
|
|
252
|
+
*
|
|
253
|
+
* It renders into a container of its own, appended to `<body>`, with the
|
|
254
|
+
* package's copy of React. The host's page needs none and never sees it.
|
|
255
|
+
*/
|
|
256
|
+
declare function mountMedianSupport(options: MedianSupportProps): MedianSupportInstance;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Filing the crash the visitor is looking at.
|
|
260
|
+
*
|
|
261
|
+
* Every other send in the SDK starts with somebody typing. This one starts
|
|
262
|
+
* with a page that has stopped working, which is the report nobody writes:
|
|
263
|
+
* whoever was using it can see that it broke and has nothing to add about
|
|
264
|
+
* why. So the error describes itself, the browser hands over what it was
|
|
265
|
+
* doing, and one press files it.
|
|
266
|
+
*
|
|
267
|
+
* It brings its own connection and reads its key from the last widget that
|
|
268
|
+
* mounted, because by the time this runs the tree that widget lived in has
|
|
269
|
+
* usually been replaced by the error screen calling it. Nothing here needs a
|
|
270
|
+
* component, a provider, or a panel on the page.
|
|
271
|
+
*
|
|
272
|
+
* It never throws. A crash screen that crashes reporting a crash is a poor
|
|
273
|
+
* apology, so everything that can go wrong comes back as a reason instead.
|
|
274
|
+
*/
|
|
275
|
+
type MedianErrorReportOptions = {
|
|
276
|
+
/**
|
|
277
|
+
* The key, when no widget has mounted on this page to remember it. Both
|
|
278
|
+
* panels write theirs down as they mount, so an app with either one
|
|
279
|
+
* installed can leave this alone.
|
|
280
|
+
*/
|
|
281
|
+
apiKey?: string;
|
|
282
|
+
/** Who is in the chair. Defaults to whoever the mounted widget was given. */
|
|
283
|
+
user?: MedianUser;
|
|
284
|
+
/**
|
|
285
|
+
* Where it happened, when your router names the page better than its URL
|
|
286
|
+
* does: "/orders/[id]" rather than "/orders/8912". It leads the title, so
|
|
287
|
+
* the same crash on the same route is one row on the list however many
|
|
288
|
+
* people hit it. Defaults to the path in the address bar.
|
|
289
|
+
*/
|
|
290
|
+
route?: string;
|
|
291
|
+
/** React's component stack, when your boundary was handed one. */
|
|
292
|
+
componentStack?: string;
|
|
293
|
+
/** Facts to put on the report, the same shape as `appState`. */
|
|
294
|
+
state?: Record<string, string | number | boolean>;
|
|
295
|
+
/** Blocks to attach, the same shape and limits as `attach`. */
|
|
296
|
+
attach?: Record<string, unknown>;
|
|
297
|
+
/** Whether the report carries what the browser knows. On by default. */
|
|
298
|
+
telemetry?: boolean;
|
|
299
|
+
};
|
|
300
|
+
/** Where the report went, or why it did not go. */
|
|
301
|
+
type MedianErrorReportOutcome = {
|
|
302
|
+
reported: true;
|
|
303
|
+
/** True when somebody had already reported this one. */
|
|
304
|
+
joined: boolean;
|
|
305
|
+
} | {
|
|
306
|
+
reported: false;
|
|
307
|
+
reason: string;
|
|
308
|
+
};
|
|
309
|
+
declare function reportError(error: unknown, options?: MedianErrorReportOptions): Promise<MedianErrorReportOutcome>;
|
|
310
|
+
/**
|
|
311
|
+
* Whether a report has anywhere to go: a widget has mounted on this page and
|
|
312
|
+
* left its key, and this build knows which Median to send to.
|
|
313
|
+
*
|
|
314
|
+
* For a "Report error" button, which should not be on the screen at all if
|
|
315
|
+
* pressing it could only ever fail. It follows the install rather than
|
|
316
|
+
* reading it once, since an error screen can render before the widget does.
|
|
317
|
+
*/
|
|
318
|
+
declare function useCanReportError(): boolean;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The widget from anywhere, React or not: the same shape the hook hands
|
|
322
|
+
* back, for a plain `onclick` or a shortcut listener that never sees a
|
|
323
|
+
* component. `isOpen` reads live but does not re-render anything; the hook
|
|
324
|
+
* is for that.
|
|
325
|
+
*/
|
|
326
|
+
declare const medianSupport: {
|
|
327
|
+
/** What the panel is doing right now. */
|
|
328
|
+
readonly isOpen: boolean;
|
|
329
|
+
open(): void;
|
|
330
|
+
close(): void;
|
|
331
|
+
toggle(): void;
|
|
332
|
+
/**
|
|
333
|
+
* Put your own data on the next bug report: a crash trace from your error
|
|
334
|
+
* handler, a request id, the state of a store. Objects are written out as
|
|
335
|
+
* JSON. Entries ride the visitor's next message and land on the report,
|
|
336
|
+
* whether or not diagnostics are on.
|
|
337
|
+
*/
|
|
338
|
+
attach(label: string, value: unknown): void;
|
|
339
|
+
/**
|
|
340
|
+
* File the crash your error screen is showing, with everything the browser
|
|
341
|
+
* was doing when it happened. One press from a "Report error" button:
|
|
342
|
+
*
|
|
343
|
+
* const outcome = await medianSupport.reportError(error);
|
|
344
|
+
*
|
|
345
|
+
* It answers rather than throws, so the screen can say whether it landed.
|
|
346
|
+
* It works from a boundary with no widget left under it, which is most of
|
|
347
|
+
* them.
|
|
348
|
+
*/
|
|
349
|
+
reportError(error: unknown, options?: MedianErrorReportOptions): Promise<MedianErrorReportOutcome>;
|
|
350
|
+
/**
|
|
351
|
+
* Forget this browser's conversation, so the next one starts fresh and
|
|
352
|
+
* anonymous. Call it when your user signs out: whoever sits down next
|
|
353
|
+
* should not find somebody else's thread waiting.
|
|
354
|
+
*/
|
|
355
|
+
reset(): void;
|
|
356
|
+
};
|
|
357
|
+
/** The shape of {@link medianSupport}, and of what the hook returns. */
|
|
358
|
+
type MedianSupportControl = {
|
|
359
|
+
/** Whether the panel is open. */
|
|
360
|
+
isOpen: boolean;
|
|
361
|
+
open: () => void;
|
|
362
|
+
close: () => void;
|
|
363
|
+
toggle: () => void;
|
|
364
|
+
/** Put your own data on the next bug report: a crash trace, an id. */
|
|
365
|
+
attach: (label: string, value: unknown) => void;
|
|
366
|
+
/** File the crash your error screen is showing. For a report button. */
|
|
367
|
+
reportError: (error: unknown, options?: MedianErrorReportOptions) => Promise<MedianErrorReportOutcome>;
|
|
368
|
+
/** Forget this browser's conversation. For sign out. */
|
|
369
|
+
reset: () => void;
|
|
370
|
+
};
|
|
371
|
+
/**
|
|
372
|
+
* The widget, from anywhere else on the page:
|
|
373
|
+
*
|
|
374
|
+
* const support = useMedianSupport();
|
|
375
|
+
*
|
|
376
|
+
* <button onClick={support.open}>Contact us</button>
|
|
377
|
+
*
|
|
378
|
+
* `isOpen` follows the panel however it was opened, so a trigger can render
|
|
379
|
+
* itself as pressed, or step out of the way while the panel is up.
|
|
380
|
+
*/
|
|
381
|
+
declare function useMedianSupport(): MedianSupportControl;
|
|
382
|
+
|
|
383
|
+
type MedianFeedbackProps = {
|
|
384
|
+
apiKey: string;
|
|
385
|
+
/**
|
|
386
|
+
* Your own trigger. Whatever you pass is rendered where you put it and the
|
|
387
|
+
* panel hangs off it, the way a dropdown hangs off the thing that opens
|
|
388
|
+
* it:
|
|
389
|
+
*
|
|
390
|
+
* <MedianFeedback apiKey="median_pk_...">
|
|
391
|
+
* <button>Share feedback</button>
|
|
392
|
+
* </MedianFeedback>
|
|
393
|
+
*
|
|
394
|
+
* It is wrapped in an inline span that takes the click, so your button
|
|
395
|
+
* keeps its own styles and needs to know nothing about any of this.
|
|
396
|
+
*
|
|
397
|
+
* Without children the panel sits in the bottom right corner instead, for
|
|
398
|
+
* a host opening it from somewhere else entirely with
|
|
399
|
+
* `useMedianFeedback()`.
|
|
400
|
+
*/
|
|
401
|
+
children?: ReactNode;
|
|
402
|
+
/**
|
|
403
|
+
* Something already on your page for the panel to hang off, as a CSS
|
|
404
|
+
* selector. For the case children cannot cover: a row inside a dropdown,
|
|
405
|
+
* which closes itself opening this and takes the trigger with it.
|
|
406
|
+
*
|
|
407
|
+
* <MedianFeedback apiKey="median_pk_..." anchor="#account-button" />
|
|
408
|
+
*
|
|
409
|
+
* Looked up each time the panel is placed, so it works for anything that
|
|
410
|
+
* comes and goes. Unlike a trigger it is only a position: pressing it does
|
|
411
|
+
* not open or close the panel, and a press on it dismisses the panel like
|
|
412
|
+
* a press anywhere else. Ignored when `children` are passed.
|
|
413
|
+
*/
|
|
414
|
+
anchor?: string;
|
|
415
|
+
/** Who is signed in on your site, if anyone. Shown to your team. */
|
|
416
|
+
user?: MedianUser;
|
|
417
|
+
/**
|
|
418
|
+
* Whether a note carries what the browser already knows: time zone,
|
|
419
|
+
* language, browser, OS, device, screen, and the page it was written on.
|
|
420
|
+
* On by default, because the page is most of what makes a note actionable,
|
|
421
|
+
* which is what the panel says out loud. Pass `false` and none of it is
|
|
422
|
+
* read at all.
|
|
423
|
+
*/
|
|
424
|
+
telemetry?: boolean;
|
|
425
|
+
/**
|
|
426
|
+
* Watch what goes wrong on the page, so a bug filed from a note arrives
|
|
427
|
+
* with the error already attached. The same switch `<MedianSupport>`
|
|
428
|
+
* takes, and the same buffer behind it: mount either one with it on.
|
|
429
|
+
*/
|
|
430
|
+
diagnostics?: MedianDiagnostics;
|
|
431
|
+
/**
|
|
432
|
+
* Anything about your own app worth having next to a bug report: a build,
|
|
433
|
+
* a route, a flag. Read when a note is sent.
|
|
434
|
+
*/
|
|
435
|
+
appState?: Record<string, string | number | boolean>;
|
|
436
|
+
/** Pass it, with `onOpenChange`, to hold the open state yourself. */
|
|
437
|
+
open?: boolean;
|
|
438
|
+
onOpenChange?: (open: boolean) => void;
|
|
439
|
+
/** Any CSS length. The panel never grows past the viewport whatever this says. */
|
|
440
|
+
width?: string;
|
|
441
|
+
/** The heading, for a host whose product calls this something else. */
|
|
442
|
+
title?: string;
|
|
443
|
+
/** The line under it. */
|
|
444
|
+
description?: string;
|
|
445
|
+
placeholder?: string;
|
|
446
|
+
};
|
|
447
|
+
/**
|
|
448
|
+
* A feedback panel, hung off a trigger of your own.
|
|
449
|
+
*
|
|
450
|
+
* One box and one button. Whoever is on the page types a sentence, and a
|
|
451
|
+
* fast model reads it: a bug goes on the bug list, an idea goes on the
|
|
452
|
+
* suggestions list, and an advert goes nowhere. Nobody has to pick a
|
|
453
|
+
* category, rank anything, or write a title, which is the point, because a
|
|
454
|
+
* form that asks for those is a form nobody fills in.
|
|
455
|
+
*
|
|
456
|
+
* <MedianFeedback apiKey="median_pk_...">
|
|
457
|
+
* <MenuItem>Share feedback</MenuItem>
|
|
458
|
+
* </MedianFeedback>
|
|
459
|
+
*
|
|
460
|
+
* The page they were on rides along on its own, which the line under the
|
|
461
|
+
* title says plainly.
|
|
462
|
+
*
|
|
463
|
+
* It brings its own connection, so it works on pages that have never heard
|
|
464
|
+
* of Convex, and it is independent of `<MedianSupport>`: mount either, both,
|
|
465
|
+
* or neither. Mounted together they share a visitor, so the same person
|
|
466
|
+
* writing in and leaving a note is one customer with both to their name.
|
|
467
|
+
*/
|
|
468
|
+
declare function MedianFeedback(props: MedianFeedbackProps): react.JSX.Element;
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* What a mounted panel hands back: the two things a host without React has
|
|
472
|
+
* no re-render to do for them. Opening and closing go through
|
|
473
|
+
* `medianFeedback`, same as anywhere.
|
|
474
|
+
*/
|
|
475
|
+
type MedianFeedbackInstance = {
|
|
476
|
+
/**
|
|
477
|
+
* Change any option after mount. Only the keys passed change, so
|
|
478
|
+
* `update({ user })` when somebody signs in leaves the rest alone.
|
|
479
|
+
*/
|
|
480
|
+
update: (options: Partial<MedianFeedbackProps>) => void;
|
|
481
|
+
/** Take the panel off the page. */
|
|
482
|
+
unmount: () => void;
|
|
483
|
+
};
|
|
484
|
+
/**
|
|
485
|
+
* The same panel, for a page that has never heard of React: Vue, Svelte,
|
|
486
|
+
* Rails, a script on plain HTML.
|
|
487
|
+
*
|
|
488
|
+
* const feedback = mountMedianFeedback({ apiKey: "median_pk_..." });
|
|
489
|
+
*
|
|
490
|
+
* document.querySelector("#feedback").onclick = () => medianFeedback.open();
|
|
491
|
+
*
|
|
492
|
+
* There is no trigger to wrap out here, so the panel opens in the bottom
|
|
493
|
+
* right corner and your own button says when. Everything else is the
|
|
494
|
+
* component's, unchanged.
|
|
495
|
+
*/
|
|
496
|
+
declare function mountMedianFeedback(options: Omit<MedianFeedbackProps, "children">): MedianFeedbackInstance;
|
|
497
|
+
|
|
498
|
+
declare const medianFeedback: {
|
|
499
|
+
/** What the panel is doing right now. */
|
|
500
|
+
readonly isOpen: boolean;
|
|
501
|
+
open(): void;
|
|
502
|
+
close(): void;
|
|
503
|
+
toggle(): void;
|
|
504
|
+
};
|
|
505
|
+
/** The shape of {@link medianFeedback}, and of what the hook returns. */
|
|
506
|
+
type MedianFeedbackControl = {
|
|
507
|
+
/** Whether the panel is open. */
|
|
508
|
+
isOpen: boolean;
|
|
509
|
+
open: () => void;
|
|
510
|
+
close: () => void;
|
|
511
|
+
toggle: () => void;
|
|
512
|
+
};
|
|
513
|
+
/**
|
|
514
|
+
* The feedback panel, from anywhere else on the page:
|
|
515
|
+
*
|
|
516
|
+
* const feedback = useMedianFeedback();
|
|
517
|
+
*
|
|
518
|
+
* <button onClick={feedback.open}>Share feedback</button>
|
|
519
|
+
*
|
|
520
|
+
* `isOpen` follows the panel however it was opened, so a menu row can render
|
|
521
|
+
* itself as pressed while it is up.
|
|
522
|
+
*/
|
|
523
|
+
declare function useMedianFeedback(): MedianFeedbackControl;
|
|
524
|
+
|
|
525
|
+
export { type MedianDiagnostics, type MedianErrorReportOptions, type MedianErrorReportOutcome, MedianFeedback, type MedianFeedbackControl, type MedianFeedbackInstance, type MedianFeedbackProps, MedianSupport, type MedianSupportControl, type MedianSupportInstance, type MedianSupportProps, type MedianUser, type MedianDiagnostics as PortalDiagnostics, type MedianErrorReportOptions as PortalErrorReportOptions, type MedianErrorReportOutcome as PortalErrorReportOutcome, MedianFeedback as PortalFeedback, type MedianFeedbackControl as PortalFeedbackControl, type MedianFeedbackInstance as PortalFeedbackInstance, type MedianFeedbackProps as PortalFeedbackProps, MedianSupport as PortalSupport, type MedianSupportControl as PortalSupportControl, type MedianSupportInstance as PortalSupportInstance, type MedianSupportProps as PortalSupportProps, type MedianUser as PortalUser, type SupportLauncherKind, medianFeedback, medianSupport, mountMedianFeedback, mountMedianSupport, mountMedianFeedback as mountPortalFeedback, mountMedianSupport as mountPortalSupport, medianFeedback as portalFeedback, medianSupport as portalSupport, reportError, useCanReportError, useMedianFeedback, useMedianSupport, useMedianFeedback as usePortalFeedback, useMedianSupport as usePortalSupport };
|