@verbumia/feedback 0.2.6 → 0.2.8
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-EKU6NNWI.js +40 -0
- package/dist/chunk-EKU6NNWI.js.map +1 -0
- package/dist/{chunk-5RTFWOGT.js → chunk-EMGN6MR4.js} +87 -3
- package/dist/chunk-EMGN6MR4.js.map +1 -0
- package/dist/{client-qgDSbz3A.d.cts → client-CnEK_2SD.d.cts} +114 -1
- package/dist/{client-qgDSbz3A.d.ts → client-CnEK_2SD.d.ts} +114 -1
- package/dist/core/index.cjs +86 -2
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/{keys-BhuK_fy1.d.cts → keys-2_T5bDpX.d.cts} +1 -1
- package/dist/{keys-CEWu0Htb.d.ts → keys-eHc_lx5v.d.ts} +1 -1
- package/dist/native/index.cjs +317 -48
- package/dist/native/index.cjs.map +1 -1
- package/dist/native/index.d.cts +48 -4
- package/dist/native/index.d.ts +48 -4
- package/dist/native/index.js +200 -48
- package/dist/native/index.js.map +1 -1
- package/dist/react/index.cjs +319 -41
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +119 -4
- package/dist/react/index.d.ts +119 -4
- package/dist/react/index.js +202 -41
- package/dist/react/index.js.map +1 -1
- package/dist/svelte/index.cjs +86 -2
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.d.cts +2 -2
- package/dist/svelte/index.d.ts +2 -2
- package/dist/svelte/index.js +1 -1
- package/dist/vue/index.cjs +86 -2
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.d.cts +2 -2
- package/dist/vue/index.d.ts +2 -2
- package/dist/vue/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5RTFWOGT.js.map +0 -1
package/dist/react/index.cjs
CHANGED
|
@@ -81,11 +81,61 @@ var FeedbackClient = class {
|
|
|
81
81
|
get hasConsented() {
|
|
82
82
|
return this.tokens !== null;
|
|
83
83
|
}
|
|
84
|
+
/** Alias of `hasConsented` exposed under the 0.2.7 naming used by the
|
|
85
|
+
* `controller.hasAcceptedTos` getter (the SDK's built-in modal and a
|
|
86
|
+
* host's external ToS page share the same persisted token bundle, so
|
|
87
|
+
* both flip this boolean once a session is bootstrapped). */
|
|
88
|
+
get hasAcceptedTos() {
|
|
89
|
+
return this.tokens !== null;
|
|
90
|
+
}
|
|
84
91
|
/** Server-minted sessionId / grouping_key (from the token bundle).
|
|
85
92
|
* Available only after `acceptTos()`; never client-generated. */
|
|
86
93
|
get sessionId() {
|
|
87
94
|
return this.tokens?.grouping_key;
|
|
88
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* 0.2.7 — `GET /v1/projects/{projectId}/feedback-addon/state`.
|
|
98
|
+
*
|
|
99
|
+
* Auth selection (matches backend's dual-acceptance after task 836's
|
|
100
|
+
* follow-up PR — see CONTRACT note in the response type):
|
|
101
|
+
* - If `cfg.apiKey` is set → `Authorization: ApiKey <key>` (so the
|
|
102
|
+
* plugin can fetch state at `setup()` time, before the user has
|
|
103
|
+
* accepted ToS).
|
|
104
|
+
* - Else if a user-session bundle exists → `Authorization: Bearer
|
|
105
|
+
* <access_token>` (the deferred-after-acceptTos path).
|
|
106
|
+
* - Else → throw `FeedbackError("addon state requires apiKey or
|
|
107
|
+
* acceptTos")`. The plugin's setup catches and surfaces a
|
|
108
|
+
* console.warn the first time, then re-attempts on the
|
|
109
|
+
* bundle-mint that follows `acceptTos()`.
|
|
110
|
+
*
|
|
111
|
+
* Best-effort: a transport error returns `null` instead of throwing,
|
|
112
|
+
* so the controller's `isActive` falls back to whatever was last
|
|
113
|
+
* known (or its initial value) without flipping the host's CTA into
|
|
114
|
+
* an inconsistent state on a transient blip.
|
|
115
|
+
*/
|
|
116
|
+
async getAddonState() {
|
|
117
|
+
let auth;
|
|
118
|
+
if (this.cfg.apiKey) {
|
|
119
|
+
auth = `ApiKey ${this.cfg.apiKey}`;
|
|
120
|
+
} else if (this.tokens) {
|
|
121
|
+
auth = `Bearer ${this.tokens.access_token}`;
|
|
122
|
+
} else {
|
|
123
|
+
throw new FeedbackError(
|
|
124
|
+
"addon state requires apiKey or acceptTos"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const url = `${this.base()}/v1/projects/${this.cfg.projectId}/feedback-addon/state`;
|
|
128
|
+
try {
|
|
129
|
+
const res = await this.fetchImpl(url, {
|
|
130
|
+
method: "GET",
|
|
131
|
+
headers: { Authorization: auth }
|
|
132
|
+
});
|
|
133
|
+
if (!res.ok) return null;
|
|
134
|
+
return await res.json();
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
89
139
|
/** BCP-47 language the widget is rating strings in. */
|
|
90
140
|
get language() {
|
|
91
141
|
return this.cfg.language;
|
|
@@ -157,9 +207,18 @@ var FeedbackClient = class {
|
|
|
157
207
|
}
|
|
158
208
|
this.tokens = await res.json();
|
|
159
209
|
}
|
|
160
|
-
/** Authenticated fetch with a single transparent refresh-on-401 retry.
|
|
210
|
+
/** Authenticated fetch with a single transparent refresh-on-401 retry.
|
|
211
|
+
* When `cfg.autoAcceptTos === false` (the plugin's `tos: "skip"` opt-in
|
|
212
|
+
* / 0.2.7) and no token has been minted yet, throws a `not consented`
|
|
213
|
+
* error instead of silently calling `acceptTos()` — the host promises
|
|
214
|
+
* to handle consent externally via `controller.acceptTos()`. */
|
|
161
215
|
async authed(path, init, retry = true) {
|
|
162
|
-
if (!this.tokens)
|
|
216
|
+
if (!this.tokens) {
|
|
217
|
+
if (this.cfg.autoAcceptTos === false) {
|
|
218
|
+
throw new FeedbackError("not consented");
|
|
219
|
+
}
|
|
220
|
+
await this.acceptTos();
|
|
221
|
+
}
|
|
163
222
|
const res = await this.fetchImpl(`${this.base()}${path}`, {
|
|
164
223
|
...init,
|
|
165
224
|
headers: {
|
|
@@ -196,6 +255,31 @@ var FeedbackClient = class {
|
|
|
196
255
|
suggest(payload) {
|
|
197
256
|
this.enqueue({ kind: "suggestion", payload });
|
|
198
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* 0.2.8 — `PATCH /v1/feedback/suggestions/{id}` (backend task 847,
|
|
260
|
+
* deploy `45190c8`). Used by the panel's edit-mode editor when the
|
|
261
|
+
* end-user already has a pending suggestion for a string (the
|
|
262
|
+
* `FeedbackString.my_suggestion.id` from a prior submit). Submits
|
|
263
|
+
* synchronously rather than going through the rating/suggestion
|
|
264
|
+
* batch queue — the host triggered an explicit edit, not a passive
|
|
265
|
+
* rating, so we want the round-trip to surface before the panel
|
|
266
|
+
* closes. Best-effort failures bubble; the caller catches and
|
|
267
|
+
* shows an error row in the panel.
|
|
268
|
+
*
|
|
269
|
+
* Wire body: `{ text: string }` (backend probe confirmed). NOT the
|
|
270
|
+
* legacy `{ suggested_text }` of the batched POST path.
|
|
271
|
+
*/
|
|
272
|
+
async editSuggestion(id, text) {
|
|
273
|
+
if (!id) throw new FeedbackError("editSuggestion: id is required");
|
|
274
|
+
const trimmed = (text ?? "").trim();
|
|
275
|
+
if (!trimmed) throw new FeedbackError("editSuggestion: text is required");
|
|
276
|
+
const res = await this.authed(`/v1/feedback/suggestions/${encodeURIComponent(id)}`, {
|
|
277
|
+
method: "PATCH",
|
|
278
|
+
headers: { "Content-Type": "application/json" },
|
|
279
|
+
body: JSON.stringify({ text: trimmed })
|
|
280
|
+
});
|
|
281
|
+
if (!res.ok) throw await this.problem(res, "failed to update suggestion");
|
|
282
|
+
}
|
|
199
283
|
enqueue(item) {
|
|
200
284
|
this.queue.push(item);
|
|
201
285
|
if (this.queue.length >= this.cfg.maxBatch) {
|
|
@@ -291,6 +375,42 @@ function dedupe(keys) {
|
|
|
291
375
|
return out;
|
|
292
376
|
}
|
|
293
377
|
|
|
378
|
+
// src/core/locales.ts
|
|
379
|
+
var MESSAGES = {
|
|
380
|
+
en: {
|
|
381
|
+
showOriginal: "Show original",
|
|
382
|
+
submitSuggestion: "Submit a suggestion",
|
|
383
|
+
updateMySuggestion: "Update my suggestion"
|
|
384
|
+
},
|
|
385
|
+
fr: {
|
|
386
|
+
showOriginal: "Voir l'original",
|
|
387
|
+
submitSuggestion: "Soumettre une suggestion",
|
|
388
|
+
updateMySuggestion: "Mettre \xE0 jour ma suggestion"
|
|
389
|
+
},
|
|
390
|
+
es: {
|
|
391
|
+
showOriginal: "Ver el original",
|
|
392
|
+
submitSuggestion: "Enviar una sugerencia",
|
|
393
|
+
updateMySuggestion: "Actualizar mi sugerencia"
|
|
394
|
+
},
|
|
395
|
+
de: {
|
|
396
|
+
showOriginal: "Original anzeigen",
|
|
397
|
+
submitSuggestion: "Vorschlag einreichen",
|
|
398
|
+
updateMySuggestion: "Meinen Vorschlag aktualisieren"
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
function t(locale, key) {
|
|
402
|
+
if (locale) {
|
|
403
|
+
const exact = MESSAGES[locale];
|
|
404
|
+
if (exact) return exact[key];
|
|
405
|
+
const base = locale.split("-")[0];
|
|
406
|
+
if (base) {
|
|
407
|
+
const baseMsgs = MESSAGES[base];
|
|
408
|
+
if (baseMsgs) return baseMsgs[key];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return MESSAGES.en[key];
|
|
412
|
+
}
|
|
413
|
+
|
|
294
414
|
// src/react/panel.tsx
|
|
295
415
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
296
416
|
var C = {
|
|
@@ -303,11 +423,14 @@ var C = {
|
|
|
303
423
|
emeraldSoft: "#34d399"
|
|
304
424
|
};
|
|
305
425
|
function FeedbackPanel(props) {
|
|
306
|
-
const { client, keys, namespace, onClose } = props;
|
|
307
|
-
const [consented, setConsented] = (0, import_react.useState)(
|
|
426
|
+
const { client, keys, namespace, tos = "modal", onClose } = props;
|
|
427
|
+
const [consented, setConsented] = (0, import_react.useState)(
|
|
428
|
+
tos === "skip" ? true : client.hasConsented
|
|
429
|
+
);
|
|
308
430
|
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
309
431
|
const [error, setError] = (0, import_react.useState)(null);
|
|
310
432
|
const [strings, setStrings] = (0, import_react.useState)([]);
|
|
433
|
+
const [showSource, setShowSource] = (0, import_react.useState)(false);
|
|
311
434
|
const loadStrings = (0, import_react.useCallback)(async () => {
|
|
312
435
|
setBusy(true);
|
|
313
436
|
setError(null);
|
|
@@ -414,7 +537,52 @@ function FeedbackPanel(props) {
|
|
|
414
537
|
busy,
|
|
415
538
|
onAccept: accept
|
|
416
539
|
}
|
|
417
|
-
) : busy && !strings.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: C.dim }, children: "Loading\u2026" }) : !strings.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: C.dim }, children: "No strings to review on this view." }) :
|
|
540
|
+
) : busy && !strings.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: C.dim }, children: "Loading\u2026" }) : !strings.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: C.dim }, children: "No strings to review on this view." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
541
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
542
|
+
"div",
|
|
543
|
+
{
|
|
544
|
+
style: {
|
|
545
|
+
display: "flex",
|
|
546
|
+
alignItems: "center",
|
|
547
|
+
justifyContent: "flex-end",
|
|
548
|
+
marginBottom: 10
|
|
549
|
+
},
|
|
550
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
551
|
+
"label",
|
|
552
|
+
{
|
|
553
|
+
style: {
|
|
554
|
+
display: "inline-flex",
|
|
555
|
+
alignItems: "center",
|
|
556
|
+
gap: 8,
|
|
557
|
+
color: C.dim,
|
|
558
|
+
fontSize: 12,
|
|
559
|
+
cursor: "pointer"
|
|
560
|
+
},
|
|
561
|
+
children: [
|
|
562
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
563
|
+
"input",
|
|
564
|
+
{
|
|
565
|
+
type: "checkbox",
|
|
566
|
+
checked: showSource,
|
|
567
|
+
onChange: (e) => setShowSource(e.target.checked)
|
|
568
|
+
}
|
|
569
|
+
),
|
|
570
|
+
t(client.language, "showOriginal")
|
|
571
|
+
]
|
|
572
|
+
}
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
),
|
|
576
|
+
strings.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
577
|
+
StringRow,
|
|
578
|
+
{
|
|
579
|
+
s,
|
|
580
|
+
client,
|
|
581
|
+
showSource
|
|
582
|
+
},
|
|
583
|
+
`${s.namespace}:${s.key}`
|
|
584
|
+
))
|
|
585
|
+
] })
|
|
418
586
|
] }),
|
|
419
587
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
420
588
|
"footer",
|
|
@@ -461,11 +629,13 @@ function ConsentStep(props) {
|
|
|
461
629
|
] });
|
|
462
630
|
}
|
|
463
631
|
function StringRow(props) {
|
|
464
|
-
const { s, client } = props;
|
|
632
|
+
const { s, client, showSource } = props;
|
|
465
633
|
const [mine, setMine] = (0, import_react.useState)(s.my_rating);
|
|
466
634
|
const [showSuggest, setShowSuggest] = (0, import_react.useState)(false);
|
|
467
|
-
const
|
|
635
|
+
const mySuggestionId = s.my_suggestion ? s.my_suggestion.id : null;
|
|
636
|
+
const [text, setText] = (0, import_react.useState)(s.my_suggestion?.text ?? "");
|
|
468
637
|
const [sent, setSent] = (0, import_react.useState)(false);
|
|
638
|
+
const [editError, setEditError] = (0, import_react.useState)(null);
|
|
469
639
|
const rate = (stars) => {
|
|
470
640
|
setMine(stars);
|
|
471
641
|
client.rate({
|
|
@@ -476,19 +646,35 @@ function StringRow(props) {
|
|
|
476
646
|
stars
|
|
477
647
|
});
|
|
478
648
|
};
|
|
479
|
-
const submitSuggestion = () => {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
649
|
+
const submitSuggestion = async () => {
|
|
650
|
+
const trimmed = text.trim();
|
|
651
|
+
if (!trimmed) return;
|
|
652
|
+
setEditError(null);
|
|
653
|
+
try {
|
|
654
|
+
if (mySuggestionId) {
|
|
655
|
+
await client.editSuggestion(mySuggestionId, trimmed);
|
|
656
|
+
} else {
|
|
657
|
+
client.suggest({
|
|
658
|
+
namespace: s.namespace,
|
|
659
|
+
key: s.key,
|
|
660
|
+
language: client.language,
|
|
661
|
+
translation_hash: s.translation_hash,
|
|
662
|
+
suggested_text: trimmed
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
setSent(true);
|
|
666
|
+
setShowSuggest(false);
|
|
667
|
+
} catch (e) {
|
|
668
|
+
setEditError(
|
|
669
|
+
e instanceof Error ? e.message : "Could not update the suggestion"
|
|
670
|
+
);
|
|
671
|
+
}
|
|
491
672
|
};
|
|
673
|
+
const showSourceRow = showSource && s.source_locale !== client.language;
|
|
674
|
+
const submitLabel = t(
|
|
675
|
+
client.language,
|
|
676
|
+
mySuggestionId ? "updateMySuggestion" : "submitSuggestion"
|
|
677
|
+
);
|
|
492
678
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
493
679
|
"div",
|
|
494
680
|
{
|
|
@@ -505,6 +691,19 @@ function StringRow(props) {
|
|
|
505
691
|
" \xB7 ",
|
|
506
692
|
s.key
|
|
507
693
|
] }),
|
|
694
|
+
showSourceRow ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
695
|
+
"div",
|
|
696
|
+
{
|
|
697
|
+
"data-testid": "source-row",
|
|
698
|
+
style: {
|
|
699
|
+
margin: "4px 0 2px",
|
|
700
|
+
fontSize: 12,
|
|
701
|
+
color: C.dim,
|
|
702
|
+
fontStyle: "italic"
|
|
703
|
+
},
|
|
704
|
+
children: s.source_text
|
|
705
|
+
}
|
|
706
|
+
) : null,
|
|
508
707
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { margin: "6px 0 10px", fontSize: 15 }, children: s.value }),
|
|
509
708
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 4 }, children: [
|
|
510
709
|
[1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -539,7 +738,7 @@ function StringRow(props) {
|
|
|
539
738
|
fontSize: 12,
|
|
540
739
|
cursor: "pointer"
|
|
541
740
|
},
|
|
542
|
-
children: sent ? "Suggested \u2713" :
|
|
741
|
+
children: sent ? "Suggested \u2713" : submitLabel
|
|
543
742
|
}
|
|
544
743
|
)
|
|
545
744
|
] }),
|
|
@@ -563,11 +762,12 @@ function StringRow(props) {
|
|
|
563
762
|
}
|
|
564
763
|
}
|
|
565
764
|
),
|
|
765
|
+
editError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: "#f87171", fontSize: 12, marginTop: 6 }, children: editError }) : null,
|
|
566
766
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
567
767
|
"button",
|
|
568
768
|
{
|
|
569
769
|
type: "button",
|
|
570
|
-
onClick: submitSuggestion,
|
|
770
|
+
onClick: () => void submitSuggestion(),
|
|
571
771
|
style: {
|
|
572
772
|
marginTop: 6,
|
|
573
773
|
background: C.emerald,
|
|
@@ -578,7 +778,7 @@ function StringRow(props) {
|
|
|
578
778
|
fontWeight: 700,
|
|
579
779
|
cursor: "pointer"
|
|
580
780
|
},
|
|
581
|
-
children:
|
|
781
|
+
children: submitLabel
|
|
582
782
|
}
|
|
583
783
|
)
|
|
584
784
|
] })
|
|
@@ -590,42 +790,73 @@ function StringRow(props) {
|
|
|
590
790
|
// src/react/plugin.tsx
|
|
591
791
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
592
792
|
function makeStore() {
|
|
593
|
-
let
|
|
793
|
+
let state = { isOpen: false, snapshotKeys: void 0 };
|
|
594
794
|
const listeners = /* @__PURE__ */ new Set();
|
|
595
795
|
return {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
open
|
|
600
|
-
|
|
796
|
+
getState: () => state,
|
|
797
|
+
setOpen(open, snapshotKeys) {
|
|
798
|
+
const next = {
|
|
799
|
+
isOpen: open,
|
|
800
|
+
snapshotKeys: open ? snapshotKeys : void 0
|
|
801
|
+
};
|
|
802
|
+
if (state.isOpen === next.isOpen && state.snapshotKeys === next.snapshotKeys) {
|
|
803
|
+
return;
|
|
601
804
|
}
|
|
805
|
+
state = next;
|
|
806
|
+
listeners.forEach((l) => l());
|
|
602
807
|
},
|
|
603
808
|
subscribe(l) {
|
|
604
809
|
listeners.add(l);
|
|
605
|
-
return () =>
|
|
810
|
+
return () => {
|
|
811
|
+
listeners.delete(l);
|
|
812
|
+
};
|
|
606
813
|
}
|
|
607
814
|
};
|
|
608
815
|
}
|
|
816
|
+
async function captureCurrentViewSnapshot(i18next) {
|
|
817
|
+
const reg = globalThis.__verbumia_key_registry__;
|
|
818
|
+
if (!reg) return [];
|
|
819
|
+
const lng = i18next?.language;
|
|
820
|
+
const change = i18next?.changeLanguage;
|
|
821
|
+
if (typeof change === "function" && typeof lng === "string" && lng) {
|
|
822
|
+
reg.reset?.();
|
|
823
|
+
try {
|
|
824
|
+
await change(lng);
|
|
825
|
+
} catch {
|
|
826
|
+
}
|
|
827
|
+
await new Promise((r) => {
|
|
828
|
+
if (typeof requestAnimationFrame === "function") {
|
|
829
|
+
requestAnimationFrame(() => r());
|
|
830
|
+
} else {
|
|
831
|
+
setTimeout(() => r(), 16);
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
return reg.snapshot();
|
|
835
|
+
}
|
|
836
|
+
return reg.snapshot();
|
|
837
|
+
}
|
|
609
838
|
function feedbackPlugin(options) {
|
|
610
839
|
const store = makeStore();
|
|
611
840
|
let client = null;
|
|
612
841
|
function Outlet() {
|
|
613
|
-
const
|
|
842
|
+
const state = (0, import_react2.useSyncExternalStore)(
|
|
614
843
|
store.subscribe,
|
|
615
|
-
store.
|
|
616
|
-
store.
|
|
844
|
+
store.getState,
|
|
845
|
+
store.getState
|
|
617
846
|
);
|
|
618
|
-
if (!isOpen || !client || typeof document === "undefined") return null;
|
|
847
|
+
if (!state.isOpen || !client || typeof document === "undefined") return null;
|
|
619
848
|
const c = client;
|
|
849
|
+
const panelKeys = options.keys ?? state.snapshotKeys;
|
|
620
850
|
return (0, import_react_dom.createPortal)(
|
|
621
851
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
622
852
|
FeedbackPanel,
|
|
623
853
|
{
|
|
624
854
|
client: c,
|
|
625
|
-
keys:
|
|
855
|
+
keys: panelKeys,
|
|
626
856
|
namespace: options.namespace,
|
|
857
|
+
tos: options.tos ?? "modal",
|
|
627
858
|
onClose: () => {
|
|
628
|
-
store.
|
|
859
|
+
store.setOpen(false);
|
|
629
860
|
void c.flush();
|
|
630
861
|
}
|
|
631
862
|
}
|
|
@@ -637,24 +868,71 @@ function feedbackPlugin(options) {
|
|
|
637
868
|
name: "@verbumia/feedback",
|
|
638
869
|
setup(ctx) {
|
|
639
870
|
const initialLanguage = options.language ?? ctx.i18n?.language ?? ctx.config.defaultLocale;
|
|
871
|
+
const tos = options.tos ?? "modal";
|
|
640
872
|
client = new FeedbackClient({
|
|
641
873
|
apiBase: options.apiBase ?? ctx.config.apiBase ?? "https://api.verbumia.dev",
|
|
642
874
|
projectId: options.projectId ?? ctx.config.projectUuid,
|
|
643
875
|
language: initialLanguage,
|
|
644
876
|
endUserId: options.endUserId,
|
|
645
|
-
fetchImpl: options.fetchImpl
|
|
877
|
+
fetchImpl: options.fetchImpl,
|
|
878
|
+
apiKey: options.apiKey,
|
|
879
|
+
autoAcceptTos: tos !== "skip"
|
|
646
880
|
});
|
|
881
|
+
const clientRef = client;
|
|
882
|
+
let addonState = null;
|
|
883
|
+
const cta = options.cta ?? "auto";
|
|
884
|
+
const scope = options.scope ?? "current-view";
|
|
885
|
+
const ctxI18next = ctx.i18n?.i18next;
|
|
886
|
+
const refreshState = async () => {
|
|
887
|
+
try {
|
|
888
|
+
const next = await clientRef.getAddonState();
|
|
889
|
+
if (next !== null) addonState = next;
|
|
890
|
+
} catch {
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
if (options.apiKey) void refreshState();
|
|
647
894
|
let langUnsub;
|
|
648
895
|
if (typeof ctx.onLanguageChange === "function") {
|
|
649
|
-
langUnsub = ctx.onLanguageChange((lng) =>
|
|
896
|
+
langUnsub = ctx.onLanguageChange((lng) => {
|
|
897
|
+
clientRef.setLanguage(lng);
|
|
898
|
+
void refreshState();
|
|
899
|
+
});
|
|
650
900
|
}
|
|
651
901
|
const controller = {
|
|
652
|
-
open: () =>
|
|
902
|
+
open: async () => {
|
|
903
|
+
if (scope === "current-view") {
|
|
904
|
+
const snapshot = await captureCurrentViewSnapshot(ctxI18next);
|
|
905
|
+
store.setOpen(true, snapshot);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
store.setOpen(true);
|
|
909
|
+
},
|
|
653
910
|
close: () => {
|
|
654
|
-
store.
|
|
655
|
-
void
|
|
911
|
+
store.setOpen(false);
|
|
912
|
+
void clientRef?.flush();
|
|
913
|
+
},
|
|
914
|
+
client: clientRef,
|
|
915
|
+
get tosVersion() {
|
|
916
|
+
return clientRef.tosVersion;
|
|
917
|
+
},
|
|
918
|
+
get hasAcceptedTos() {
|
|
919
|
+
return clientRef.hasAcceptedTos;
|
|
920
|
+
},
|
|
921
|
+
acceptTos: async () => {
|
|
922
|
+
await clientRef.acceptTos();
|
|
923
|
+
if (!options.apiKey) await refreshState();
|
|
656
924
|
},
|
|
657
|
-
|
|
925
|
+
get isActive() {
|
|
926
|
+
if (cta === "show") return true;
|
|
927
|
+
if (cta === "hide") return false;
|
|
928
|
+
return addonState ? addonState.isActive : null;
|
|
929
|
+
},
|
|
930
|
+
get enabledLanguages() {
|
|
931
|
+
return addonState ? addonState.enabledLanguages : null;
|
|
932
|
+
},
|
|
933
|
+
get sku() {
|
|
934
|
+
return addonState ? addonState.sku : null;
|
|
935
|
+
}
|
|
658
936
|
};
|
|
659
937
|
options.onReady?.(controller);
|
|
660
938
|
if (options.controllerRef) options.controllerRef.current = controller;
|