@foir/sdk 0.27.0 → 0.28.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/CHANGELOG.md +40 -0
- package/dist/data/builtins.d.ts +1 -2
- package/dist/react/FoirFeedback.d.ts +58 -3
- package/dist/react/feedback-notes.d.ts +16 -3
- package/dist/react/index.d.ts +1 -1
- package/dist/react.js +292 -154
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -108,6 +108,46 @@ nobody asking for `@foir/sdk` gets it early. The push that carries the commit to
|
|
|
108
108
|
|
|
109
109
|
Changes on `dev` that have not been published to npm.
|
|
110
110
|
|
|
111
|
+
## [0.28.0] — 2026-10-20
|
|
112
|
+
|
|
113
|
+
### Added
|
|
114
|
+
|
|
115
|
+
- **`<FoirFeedback appearance>`** (`@foir/sdk/react`): the built-in styles'
|
|
116
|
+
colours, radius and type, as the new `FoirFeedbackAppearance`. Each token is
|
|
117
|
+
also a CSS custom property on the wrapper (`--foir-feedback-text`,
|
|
118
|
+
`--foir-feedback-radius`, …), so a stylesheet can set it from a design
|
|
119
|
+
system's own variables. Unset, the widget looks as it did.
|
|
120
|
+
- **`<FoirFeedback layout="dialog">`**: the panel opens over the page in a
|
|
121
|
+
modal `<dialog>`, with a backdrop and a Close button, closed by Escape and
|
|
122
|
+
the backdrop too. `inline`, the default, is the panel under the button.
|
|
123
|
+
- **`<FoirFeedback signIn={false}>`**: a site where nobody can sign in says so,
|
|
124
|
+
and a visitor the project takes no notes from reads "Feedback is not open
|
|
125
|
+
right now" rather than "Sign in to send feedback".
|
|
126
|
+
|
|
127
|
+
### Changed
|
|
128
|
+
|
|
129
|
+
- **Breaking: a visitor's thank-you shows no id.** Nothing opens a visitor's
|
|
130
|
+
note from its id, so `<FoirFeedback>` no longer renders the
|
|
131
|
+
`data-foir-feedback-receipt` line. The receipt, `{ id, createdAt }`, still
|
|
132
|
+
reaches `onVisitorSubmitted`.
|
|
133
|
+
- **A visitor whose note is refused because the project stopped taking
|
|
134
|
+
visitors' notes is shown that**, as the panel would show it on opening,
|
|
135
|
+
rather than asked to try again. The widget asks `noteConfig` again after a
|
|
136
|
+
visitor's send fails for a reason it cannot name.
|
|
137
|
+
- **The widget's smaller lines are sized relative to its font size**, so
|
|
138
|
+
`fontSize` scales them; they were fixed at 13 and 15px.
|
|
139
|
+
|
|
140
|
+
## [0.27.1] — 2026-10-20
|
|
141
|
+
|
|
142
|
+
### Fixed
|
|
143
|
+
|
|
144
|
+
- **`<FoirFeedback>` takes a client made without a generated module**
|
|
145
|
+
(`@foir/sdk/react`). `createFoirClient({ apiUrl, apiKey })` with no
|
|
146
|
+
`generated` prints from the manifest it fetches, and the widget worked with
|
|
147
|
+
one, but `FoirFeedbackClient` refused it at compile time for lacking
|
|
148
|
+
`notes`. `FoirFeedbackClient` is now that client or a generated one; a
|
|
149
|
+
generated client whose module has no notes namespace is still refused.
|
|
150
|
+
|
|
111
151
|
## [0.27.0] — 2026-10-20
|
|
112
152
|
|
|
113
153
|
### Added
|
package/dist/data/builtins.d.ts
CHANGED
|
@@ -554,8 +554,7 @@ export interface FoirNoteConfig {
|
|
|
554
554
|
* and nothing else, because nobody is signed in to read the note back as. Ask
|
|
555
555
|
* for more and the write is refused before anything is written.
|
|
556
556
|
*
|
|
557
|
-
*
|
|
558
|
-
* plane takes it to read the note.
|
|
557
|
+
* Nothing on the public plane takes the `id` to read the note.
|
|
559
558
|
*/
|
|
560
559
|
export interface FoirNoteReceipt {
|
|
561
560
|
id: string;
|
|
@@ -46,6 +46,25 @@ export interface FoirFeedbackProps {
|
|
|
46
46
|
* button that calls it; given none, it says so and stops.
|
|
47
47
|
*/
|
|
48
48
|
onSignIn?: () => void;
|
|
49
|
+
/**
|
|
50
|
+
* Whether a visitor can sign in on this site. Default true. `false` says
|
|
51
|
+
* they cannot, so where the project takes no notes from visitors who are not
|
|
52
|
+
* signed in, the widget says feedback is not open rather than asking them to
|
|
53
|
+
* sign in somewhere they cannot.
|
|
54
|
+
*/
|
|
55
|
+
signIn?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* How the panel opens. `inline` (the default) opens it under the button, in
|
|
58
|
+
* the page's flow. `dialog` opens it over the page in a modal `<dialog>`,
|
|
59
|
+
* with a backdrop, closed by Escape, the backdrop or its Close button.
|
|
60
|
+
*/
|
|
61
|
+
layout?: 'inline' | 'dialog';
|
|
62
|
+
/**
|
|
63
|
+
* The built-in styles' colours, radius and type. See
|
|
64
|
+
* `FoirFeedbackAppearance`: each is also a CSS custom property, so a
|
|
65
|
+
* stylesheet can set it instead. Ignored with `className`.
|
|
66
|
+
*/
|
|
67
|
+
appearance?: FoirFeedbackAppearance;
|
|
49
68
|
/** Called with a signed-in user's note once it is written. */
|
|
50
69
|
onSubmitted?: (note: FoirUserNote) => void;
|
|
51
70
|
/**
|
|
@@ -61,9 +80,45 @@ export interface FoirFeedbackProps {
|
|
|
61
80
|
onError?: (error: unknown) => void;
|
|
62
81
|
/**
|
|
63
82
|
* Style it yourself. Passing a class turns OFF the built-in inline styles
|
|
64
|
-
* entirely, so the `data-foir-feedback-*` attributes
|
|
65
|
-
* without fighting anything.
|
|
83
|
+
* entirely, `appearance` with them, so the `data-foir-feedback-*` attributes
|
|
84
|
+
* are yours to target without fighting anything.
|
|
66
85
|
*/
|
|
67
86
|
className?: string;
|
|
68
87
|
}
|
|
69
|
-
|
|
88
|
+
/**
|
|
89
|
+
* The built-in styles' tokens.
|
|
90
|
+
*
|
|
91
|
+
* Each is a CSS custom property on the wrapper, `--foir-feedback-<name>`, which
|
|
92
|
+
* the built-in styles read with today's look as the fallback. So a token can be
|
|
93
|
+
* set here, or from a stylesheet, where a design system's own variables can
|
|
94
|
+
* feed it:
|
|
95
|
+
*
|
|
96
|
+
* ```css
|
|
97
|
+
* [data-foir-feedback] { --foir-feedback-radius: var(--radius); }
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* A value is any CSS value the property takes.
|
|
101
|
+
*/
|
|
102
|
+
export interface FoirFeedbackAppearance {
|
|
103
|
+
/** `--foir-feedback-text`. Default: inherited, or the browser's dialog text in a dialog. */
|
|
104
|
+
colorText?: string;
|
|
105
|
+
/** `--foir-feedback-muted`, the line naming what it is about. Default: the text at three quarters. */
|
|
106
|
+
colorMuted?: string;
|
|
107
|
+
/** `--foir-feedback-background`, the panel and its fields. Default: none, or the browser's dialog background in a dialog. */
|
|
108
|
+
colorBackground?: string;
|
|
109
|
+
/** `--foir-feedback-border`. Default `currentColor`. */
|
|
110
|
+
colorBorder?: string;
|
|
111
|
+
/** `--foir-feedback-primary`, the send button's fill and border. Default: no fill. */
|
|
112
|
+
colorPrimary?: string;
|
|
113
|
+
/** `--foir-feedback-primary-text`, the send button's text. Default: inherited. */
|
|
114
|
+
colorPrimaryText?: string;
|
|
115
|
+
/** `--foir-feedback-danger`, a failure. Default `#c0392b`. */
|
|
116
|
+
colorDanger?: string;
|
|
117
|
+
/** `--foir-feedback-radius`, the panel's corners; its controls take three quarters. Default `8px`. */
|
|
118
|
+
radius?: string;
|
|
119
|
+
/** `--foir-feedback-font-family`. Default: inherited. */
|
|
120
|
+
fontFamily?: string;
|
|
121
|
+
/** `--foir-feedback-font-size`. Default: inherited. */
|
|
122
|
+
fontSize?: string;
|
|
123
|
+
}
|
|
124
|
+
export declare function FoirFeedback({ client, subjects, subject, label, triggerLabel, title, placeholder, defaultOpen, onSignIn, signIn, layout, appearance, onSubmitted, onVisitorSubmitted, onError, className, }: FoirFeedbackProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -7,10 +7,22 @@
|
|
|
7
7
|
* shape (see `data/selection.ts`) — so every response here has to be read rather
|
|
8
8
|
* than cast, and reading is the part worth testing on its own.
|
|
9
9
|
*/
|
|
10
|
-
import type { FoirUserNote, FoirNoteConfig, FoirNoteReceipt, FoirSubject } from '../data';
|
|
10
|
+
import type { FoirClient, FoirSchemaDefinition, FoirUserNote, FoirNoteConfig, FoirNoteReceipt, FoirSubject } from '../data';
|
|
11
11
|
import type { FoirScreenCapture } from '../media/screen-capture';
|
|
12
12
|
/**
|
|
13
|
-
* The client the widget
|
|
13
|
+
* The client the widget writes through: a generated one whose module names the
|
|
14
|
+
* notes namespace, or one made without a generated module.
|
|
15
|
+
*
|
|
16
|
+
* A client made without a module prints from the manifest it fetches, so which
|
|
17
|
+
* namespaces it reaches is known only once it has asked, and its type names
|
|
18
|
+
* every namespace. The panel's first call is what finds out whether its
|
|
19
|
+
* credential reaches notes, as it is for a generated client whose key has lost
|
|
20
|
+
* the scope. A generated client whose module has no notes namespace is neither,
|
|
21
|
+
* and is refused here: it cannot send a note.
|
|
22
|
+
*/
|
|
23
|
+
export type FoirFeedbackClient = FoirGeneratedFeedbackClient | FoirClient<FoirSchemaDefinition>;
|
|
24
|
+
/**
|
|
25
|
+
* A generated client, as far as the widget needs it.
|
|
14
26
|
*
|
|
15
27
|
* Structural rather than `FoirClient<S>`, for two reasons. `FoirClient` is
|
|
16
28
|
* generic in the caller's generated schema, so naming it here would force the
|
|
@@ -23,7 +35,7 @@ import type { FoirScreenCapture } from '../media/screen-capture';
|
|
|
23
35
|
* `Record<string, unknown>` in, `unknown` out — so a real generated client
|
|
24
36
|
* assigns to this without a cast. `react/__tests__/feedback.test-d.ts` pins that.
|
|
25
37
|
*/
|
|
26
|
-
|
|
38
|
+
interface FoirGeneratedFeedbackClient {
|
|
27
39
|
readonly ns: {
|
|
28
40
|
readonly notes: {
|
|
29
41
|
noteConfig(args?: Record<string, unknown>): Promise<unknown>;
|
|
@@ -135,3 +147,4 @@ export interface FoirVisitorSubmission {
|
|
|
135
147
|
* visitor's note cannot carry.
|
|
136
148
|
*/
|
|
137
149
|
export declare function submitVisitorNote(client: FoirFeedbackClient, submission: FoirVisitorSubmission): Promise<FoirNoteReceipt>;
|
|
150
|
+
export {};
|
package/dist/react/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type { OAuthButtonsProps } from './OAuthButtons';
|
|
|
20
20
|
export { UserButton } from './UserButton';
|
|
21
21
|
export type { UserButtonProps } from './UserButton';
|
|
22
22
|
export { FoirFeedback } from './FoirFeedback';
|
|
23
|
-
export type {
|
|
23
|
+
export type { FoirFeedbackAppearance, FoirFeedbackClient, FoirFeedbackProps, } from './FoirFeedback';
|
|
24
24
|
export { createLocalStorageAdapter } from './localStorageAdapter';
|
|
25
25
|
export type { FoirAccess, FoirUser, SessionAdapter, AuthProviderPublic } from '../auth';
|
|
26
26
|
export type { FoirEnv } from '../graphql/foir-request';
|
package/dist/react.js
CHANGED
|
@@ -2393,16 +2393,29 @@ function screenshotFilename(mimeType) {
|
|
|
2393
2393
|
|
|
2394
2394
|
// src/react/FoirFeedback.tsx
|
|
2395
2395
|
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2396
|
+
var TOKENS = {
|
|
2397
|
+
colorText: "--foir-feedback-text",
|
|
2398
|
+
colorMuted: "--foir-feedback-muted",
|
|
2399
|
+
colorBackground: "--foir-feedback-background",
|
|
2400
|
+
colorBorder: "--foir-feedback-border",
|
|
2401
|
+
colorPrimary: "--foir-feedback-primary",
|
|
2402
|
+
colorPrimaryText: "--foir-feedback-primary-text",
|
|
2403
|
+
colorDanger: "--foir-feedback-danger",
|
|
2404
|
+
radius: "--foir-feedback-radius",
|
|
2405
|
+
fontFamily: "--foir-feedback-font-family",
|
|
2406
|
+
fontSize: "--foir-feedback-font-size"
|
|
2407
|
+
};
|
|
2396
2408
|
var COPY = {
|
|
2397
2409
|
signIn: "Sign in to send feedback.",
|
|
2398
2410
|
signInAction: "Sign in",
|
|
2411
|
+
closed: "Feedback is not open right now.",
|
|
2412
|
+
close: "Close",
|
|
2399
2413
|
notEnabled: "Feedback is not available on this account.",
|
|
2400
2414
|
unavailable: "Feedback is unavailable right now. Please try again later.",
|
|
2401
2415
|
noSubject: "There is nothing on this page to give feedback on.",
|
|
2402
2416
|
loading: "One moment\u2026",
|
|
2403
2417
|
sendFailed: "Your feedback could not be sent. Please try again.",
|
|
2404
2418
|
sent: "Thanks. Your feedback has been sent.",
|
|
2405
|
-
reference: "Reference",
|
|
2406
2419
|
contactEmail: "Your email, if you would like a reply (optional)",
|
|
2407
2420
|
send: "Send feedback",
|
|
2408
2421
|
sending: "Sending\u2026",
|
|
@@ -2413,52 +2426,98 @@ var COPY = {
|
|
|
2413
2426
|
removeScreenshot: "Remove screenshot",
|
|
2414
2427
|
about: "About"
|
|
2415
2428
|
};
|
|
2429
|
+
var border = "1px solid var(--foir-feedback-border, currentColor)";
|
|
2430
|
+
var radius = "var(--foir-feedback-radius, 8px)";
|
|
2431
|
+
var controlRadius = `calc(${radius} * 0.75)`;
|
|
2432
|
+
var background = "var(--foir-feedback-background, transparent)";
|
|
2433
|
+
function wrapperStyle(appearance) {
|
|
2434
|
+
const style = {
|
|
2435
|
+
color: "var(--foir-feedback-text)",
|
|
2436
|
+
fontFamily: "var(--foir-feedback-font-family)",
|
|
2437
|
+
fontSize: "var(--foir-feedback-font-size)"
|
|
2438
|
+
};
|
|
2439
|
+
for (const [name, value] of Object.entries(appearance ?? {})) {
|
|
2440
|
+
const property = TOKENS[name];
|
|
2441
|
+
if (property && typeof value === "string" && value !== "") style[property] = value;
|
|
2442
|
+
}
|
|
2443
|
+
return style;
|
|
2444
|
+
}
|
|
2416
2445
|
var stackStyle = { display: "flex", flexDirection: "column", gap: 10 };
|
|
2417
2446
|
var panelStyle = {
|
|
2418
2447
|
...stackStyle,
|
|
2419
2448
|
maxWidth: 340,
|
|
2449
|
+
marginTop: 8,
|
|
2420
2450
|
padding: 16,
|
|
2421
|
-
borderRadius:
|
|
2422
|
-
border
|
|
2451
|
+
borderRadius: radius,
|
|
2452
|
+
border,
|
|
2453
|
+
background,
|
|
2423
2454
|
font: "inherit"
|
|
2424
2455
|
};
|
|
2456
|
+
var dialogStyle = {
|
|
2457
|
+
// The browser centres a modal dialog with its margin, and a CSS reset that
|
|
2458
|
+
// zeroes every margin (Tailwind's preflight does) pins it to the corner.
|
|
2459
|
+
margin: "auto",
|
|
2460
|
+
width: "min(420px, calc(100vw - 32px))",
|
|
2461
|
+
padding: 0,
|
|
2462
|
+
borderRadius: radius,
|
|
2463
|
+
border,
|
|
2464
|
+
background: "var(--foir-feedback-background, Canvas)",
|
|
2465
|
+
color: "var(--foir-feedback-text, CanvasText)",
|
|
2466
|
+
font: "inherit"
|
|
2467
|
+
};
|
|
2468
|
+
var dialogPanelStyle = { ...stackStyle, padding: 20 };
|
|
2469
|
+
var headerStyle = {
|
|
2470
|
+
display: "flex",
|
|
2471
|
+
alignItems: "center",
|
|
2472
|
+
justifyContent: "space-between",
|
|
2473
|
+
gap: 12
|
|
2474
|
+
};
|
|
2425
2475
|
var buttonStyle = {
|
|
2426
2476
|
padding: "8px 10px",
|
|
2427
|
-
borderRadius:
|
|
2428
|
-
border
|
|
2477
|
+
borderRadius: controlRadius,
|
|
2478
|
+
border,
|
|
2429
2479
|
background: "transparent",
|
|
2480
|
+
color: "inherit",
|
|
2430
2481
|
font: "inherit",
|
|
2431
2482
|
cursor: "pointer"
|
|
2432
2483
|
};
|
|
2433
|
-
var
|
|
2434
|
-
var
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
minHeight: 96,
|
|
2441
|
-
resize: "vertical"
|
|
2484
|
+
var closeStyle = { ...buttonStyle, padding: "4px 8px" };
|
|
2485
|
+
var submitStyle2 = {
|
|
2486
|
+
...buttonStyle,
|
|
2487
|
+
border: "1px solid var(--foir-feedback-primary, var(--foir-feedback-border, currentColor))",
|
|
2488
|
+
background: "var(--foir-feedback-primary, transparent)",
|
|
2489
|
+
color: "var(--foir-feedback-primary-text)",
|
|
2490
|
+
fontWeight: 600
|
|
2442
2491
|
};
|
|
2443
2492
|
var inputStyle2 = {
|
|
2444
2493
|
padding: "8px 10px",
|
|
2445
|
-
borderRadius:
|
|
2446
|
-
border
|
|
2447
|
-
background
|
|
2494
|
+
borderRadius: controlRadius,
|
|
2495
|
+
border,
|
|
2496
|
+
background,
|
|
2497
|
+
color: "inherit",
|
|
2448
2498
|
font: "inherit"
|
|
2449
2499
|
};
|
|
2450
|
-
var
|
|
2451
|
-
var
|
|
2452
|
-
var
|
|
2453
|
-
|
|
2500
|
+
var textareaStyle = { ...inputStyle2, minHeight: 96, resize: "vertical" };
|
|
2501
|
+
var headingStyle = { margin: 0, fontSize: "1.07em", fontWeight: 600 };
|
|
2502
|
+
var subjectStyle = {
|
|
2503
|
+
margin: 0,
|
|
2504
|
+
fontSize: "0.93em",
|
|
2505
|
+
color: "var(--foir-feedback-muted, color-mix(in srgb, currentColor 75%, transparent))"
|
|
2506
|
+
};
|
|
2507
|
+
var noteStyle = { margin: 0, fontSize: "0.93em" };
|
|
2508
|
+
var errorStyle2 = {
|
|
2509
|
+
margin: 0,
|
|
2510
|
+
fontSize: "0.93em",
|
|
2511
|
+
color: "var(--foir-feedback-danger, #c0392b)"
|
|
2512
|
+
};
|
|
2454
2513
|
var previewStyle = {
|
|
2455
2514
|
display: "block",
|
|
2456
2515
|
width: "100%",
|
|
2457
2516
|
height: 96,
|
|
2458
2517
|
objectFit: "cover",
|
|
2459
2518
|
objectPosition: "top",
|
|
2460
|
-
borderRadius:
|
|
2461
|
-
border
|
|
2519
|
+
borderRadius: controlRadius,
|
|
2520
|
+
border
|
|
2462
2521
|
};
|
|
2463
2522
|
function FoirFeedback({
|
|
2464
2523
|
client,
|
|
@@ -2470,6 +2529,9 @@ function FoirFeedback({
|
|
|
2470
2529
|
placeholder = "What would you like to tell us?",
|
|
2471
2530
|
defaultOpen = false,
|
|
2472
2531
|
onSignIn,
|
|
2532
|
+
signIn = true,
|
|
2533
|
+
layout = "inline",
|
|
2534
|
+
appearance,
|
|
2473
2535
|
onSubmitted,
|
|
2474
2536
|
onVisitorSubmitted,
|
|
2475
2537
|
onError,
|
|
@@ -2479,7 +2541,6 @@ function FoirFeedback({
|
|
|
2479
2541
|
const [probe, setProbe] = useState4({ status: "loading" });
|
|
2480
2542
|
const [body, setBody] = useState4("");
|
|
2481
2543
|
const [contactEmail, setContactEmail] = useState4("");
|
|
2482
|
-
const [receipt, setReceipt] = useState4(null);
|
|
2483
2544
|
const [screenshot, setScreenshot] = useState4(null);
|
|
2484
2545
|
const [capturing, setCapturing] = useState4(false);
|
|
2485
2546
|
const [busy, setBusy] = useState4(false);
|
|
@@ -2508,6 +2569,7 @@ function FoirFeedback({
|
|
|
2508
2569
|
if (cancelled) return;
|
|
2509
2570
|
const fault = classifyNotesFault(err);
|
|
2510
2571
|
reportFault(fault, err, "the notes surface could not be read");
|
|
2572
|
+
announceClosed(fault, signIn);
|
|
2511
2573
|
onErrorRef.current?.(err);
|
|
2512
2574
|
setProbe({ status: "blocked", fault });
|
|
2513
2575
|
}
|
|
@@ -2515,7 +2577,19 @@ function FoirFeedback({
|
|
|
2515
2577
|
return () => {
|
|
2516
2578
|
cancelled = true;
|
|
2517
2579
|
};
|
|
2518
|
-
}, [open, client]);
|
|
2580
|
+
}, [open, client, signIn]);
|
|
2581
|
+
const dialogRef = useRef2(null);
|
|
2582
|
+
useEffect3(() => {
|
|
2583
|
+
const dialog2 = dialogRef.current;
|
|
2584
|
+
if (!dialog2) return;
|
|
2585
|
+
if (open && !dialog2.open) {
|
|
2586
|
+
if (typeof dialog2.showModal === "function") dialog2.showModal();
|
|
2587
|
+
else dialog2.setAttribute("open", "");
|
|
2588
|
+
} else if (!open && dialog2.open) {
|
|
2589
|
+
if (typeof dialog2.close === "function") dialog2.close();
|
|
2590
|
+
else dialog2.removeAttribute("open");
|
|
2591
|
+
}
|
|
2592
|
+
}, [open, layout]);
|
|
2519
2593
|
const capture = useCallback2(async () => {
|
|
2520
2594
|
setFailure(null);
|
|
2521
2595
|
setCapturing(true);
|
|
@@ -2555,7 +2629,6 @@ function FoirFeedback({
|
|
|
2555
2629
|
});
|
|
2556
2630
|
setBody("");
|
|
2557
2631
|
setContactEmail("");
|
|
2558
|
-
setReceipt(written);
|
|
2559
2632
|
setSent(true);
|
|
2560
2633
|
onVisitorSubmitted?.(written);
|
|
2561
2634
|
}
|
|
@@ -2563,7 +2636,15 @@ function FoirFeedback({
|
|
|
2563
2636
|
const fault = classifyNotesFault(err);
|
|
2564
2637
|
reportFault(fault, err, "sending feedback failed");
|
|
2565
2638
|
onErrorRef.current?.(err);
|
|
2566
|
-
|
|
2639
|
+
if (!probe.signedIn && fault.kind === "other") {
|
|
2640
|
+
const now = await probeRefusal(client);
|
|
2641
|
+
if (now) {
|
|
2642
|
+
announceClosed(now, signIn);
|
|
2643
|
+
setProbe({ status: "blocked", fault: now });
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
setFailure(failureCopy(fault, signIn));
|
|
2567
2648
|
} finally {
|
|
2568
2649
|
setBusy(false);
|
|
2569
2650
|
}
|
|
@@ -2574,157 +2655,214 @@ function FoirFeedback({
|
|
|
2574
2655
|
return;
|
|
2575
2656
|
}
|
|
2576
2657
|
setSent(false);
|
|
2577
|
-
setReceipt(null);
|
|
2578
2658
|
setFailure(null);
|
|
2579
2659
|
setOpen(true);
|
|
2580
2660
|
};
|
|
2581
2661
|
const subjectName = resolved ? label ?? resolved.label : void 0;
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
children:
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
"section",
|
|
2596
|
-
{
|
|
2597
|
-
"data-foir-feedback-panel": true,
|
|
2598
|
-
"aria-label": title,
|
|
2599
|
-
style: styled ? panelStyle : void 0,
|
|
2600
|
-
children: [
|
|
2601
|
-
/* @__PURE__ */ jsx5("h2", { "data-foir-feedback-title": true, style: styled ? headingStyle : void 0, children: title }),
|
|
2602
|
-
subjectName && /* @__PURE__ */ jsxs4("p", { "data-foir-feedback-subject": true, style: styled ? subjectStyle : void 0, children: [
|
|
2603
|
-
COPY.about,
|
|
2604
|
-
": ",
|
|
2605
|
-
subjectName
|
|
2606
|
-
] }),
|
|
2607
|
-
probe.status === "loading" && /* @__PURE__ */ jsx5(
|
|
2608
|
-
"p",
|
|
2662
|
+
const dialog = layout === "dialog";
|
|
2663
|
+
const heading = /* @__PURE__ */ jsx5("h2", { "data-foir-feedback-title": true, style: styled ? headingStyle : void 0, children: title });
|
|
2664
|
+
const panel = open && /* @__PURE__ */ jsxs4(
|
|
2665
|
+
"section",
|
|
2666
|
+
{
|
|
2667
|
+
"data-foir-feedback-panel": true,
|
|
2668
|
+
"aria-label": title,
|
|
2669
|
+
style: styled ? dialog ? dialogPanelStyle : panelStyle : void 0,
|
|
2670
|
+
children: [
|
|
2671
|
+
dialog ? /* @__PURE__ */ jsxs4("div", { "data-foir-feedback-header": true, style: styled ? headerStyle : void 0, children: [
|
|
2672
|
+
heading,
|
|
2673
|
+
/* @__PURE__ */ jsx5(
|
|
2674
|
+
"button",
|
|
2609
2675
|
{
|
|
2610
|
-
|
|
2611
|
-
"data-foir-feedback-
|
|
2612
|
-
|
|
2613
|
-
|
|
2676
|
+
type: "button",
|
|
2677
|
+
"data-foir-feedback-close": true,
|
|
2678
|
+
onClick: () => setOpen(false),
|
|
2679
|
+
style: styled ? closeStyle : void 0,
|
|
2680
|
+
children: COPY.close
|
|
2681
|
+
}
|
|
2682
|
+
)
|
|
2683
|
+
] }) : heading,
|
|
2684
|
+
subjectName && /* @__PURE__ */ jsxs4("p", { "data-foir-feedback-subject": true, style: styled ? subjectStyle : void 0, children: [
|
|
2685
|
+
COPY.about,
|
|
2686
|
+
": ",
|
|
2687
|
+
subjectName
|
|
2688
|
+
] }),
|
|
2689
|
+
probe.status === "loading" && /* @__PURE__ */ jsx5(
|
|
2690
|
+
"p",
|
|
2691
|
+
{
|
|
2692
|
+
role: "status",
|
|
2693
|
+
"data-foir-feedback-status": true,
|
|
2694
|
+
style: styled ? noteStyle : void 0,
|
|
2695
|
+
children: COPY.loading
|
|
2696
|
+
}
|
|
2697
|
+
),
|
|
2698
|
+
probe.status === "blocked" && /* @__PURE__ */ jsx5(
|
|
2699
|
+
BlockedNotice,
|
|
2700
|
+
{
|
|
2701
|
+
fault: probe.fault,
|
|
2702
|
+
onSignIn,
|
|
2703
|
+
signIn,
|
|
2704
|
+
styled
|
|
2705
|
+
}
|
|
2706
|
+
),
|
|
2707
|
+
probe.status === "ready" && !resolved && /* @__PURE__ */ jsx5(
|
|
2708
|
+
"p",
|
|
2709
|
+
{
|
|
2710
|
+
role: "status",
|
|
2711
|
+
"data-foir-feedback-status": true,
|
|
2712
|
+
style: styled ? noteStyle : void 0,
|
|
2713
|
+
children: COPY.noSubject
|
|
2714
|
+
}
|
|
2715
|
+
),
|
|
2716
|
+
probe.status === "ready" && resolved && /* @__PURE__ */ jsxs4("form", { onSubmit, style: styled ? stackStyle : void 0, children: [
|
|
2717
|
+
/* @__PURE__ */ jsx5(
|
|
2718
|
+
"textarea",
|
|
2719
|
+
{
|
|
2720
|
+
"data-foir-feedback-body": true,
|
|
2721
|
+
"aria-label": placeholder,
|
|
2722
|
+
placeholder,
|
|
2723
|
+
required: true,
|
|
2724
|
+
value: body,
|
|
2725
|
+
disabled: busy,
|
|
2726
|
+
onChange: (event) => {
|
|
2727
|
+
setBody(event.target.value);
|
|
2728
|
+
if (sent) setSent(false);
|
|
2729
|
+
},
|
|
2730
|
+
style: styled ? textareaStyle : void 0
|
|
2614
2731
|
}
|
|
2615
2732
|
),
|
|
2616
|
-
probe.
|
|
2617
|
-
|
|
2618
|
-
"p",
|
|
2733
|
+
!probe.signedIn && /* @__PURE__ */ jsx5(
|
|
2734
|
+
"input",
|
|
2619
2735
|
{
|
|
2620
|
-
|
|
2621
|
-
"data-foir-feedback-
|
|
2622
|
-
|
|
2623
|
-
|
|
2736
|
+
type: "email",
|
|
2737
|
+
"data-foir-feedback-contact": true,
|
|
2738
|
+
"aria-label": COPY.contactEmail,
|
|
2739
|
+
placeholder: COPY.contactEmail,
|
|
2740
|
+
autoComplete: "email",
|
|
2741
|
+
value: contactEmail,
|
|
2742
|
+
disabled: busy,
|
|
2743
|
+
onChange: (event) => setContactEmail(event.target.value),
|
|
2744
|
+
style: styled ? inputStyle2 : void 0
|
|
2624
2745
|
}
|
|
2625
2746
|
),
|
|
2626
|
-
probe.
|
|
2747
|
+
probe.screenshotsEnabled && isFoirScreenCaptureSupported() && /* @__PURE__ */ jsx5(Fragment2, { children: screenshot ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2627
2748
|
/* @__PURE__ */ jsx5(
|
|
2628
|
-
"
|
|
2749
|
+
"img",
|
|
2629
2750
|
{
|
|
2630
|
-
"data-foir-feedback-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
value: body,
|
|
2635
|
-
disabled: busy,
|
|
2636
|
-
onChange: (event) => {
|
|
2637
|
-
setBody(event.target.value);
|
|
2638
|
-
if (sent) {
|
|
2639
|
-
setSent(false);
|
|
2640
|
-
setReceipt(null);
|
|
2641
|
-
}
|
|
2642
|
-
},
|
|
2643
|
-
style: styled ? textareaStyle : void 0
|
|
2644
|
-
}
|
|
2645
|
-
),
|
|
2646
|
-
!probe.signedIn && /* @__PURE__ */ jsx5(
|
|
2647
|
-
"input",
|
|
2648
|
-
{
|
|
2649
|
-
type: "email",
|
|
2650
|
-
"data-foir-feedback-contact": true,
|
|
2651
|
-
"aria-label": COPY.contactEmail,
|
|
2652
|
-
placeholder: COPY.contactEmail,
|
|
2653
|
-
autoComplete: "email",
|
|
2654
|
-
value: contactEmail,
|
|
2655
|
-
disabled: busy,
|
|
2656
|
-
onChange: (event) => setContactEmail(event.target.value),
|
|
2657
|
-
style: styled ? inputStyle2 : void 0
|
|
2751
|
+
"data-foir-feedback-screenshot": true,
|
|
2752
|
+
src: screenshot.dataUrl,
|
|
2753
|
+
alt: COPY.screenshotAlt,
|
|
2754
|
+
style: styled ? previewStyle : void 0
|
|
2658
2755
|
}
|
|
2659
2756
|
),
|
|
2660
|
-
|
|
2661
|
-
/* @__PURE__ */ jsx5(
|
|
2662
|
-
"img",
|
|
2663
|
-
{
|
|
2664
|
-
"data-foir-feedback-screenshot": true,
|
|
2665
|
-
src: screenshot.dataUrl,
|
|
2666
|
-
alt: COPY.screenshotAlt,
|
|
2667
|
-
style: styled ? previewStyle : void 0
|
|
2668
|
-
}
|
|
2669
|
-
),
|
|
2670
|
-
/* @__PURE__ */ jsx5(
|
|
2671
|
-
"button",
|
|
2672
|
-
{
|
|
2673
|
-
type: "button",
|
|
2674
|
-
"data-foir-feedback-remove": true,
|
|
2675
|
-
onClick: () => setScreenshot(null),
|
|
2676
|
-
disabled: busy,
|
|
2677
|
-
style: styled ? buttonStyle : void 0,
|
|
2678
|
-
children: COPY.removeScreenshot
|
|
2679
|
-
}
|
|
2680
|
-
)
|
|
2681
|
-
] }) : /* @__PURE__ */ jsx5(
|
|
2757
|
+
/* @__PURE__ */ jsx5(
|
|
2682
2758
|
"button",
|
|
2683
2759
|
{
|
|
2684
2760
|
type: "button",
|
|
2685
|
-
"data-foir-feedback-
|
|
2686
|
-
onClick: () =>
|
|
2687
|
-
disabled: busy
|
|
2761
|
+
"data-foir-feedback-remove": true,
|
|
2762
|
+
onClick: () => setScreenshot(null),
|
|
2763
|
+
disabled: busy,
|
|
2688
2764
|
style: styled ? buttonStyle : void 0,
|
|
2689
|
-
children:
|
|
2690
|
-
}
|
|
2691
|
-
) }),
|
|
2692
|
-
failure && /* @__PURE__ */ jsx5("p", { role: "alert", "data-foir-feedback-error": true, style: styled ? errorStyle2 : void 0, children: failure }),
|
|
2693
|
-
sent && !failure && /* @__PURE__ */ jsx5(
|
|
2694
|
-
"p",
|
|
2695
|
-
{
|
|
2696
|
-
role: "status",
|
|
2697
|
-
"data-foir-feedback-status": true,
|
|
2698
|
-
style: styled ? noteStyle : void 0,
|
|
2699
|
-
children: COPY.sent
|
|
2700
|
-
}
|
|
2701
|
-
),
|
|
2702
|
-
sent && !failure && receipt && /* @__PURE__ */ jsxs4("p", { "data-foir-feedback-receipt": true, style: styled ? subjectStyle : void 0, children: [
|
|
2703
|
-
COPY.reference,
|
|
2704
|
-
": ",
|
|
2705
|
-
receipt.id
|
|
2706
|
-
] }),
|
|
2707
|
-
/* @__PURE__ */ jsx5(
|
|
2708
|
-
"button",
|
|
2709
|
-
{
|
|
2710
|
-
type: "submit",
|
|
2711
|
-
"data-foir-feedback-submit": true,
|
|
2712
|
-
disabled: busy || body.trim() === "",
|
|
2713
|
-
style: styled ? submitStyle2 : void 0,
|
|
2714
|
-
children: busy ? COPY.sending : COPY.send
|
|
2765
|
+
children: COPY.removeScreenshot
|
|
2715
2766
|
}
|
|
2716
2767
|
)
|
|
2717
|
-
] })
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2768
|
+
] }) : /* @__PURE__ */ jsx5(
|
|
2769
|
+
"button",
|
|
2770
|
+
{
|
|
2771
|
+
type: "button",
|
|
2772
|
+
"data-foir-feedback-capture": true,
|
|
2773
|
+
onClick: () => void capture(),
|
|
2774
|
+
disabled: busy || capturing,
|
|
2775
|
+
style: styled ? buttonStyle : void 0,
|
|
2776
|
+
children: capturing ? COPY.capturing : COPY.capture
|
|
2777
|
+
}
|
|
2778
|
+
) }),
|
|
2779
|
+
failure && /* @__PURE__ */ jsx5("p", { role: "alert", "data-foir-feedback-error": true, style: styled ? errorStyle2 : void 0, children: failure }),
|
|
2780
|
+
sent && !failure && /* @__PURE__ */ jsx5(
|
|
2781
|
+
"p",
|
|
2782
|
+
{
|
|
2783
|
+
role: "status",
|
|
2784
|
+
"data-foir-feedback-status": true,
|
|
2785
|
+
style: styled ? noteStyle : void 0,
|
|
2786
|
+
children: COPY.sent
|
|
2787
|
+
}
|
|
2788
|
+
),
|
|
2789
|
+
/* @__PURE__ */ jsx5(
|
|
2790
|
+
"button",
|
|
2791
|
+
{
|
|
2792
|
+
type: "submit",
|
|
2793
|
+
"data-foir-feedback-submit": true,
|
|
2794
|
+
disabled: busy || body.trim() === "",
|
|
2795
|
+
style: styled ? submitStyle2 : void 0,
|
|
2796
|
+
children: busy ? COPY.sending : COPY.send
|
|
2797
|
+
}
|
|
2798
|
+
)
|
|
2799
|
+
] })
|
|
2800
|
+
]
|
|
2801
|
+
}
|
|
2802
|
+
);
|
|
2803
|
+
return /* @__PURE__ */ jsxs4(
|
|
2804
|
+
"div",
|
|
2805
|
+
{
|
|
2806
|
+
className,
|
|
2807
|
+
"data-foir-feedback": true,
|
|
2808
|
+
"data-foir-feedback-layout": layout,
|
|
2809
|
+
style: styled ? wrapperStyle(appearance) : void 0,
|
|
2810
|
+
children: [
|
|
2811
|
+
/* @__PURE__ */ jsx5(
|
|
2812
|
+
"button",
|
|
2813
|
+
{
|
|
2814
|
+
type: "button",
|
|
2815
|
+
"data-foir-feedback-trigger": true,
|
|
2816
|
+
"aria-expanded": open,
|
|
2817
|
+
"aria-haspopup": dialog ? "dialog" : void 0,
|
|
2818
|
+
onClick: toggle,
|
|
2819
|
+
style: styled ? buttonStyle : void 0,
|
|
2820
|
+
children: triggerLabel
|
|
2821
|
+
}
|
|
2822
|
+
),
|
|
2823
|
+
dialog ? /* @__PURE__ */ jsx5(
|
|
2824
|
+
"dialog",
|
|
2825
|
+
{
|
|
2826
|
+
ref: dialogRef,
|
|
2827
|
+
"data-foir-feedback-dialog": true,
|
|
2828
|
+
"aria-label": title,
|
|
2829
|
+
onClose: () => setOpen(false),
|
|
2830
|
+
onClick: (event) => {
|
|
2831
|
+
if (event.target === event.currentTarget) setOpen(false);
|
|
2832
|
+
},
|
|
2833
|
+
style: styled ? dialogStyle : void 0,
|
|
2834
|
+
children: panel
|
|
2835
|
+
}
|
|
2836
|
+
) : panel
|
|
2837
|
+
]
|
|
2838
|
+
}
|
|
2839
|
+
);
|
|
2840
|
+
}
|
|
2841
|
+
async function probeRefusal(client) {
|
|
2842
|
+
try {
|
|
2843
|
+
await readNoteConfig(client);
|
|
2844
|
+
return null;
|
|
2845
|
+
} catch (err) {
|
|
2846
|
+
const fault = classifyNotesFault(err);
|
|
2847
|
+
return fault.kind === "other" ? null : fault;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
function announceClosed(fault, signIn) {
|
|
2851
|
+
if (process.env.NODE_ENV === "production") return;
|
|
2852
|
+
if (fault.kind !== "signIn" || signIn) return;
|
|
2853
|
+
console.info(
|
|
2854
|
+
`[foir] <FoirFeedback>: the project takes no notes from visitors who are not signed in, and signIn={false} says this site has no sign-in, so the widget says feedback is not open. Turn on "Accept anonymous notes" in the project's Notes settings to take them.`
|
|
2855
|
+
);
|
|
2722
2856
|
}
|
|
2723
2857
|
function BlockedNotice({
|
|
2724
2858
|
fault,
|
|
2725
2859
|
onSignIn,
|
|
2860
|
+
signIn,
|
|
2726
2861
|
styled
|
|
2727
2862
|
}) {
|
|
2863
|
+
if (fault.kind === "signIn" && !signIn) {
|
|
2864
|
+
return /* @__PURE__ */ jsx5("p", { role: "status", "data-foir-feedback-status": true, style: styled ? noteStyle : void 0, children: COPY.closed });
|
|
2865
|
+
}
|
|
2728
2866
|
if (fault.kind === "signIn") {
|
|
2729
2867
|
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2730
2868
|
/* @__PURE__ */ jsx5("p", { role: "status", "data-foir-feedback-status": true, style: styled ? noteStyle : void 0, children: COPY.signIn }),
|
|
@@ -2742,8 +2880,8 @@ function BlockedNotice({
|
|
|
2742
2880
|
}
|
|
2743
2881
|
return /* @__PURE__ */ jsx5("p", { role: "status", "data-foir-feedback-status": true, style: styled ? noteStyle : void 0, children: fault.kind === "scope" ? COPY.notEnabled : COPY.unavailable });
|
|
2744
2882
|
}
|
|
2745
|
-
function failureCopy(fault) {
|
|
2746
|
-
if (fault.kind === "signIn") return COPY.signIn;
|
|
2883
|
+
function failureCopy(fault, signIn) {
|
|
2884
|
+
if (fault.kind === "signIn") return signIn ? COPY.signIn : COPY.closed;
|
|
2747
2885
|
if (fault.kind === "scope") return COPY.notEnabled;
|
|
2748
2886
|
return COPY.sendFailed;
|
|
2749
2887
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foir/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Official Foir SDK — editor iframe client + server-side platform helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -177,8 +177,8 @@
|
|
|
177
177
|
"ws": "^8.18.0",
|
|
178
178
|
"y-websocket": "^3.0.0",
|
|
179
179
|
"yjs": "^13.6.30",
|
|
180
|
-
"@foir/
|
|
181
|
-
"@foir/
|
|
180
|
+
"@foir/proto-ts": "0.109.0",
|
|
181
|
+
"@foir/access-vocab": "0.0.0"
|
|
182
182
|
},
|
|
183
183
|
"engines": {
|
|
184
184
|
"node": ">=18.0.0"
|