convex-feedback-ui 0.1.3 → 0.1.5
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 +53 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/native/expo/FeedbackScreen.d.ts +1 -1
- package/dist/native/expo/FeedbackScreen.d.ts.map +1 -1
- package/dist/native/expo/FeedbackScreen.js +3 -2
- package/dist/native/expo/FeedbackScreen.js.map +1 -1
- package/dist/native/expo/index.d.ts +2 -0
- package/dist/native/expo/index.d.ts.map +1 -1
- package/dist/native/expo/index.js +2 -0
- package/dist/native/expo/index.js.map +1 -1
- package/dist/native/expo/metadata.d.ts +4 -0
- package/dist/native/expo/metadata.d.ts.map +1 -0
- package/dist/native/expo/metadata.js +44 -0
- package/dist/native/expo/metadata.js.map +1 -0
- package/dist/native/native/FeedbackScreen.d.ts +1 -1
- package/dist/native/native/FeedbackScreen.d.ts.map +1 -1
- package/dist/native/native/FeedbackScreen.js +3 -2
- package/dist/native/native/FeedbackScreen.js.map +1 -1
- package/dist/native/native/index.d.ts +2 -0
- package/dist/native/native/index.d.ts.map +1 -1
- package/dist/native/native/index.js +2 -0
- package/dist/native/native/index.js.map +1 -1
- package/dist/native/shared/metadata.d.ts +5 -0
- package/dist/native/shared/metadata.d.ts.map +1 -0
- package/dist/native/shared/metadata.js +31 -0
- package/dist/native/shared/metadata.js.map +1 -0
- package/dist/native/shared/ui/CommentBranch.d.ts +2 -0
- package/dist/native/shared/ui/CommentBranch.d.ts.map +1 -1
- package/dist/native/shared/ui/CommentBranch.js +12 -2
- package/dist/native/shared/ui/CommentBranch.js.map +1 -1
- package/dist/native/shared/ui/EntryDetail.d.ts.map +1 -1
- package/dist/native/shared/ui/EntryDetail.js +3 -1
- package/dist/native/shared/ui/EntryDetail.js.map +1 -1
- package/dist/native/shared/ui/FeedbackScreenHeader.d.ts.map +1 -1
- package/dist/native/shared/ui/FeedbackScreenHeader.js +10 -8
- package/dist/native/shared/ui/FeedbackScreenHeader.js.map +1 -1
- package/dist/native/shared/ui/FeedbackScreenList.js +1 -1
- package/dist/native/shared/ui/FeedbackScreenList.js.map +1 -1
- package/dist/native/shared/ui/MetadataModal.d.ts +6 -0
- package/dist/native/shared/ui/MetadataModal.d.ts.map +1 -0
- package/dist/native/shared/ui/MetadataModal.js +39 -0
- package/dist/native/shared/ui/MetadataModal.js.map +1 -0
- package/dist/native/shared/ui/NewEntry.d.ts.map +1 -1
- package/dist/native/shared/ui/NewEntry.js +10 -3
- package/dist/native/shared/ui/NewEntry.js.map +1 -1
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/metadata.d.ts +4 -0
- package/dist/react/metadata.d.ts.map +1 -0
- package/dist/react/metadata.js +40 -0
- package/dist/react/metadata.js.map +1 -0
- package/dist/react/screen.d.ts +1 -1
- package/dist/react/screen.d.ts.map +1 -1
- package/dist/react/screen.js +18 -4
- package/dist/react/screen.js.map +1 -1
- package/dist/react/styles.css +35 -0
- package/dist/shared/context/FeedbackBodyProvider.d.ts +1 -1
- package/dist/shared/context/FeedbackBodyProvider.d.ts.map +1 -1
- package/dist/shared/context/FeedbackBodyProvider.js +5 -1
- package/dist/shared/context/FeedbackBodyProvider.js.map +1 -1
- package/dist/shared/messages.d.ts.map +1 -1
- package/dist/shared/messages.js +9 -0
- package/dist/shared/messages.js.map +1 -1
- package/dist/shared/metadata.d.ts +6 -0
- package/dist/shared/metadata.d.ts.map +1 -0
- package/dist/shared/metadata.js +54 -0
- package/dist/shared/metadata.js.map +1 -0
- package/dist/shared/types/index.d.ts +1 -0
- package/dist/shared/types/index.d.ts.map +1 -1
- package/dist/shared/types/messages.d.ts +17 -0
- package/dist/shared/types/messages.d.ts.map +1 -1
- package/dist/shared/types/metadata.d.ts +22 -0
- package/dist/shared/types/metadata.d.ts.map +1 -0
- package/dist/shared/types/metadata.js +2 -0
- package/dist/shared/types/metadata.js.map +1 -0
- package/dist/shared/types/screen.d.ts +10 -1
- package/dist/shared/types/screen.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/native/expo/FeedbackScreen.tsx +4 -0
- package/src/native/expo/index.ts +2 -0
- package/src/native/expo/metadata.ts +62 -0
- package/src/native/native/FeedbackScreen.tsx +4 -0
- package/src/native/native/index.ts +2 -0
- package/src/native/shared/metadata.ts +39 -0
- package/src/native/shared/ui/CommentBranch.tsx +36 -2
- package/src/native/shared/ui/EntryDetail.tsx +14 -0
- package/src/native/shared/ui/FeedbackScreenHeader.tsx +23 -21
- package/src/native/shared/ui/FeedbackScreenList.tsx +1 -1
- package/src/native/shared/ui/MetadataModal.tsx +112 -0
- package/src/native/shared/ui/NewEntry.tsx +17 -3
- package/src/react/index.ts +2 -0
- package/src/react/metadata.ts +51 -0
- package/src/react/screen.tsx +89 -2
- package/src/react/styles.css +35 -0
- package/src/shared/context/FeedbackBodyProvider.tsx +8 -0
- package/src/shared/messages.ts +9 -0
- package/src/shared/metadata.ts +84 -0
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/messages.ts +24 -0
- package/src/shared/types/metadata.ts +55 -0
- package/src/shared/types/screen.ts +13 -2
- package/src/native/shared/ui/ReplyList.tsx +0 -41
package/src/react/screen.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type { EntryKind } from "convex-feedback";
|
|
3
|
+
import type { EntryKind, FeedbackMetadata } from "convex-feedback";
|
|
4
4
|
import { useMemo, useState, type SyntheticEvent } from "react";
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
FeedbackEntry,
|
|
27
27
|
FeedbackForm,
|
|
28
28
|
} from "./primitives.js";
|
|
29
|
+
import { collectWebMetadata } from "./metadata.js";
|
|
30
|
+
import { collectEntryMetadata, formatMetadataKey } from "../shared/metadata.js";
|
|
29
31
|
|
|
30
32
|
const entryKinds: readonly EntryKind[] = [
|
|
31
33
|
"feedback",
|
|
@@ -49,6 +51,7 @@ export function FeedbackScreen({
|
|
|
49
51
|
transformComments,
|
|
50
52
|
renderActor,
|
|
51
53
|
debounceDuration = 300,
|
|
54
|
+
collectMetadata,
|
|
52
55
|
...props
|
|
53
56
|
}: FeedbackScreenProps) {
|
|
54
57
|
return (
|
|
@@ -60,6 +63,8 @@ export function FeedbackScreen({
|
|
|
60
63
|
enabledKinds={enabledKinds}
|
|
61
64
|
maxCommentDepth={maxCommentDepth}
|
|
62
65
|
debounceDuration={debounceDuration}
|
|
66
|
+
collectMetadata={collectMetadata}
|
|
67
|
+
collectStandardMetadata={collectWebMetadata}
|
|
63
68
|
transformComments={transformComments}
|
|
64
69
|
renderActor={renderActor}
|
|
65
70
|
>
|
|
@@ -207,7 +212,8 @@ function EntryCard({ entry, hooks, onOpen }: FeedbackScreenEntryCardProps) {
|
|
|
207
212
|
}
|
|
208
213
|
|
|
209
214
|
function CreateEntryForm({ onCreated }: FeedbackScreenEntryModalProps) {
|
|
210
|
-
const { hooks, enabledKinds } =
|
|
215
|
+
const { hooks, enabledKinds, collectMetadata, collectStandardMetadata } =
|
|
216
|
+
useFeedbackBody();
|
|
211
217
|
const { messages } = useFeedbackUi();
|
|
212
218
|
const [kind, setKind] = useState<EntryKind>(enabledKinds[0] ?? "feedback");
|
|
213
219
|
const [title, setTitle] = useState("");
|
|
@@ -242,10 +248,16 @@ function CreateEntryForm({ onCreated }: FeedbackScreenEntryModalProps) {
|
|
|
242
248
|
setSubmitting(true);
|
|
243
249
|
|
|
244
250
|
try {
|
|
251
|
+
const metadata = await collectEntryMetadata(
|
|
252
|
+
collectMetadata,
|
|
253
|
+
kind,
|
|
254
|
+
collectStandardMetadata,
|
|
255
|
+
);
|
|
245
256
|
const entryId = await createEntry({
|
|
246
257
|
kind,
|
|
247
258
|
title,
|
|
248
259
|
body,
|
|
260
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
249
261
|
});
|
|
250
262
|
|
|
251
263
|
onCreated(entryId);
|
|
@@ -382,6 +394,7 @@ function EntryDetail({ entryId, onBack }: FeedbackScreenEntryDetailProps) {
|
|
|
382
394
|
const comments = hooks.useComments({ entryId, sort: commentSort });
|
|
383
395
|
const createComment = hooks.useCreateComment();
|
|
384
396
|
const [body, setBody] = useState("");
|
|
397
|
+
const [showMetadata, setShowMetadata] = useState(false);
|
|
385
398
|
|
|
386
399
|
const visibleComments = useMemo(
|
|
387
400
|
() => transformComments?.(comments.results) ?? comments.results,
|
|
@@ -422,6 +435,25 @@ function EntryDetail({ entryId, onBack }: FeedbackScreenEntryDetailProps) {
|
|
|
422
435
|
</FeedbackEntry.Content>
|
|
423
436
|
</FeedbackEntry.Root>
|
|
424
437
|
|
|
438
|
+
{entry.metadata !== undefined && (
|
|
439
|
+
<div>
|
|
440
|
+
<button
|
|
441
|
+
type="button"
|
|
442
|
+
className="cf-button"
|
|
443
|
+
onClick={() => setShowMetadata(true)}
|
|
444
|
+
>
|
|
445
|
+
{messages.metadata.view}
|
|
446
|
+
</button>
|
|
447
|
+
</div>
|
|
448
|
+
)}
|
|
449
|
+
|
|
450
|
+
{showMetadata && entry.metadata !== undefined && (
|
|
451
|
+
<MetadataDialog
|
|
452
|
+
metadata={entry.metadata}
|
|
453
|
+
onClose={() => setShowMetadata(false)}
|
|
454
|
+
/>
|
|
455
|
+
)}
|
|
456
|
+
|
|
425
457
|
<section className="cf-discussion">
|
|
426
458
|
<h3>{messages.comments.title}</h3>
|
|
427
459
|
<FeedbackForm.Root
|
|
@@ -473,6 +505,61 @@ function EntryDetail({ entryId, onBack }: FeedbackScreenEntryDetailProps) {
|
|
|
473
505
|
);
|
|
474
506
|
}
|
|
475
507
|
|
|
508
|
+
function MetadataDialog({
|
|
509
|
+
metadata,
|
|
510
|
+
onClose,
|
|
511
|
+
}: {
|
|
512
|
+
metadata: FeedbackMetadata;
|
|
513
|
+
onClose: () => void;
|
|
514
|
+
}) {
|
|
515
|
+
const { messages } = useFeedbackUi();
|
|
516
|
+
const sections = [
|
|
517
|
+
[messages.metadata.standard, metadata.standard],
|
|
518
|
+
[messages.metadata.additional, metadata.additional],
|
|
519
|
+
] as const;
|
|
520
|
+
const hasValues = sections.some(
|
|
521
|
+
([, values]) => values !== undefined && Object.keys(values).length > 0,
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
return (
|
|
525
|
+
<div className="cf-confirm-backdrop" onMouseDown={onClose}>
|
|
526
|
+
<div
|
|
527
|
+
className="cf-confirm cf-metadata-dialog"
|
|
528
|
+
role="dialog"
|
|
529
|
+
aria-modal="true"
|
|
530
|
+
aria-labelledby="cf-metadata-title"
|
|
531
|
+
onMouseDown={(event) => event.stopPropagation()}
|
|
532
|
+
>
|
|
533
|
+
<h3 id="cf-metadata-title">{messages.metadata.title}</h3>
|
|
534
|
+
{hasValues ? (
|
|
535
|
+
sections.map(([label, values]) =>
|
|
536
|
+
values === undefined || Object.keys(values).length === 0 ? null : (
|
|
537
|
+
<section key={label} className="cf-metadata-section">
|
|
538
|
+
<h4>{label}</h4>
|
|
539
|
+
<dl>
|
|
540
|
+
{Object.entries(values).map(([key, value]) => (
|
|
541
|
+
<div key={key} className="cf-metadata-row">
|
|
542
|
+
<dt>{formatMetadataKey(key)}</dt>
|
|
543
|
+
<dd>{String(value)}</dd>
|
|
544
|
+
</div>
|
|
545
|
+
))}
|
|
546
|
+
</dl>
|
|
547
|
+
</section>
|
|
548
|
+
),
|
|
549
|
+
)
|
|
550
|
+
) : (
|
|
551
|
+
<p>{messages.metadata.empty}</p>
|
|
552
|
+
)}
|
|
553
|
+
<div className="cf-inline-actions">
|
|
554
|
+
<button type="button" className="cf-button" onClick={onClose}>
|
|
555
|
+
{messages.metadata.close}
|
|
556
|
+
</button>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
476
563
|
function CommentBranch({ comment, entryId }: FeedbackScreenCommentBranchProps) {
|
|
477
564
|
const { hooks, maxCommentDepth, renderActor } = useFeedbackBody();
|
|
478
565
|
const { messages } = useFeedbackUi();
|
package/src/react/styles.css
CHANGED
|
@@ -297,3 +297,38 @@
|
|
|
297
297
|
color: var(--cf-muted);
|
|
298
298
|
line-height: 1.5;
|
|
299
299
|
}
|
|
300
|
+
|
|
301
|
+
:where(.cf-board) :where(.cf-metadata-dialog) {
|
|
302
|
+
width: min(100%, 560px);
|
|
303
|
+
max-height: min(80vh, 720px);
|
|
304
|
+
overflow: auto;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
:where(.cf-board) :where(.cf-metadata-dialog h3),
|
|
308
|
+
:where(.cf-board) :where(.cf-metadata-dialog h4),
|
|
309
|
+
:where(.cf-board) :where(.cf-metadata-dialog dl) {
|
|
310
|
+
margin: 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
:where(.cf-board) :where(.cf-metadata-section) {
|
|
314
|
+
display: grid;
|
|
315
|
+
gap: 8px;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
:where(.cf-board) :where(.cf-metadata-row) {
|
|
319
|
+
display: grid;
|
|
320
|
+
grid-template-columns: minmax(120px, 0.8fr) minmax(0, 1.2fr);
|
|
321
|
+
gap: 12px;
|
|
322
|
+
padding: 8px 0;
|
|
323
|
+
border-bottom: 1px solid var(--cf-border);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
:where(.cf-board) :where(.cf-metadata-row dt) {
|
|
327
|
+
color: var(--cf-muted);
|
|
328
|
+
font-weight: 600;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
:where(.cf-board) :where(.cf-metadata-row dd) {
|
|
332
|
+
min-width: 0;
|
|
333
|
+
overflow-wrap: anywhere;
|
|
334
|
+
}
|
|
@@ -22,6 +22,10 @@ export function FeedbackBodyProvider({
|
|
|
22
22
|
enabledKinds,
|
|
23
23
|
maxCommentDepth,
|
|
24
24
|
debounceDuration,
|
|
25
|
+
collectMetadata,
|
|
26
|
+
collectStandardMetadata,
|
|
27
|
+
transformComments,
|
|
28
|
+
renderActor,
|
|
25
29
|
children,
|
|
26
30
|
}: PropsWithChildren<FeedbackScreenProviderProps>) {
|
|
27
31
|
const [selectedEntryId, setSelectedEntryId] = useState<string | null>(null);
|
|
@@ -60,6 +64,10 @@ export function FeedbackBodyProvider({
|
|
|
60
64
|
enabledKinds,
|
|
61
65
|
maxCommentDepth,
|
|
62
66
|
debounceDuration,
|
|
67
|
+
collectMetadata,
|
|
68
|
+
collectStandardMetadata,
|
|
69
|
+
transformComments,
|
|
70
|
+
renderActor,
|
|
63
71
|
} satisfies FeedbackScreenBodyContextValue;
|
|
64
72
|
|
|
65
73
|
return (
|
package/src/shared/messages.ts
CHANGED
|
@@ -41,6 +41,14 @@ export const englishFeedbackMessages: FeedbackMessages = {
|
|
|
41
41
|
open: "Open",
|
|
42
42
|
back: "Go back",
|
|
43
43
|
},
|
|
44
|
+
metadata: {
|
|
45
|
+
view: "View metadata",
|
|
46
|
+
title: "Diagnostic metadata",
|
|
47
|
+
standard: "Standard",
|
|
48
|
+
additional: "Additional",
|
|
49
|
+
empty: "No metadata values were collected.",
|
|
50
|
+
close: "Close",
|
|
51
|
+
},
|
|
44
52
|
form: {
|
|
45
53
|
kind: "Type",
|
|
46
54
|
title: "Title",
|
|
@@ -94,6 +102,7 @@ export function mergeFeedbackMessages(
|
|
|
94
102
|
...overrides.statuses,
|
|
95
103
|
},
|
|
96
104
|
entry: { ...englishFeedbackMessages.entry, ...overrides.entry },
|
|
105
|
+
metadata: { ...englishFeedbackMessages.metadata, ...overrides.metadata },
|
|
97
106
|
form: { ...englishFeedbackMessages.form, ...overrides.form },
|
|
98
107
|
comments: { ...englishFeedbackMessages.comments, ...overrides.comments },
|
|
99
108
|
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EntryKind,
|
|
3
|
+
FeedbackMetadata,
|
|
4
|
+
FeedbackMetadataValue,
|
|
5
|
+
} from "convex-feedback";
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
CollectMetadata,
|
|
9
|
+
EnabledStandardMetadataSelection,
|
|
10
|
+
MetadataCollectionContext,
|
|
11
|
+
MetadataSource,
|
|
12
|
+
StandardMetadataKey,
|
|
13
|
+
} from "./types/metadata.js";
|
|
14
|
+
|
|
15
|
+
export type StandardMetadataCollector = (
|
|
16
|
+
selection: EnabledStandardMetadataSelection,
|
|
17
|
+
) => Partial<Record<StandardMetadataKey, FeedbackMetadataValue>>;
|
|
18
|
+
|
|
19
|
+
async function resolveAdditionalMetadata(
|
|
20
|
+
source: MetadataSource | undefined,
|
|
21
|
+
context: MetadataCollectionContext,
|
|
22
|
+
): Promise<Record<string, FeedbackMetadataValue>> {
|
|
23
|
+
if (source === undefined) return {};
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return typeof source === "function" ? await source(context) : source;
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function collectEntryMetadata(
|
|
33
|
+
config: CollectMetadata | undefined,
|
|
34
|
+
kind: EntryKind,
|
|
35
|
+
collectStandard: StandardMetadataCollector,
|
|
36
|
+
): Promise<FeedbackMetadata | undefined> {
|
|
37
|
+
if (config === undefined || config === false) return undefined;
|
|
38
|
+
|
|
39
|
+
const root = config === true ? { standard: true as const } : config;
|
|
40
|
+
const kindConfig = root.kinds?.[kind];
|
|
41
|
+
if (kindConfig === false) return undefined;
|
|
42
|
+
|
|
43
|
+
const kindOptions =
|
|
44
|
+
kindConfig === true ? { standard: true as const } : kindConfig;
|
|
45
|
+
const standardSelection = kindOptions?.standard ?? root.standard ?? false;
|
|
46
|
+
const context = { kind } satisfies MetadataCollectionContext;
|
|
47
|
+
const [globalAdditional, kindAdditional] = await Promise.all([
|
|
48
|
+
resolveAdditionalMetadata(root.additional, context),
|
|
49
|
+
resolveAdditionalMetadata(kindOptions?.additional, context),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
let standard: FeedbackMetadata["standard"];
|
|
53
|
+
try {
|
|
54
|
+
standard =
|
|
55
|
+
standardSelection === false
|
|
56
|
+
? undefined
|
|
57
|
+
: collectStandard(standardSelection);
|
|
58
|
+
} catch {
|
|
59
|
+
standard = undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const additional = { ...globalAdditional, ...kindAdditional };
|
|
63
|
+
const hasStandard =
|
|
64
|
+
standard !== undefined && Object.keys(standard).length > 0;
|
|
65
|
+
const hasAdditional = Object.keys(additional).length > 0;
|
|
66
|
+
|
|
67
|
+
if (!hasStandard && !hasAdditional) return undefined;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...(hasStandard ? { standard } : {}),
|
|
71
|
+
...(hasAdditional ? { additional } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function formatMetadataKey(key: string): string {
|
|
76
|
+
const words = key
|
|
77
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
78
|
+
.replace(/[_-]+/g, " ")
|
|
79
|
+
.trim();
|
|
80
|
+
|
|
81
|
+
return words.length === 0
|
|
82
|
+
? key
|
|
83
|
+
: words.charAt(0).toLocaleUpperCase("en-US") + words.slice(1);
|
|
84
|
+
}
|
|
@@ -4,6 +4,7 @@ export type * from "./context.js";
|
|
|
4
4
|
export type * from "./entry.js";
|
|
5
5
|
export type * from "./form.js";
|
|
6
6
|
export type * from "./messages.js";
|
|
7
|
+
export type * from "./metadata.js";
|
|
7
8
|
export type * from "./render.js";
|
|
8
9
|
export type * from "./screen.js";
|
|
9
10
|
export type * from "./theme.js";
|
|
@@ -67,6 +67,27 @@ export interface FeedbackMessages {
|
|
|
67
67
|
back: string;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
/** Moderator-only diagnostic metadata copy. */
|
|
71
|
+
metadata: {
|
|
72
|
+
/** Action that opens the metadata viewer. */
|
|
73
|
+
view: string;
|
|
74
|
+
|
|
75
|
+
/** Metadata viewer heading. */
|
|
76
|
+
title: string;
|
|
77
|
+
|
|
78
|
+
/** Heading for automatically collected values. */
|
|
79
|
+
standard: string;
|
|
80
|
+
|
|
81
|
+
/** Heading for host-provided values. */
|
|
82
|
+
additional: string;
|
|
83
|
+
|
|
84
|
+
/** Empty state for a metadata object without values. */
|
|
85
|
+
empty: string;
|
|
86
|
+
|
|
87
|
+
/** Closes the metadata viewer. */
|
|
88
|
+
close: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
70
91
|
/** Create-entry form copy. */
|
|
71
92
|
form: {
|
|
72
93
|
/** Entry-kind field label. */
|
|
@@ -167,6 +188,9 @@ export interface FeedbackMessageOverrides {
|
|
|
167
188
|
/** Entry action/counter copy overrides. */
|
|
168
189
|
entry?: Partial<FeedbackMessages["entry"]> | undefined;
|
|
169
190
|
|
|
191
|
+
/** Diagnostic metadata copy overrides. */
|
|
192
|
+
metadata?: Partial<FeedbackMessages["metadata"]> | undefined;
|
|
193
|
+
|
|
170
194
|
/** Create-entry form copy overrides. */
|
|
171
195
|
form?: Partial<FeedbackMessages["form"]> | undefined;
|
|
172
196
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { EntryKind, FeedbackMetadataValue } from "convex-feedback";
|
|
2
|
+
|
|
3
|
+
export type StandardMetadataKey =
|
|
4
|
+
| "platform"
|
|
5
|
+
| "userAgent"
|
|
6
|
+
| "language"
|
|
7
|
+
| "timezone"
|
|
8
|
+
| "screenWidth"
|
|
9
|
+
| "screenHeight"
|
|
10
|
+
| "viewportWidth"
|
|
11
|
+
| "viewportHeight"
|
|
12
|
+
| "devicePixelRatio"
|
|
13
|
+
| "osVersion"
|
|
14
|
+
| "deviceModel"
|
|
15
|
+
| "pixelRatio"
|
|
16
|
+
| "fontScale"
|
|
17
|
+
| "appVersion"
|
|
18
|
+
| "buildNumber"
|
|
19
|
+
| "applicationId"
|
|
20
|
+
| "expoRuntimeVersion"
|
|
21
|
+
| "executionEnvironment";
|
|
22
|
+
|
|
23
|
+
export type EnabledStandardMetadataSelection =
|
|
24
|
+
true | readonly StandardMetadataKey[];
|
|
25
|
+
|
|
26
|
+
export type StandardMetadataSelection =
|
|
27
|
+
false | EnabledStandardMetadataSelection;
|
|
28
|
+
|
|
29
|
+
export interface MetadataCollectionContext {
|
|
30
|
+
kind: EntryKind;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type AdditionalMetadata = Record<string, FeedbackMetadataValue>;
|
|
34
|
+
|
|
35
|
+
export type MetadataSource =
|
|
36
|
+
| AdditionalMetadata
|
|
37
|
+
| ((
|
|
38
|
+
context: MetadataCollectionContext,
|
|
39
|
+
) => AdditionalMetadata | Promise<AdditionalMetadata>);
|
|
40
|
+
|
|
41
|
+
export interface MetadataCollectionOptions {
|
|
42
|
+
/** Standard platform fields to collect. `true` selects platform defaults. */
|
|
43
|
+
standard?: StandardMetadataSelection;
|
|
44
|
+
|
|
45
|
+
/** Static or submission-time host metadata. */
|
|
46
|
+
additional?: MetadataSource;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface MetadataCollectionConfig extends MetadataCollectionOptions {
|
|
50
|
+
/** Per-entry-kind overrides. `false` disables all collection for that kind. */
|
|
51
|
+
kinds?: Partial<Record<EntryKind, boolean | MetadataCollectionOptions>>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Metadata collection configuration accepted by prebuilt feedback screens. */
|
|
55
|
+
export type CollectMetadata = boolean | MetadataCollectionConfig;
|
|
@@ -9,6 +9,8 @@ import type { FeedbackHooks } from "convex-feedback/react";
|
|
|
9
9
|
import type { ReactNode } from "react";
|
|
10
10
|
|
|
11
11
|
import type { FeedbackColorProps, FeedbackProviderProps } from "./context.js";
|
|
12
|
+
import type { CollectMetadata } from "./metadata.js";
|
|
13
|
+
import type { StandardMetadataCollector } from "../metadata.js";
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Presentation-only transform applied to one currently loaded comment page.
|
|
@@ -87,6 +89,12 @@ export interface FeedbackScreenBaseProps {
|
|
|
87
89
|
* @default 300
|
|
88
90
|
*/
|
|
89
91
|
debounceDuration?: number;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Enables creation-time diagnostic metadata collection globally and/or by
|
|
95
|
+
* entry kind. Disabled by default.
|
|
96
|
+
*/
|
|
97
|
+
collectMetadata?: CollectMetadata;
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
export interface FeedbackScreenRootProps
|
|
@@ -98,8 +106,11 @@ export interface FeedbackScreenRootProps
|
|
|
98
106
|
|
|
99
107
|
export interface FeedbackScreenProviderProps
|
|
100
108
|
extends
|
|
101
|
-
Required<FeedbackScreenBaseProps
|
|
102
|
-
FeedbackScreenTransformationProps {
|
|
109
|
+
Required<Omit<FeedbackScreenBaseProps, "collectMetadata">>,
|
|
110
|
+
FeedbackScreenTransformationProps {
|
|
111
|
+
collectMetadata?: CollectMetadata;
|
|
112
|
+
collectStandardMetadata: StandardMetadataCollector;
|
|
113
|
+
}
|
|
103
114
|
|
|
104
115
|
export interface FeedbackScreenBodyContextValue
|
|
105
116
|
extends FeedbackScreenTransformationProps, FeedbackScreenProviderProps {
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
|
|
3
|
-
import { useFeedbackBody } from "../../../shared/context/FeedbackBodyProvider";
|
|
4
|
-
import { useFeedbackUi } from "../../../shared/context/FeedbackProvider";
|
|
5
|
-
import type { FeedbackScreenReplyListProps } from "../../../shared/types";
|
|
6
|
-
import { Button } from "./Button";
|
|
7
|
-
import { CommentBranch } from "./CommentBranch";
|
|
8
|
-
import { Comment } from "./primitives";
|
|
9
|
-
|
|
10
|
-
export function ReplyList({
|
|
11
|
-
entryId,
|
|
12
|
-
parentCommentId,
|
|
13
|
-
}: FeedbackScreenReplyListProps) {
|
|
14
|
-
const { hooks, commentSort, transformComments } = useFeedbackBody();
|
|
15
|
-
const { messages } = useFeedbackUi();
|
|
16
|
-
|
|
17
|
-
const replies = hooks.useComments({
|
|
18
|
-
entryId,
|
|
19
|
-
parentCommentId,
|
|
20
|
-
sort: commentSort,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const visible = useMemo(
|
|
24
|
-
() => transformComments?.(replies.results) ?? replies.results,
|
|
25
|
-
[replies.results, transformComments],
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<Comment.Children>
|
|
30
|
-
{visible.map((reply) => (
|
|
31
|
-
<CommentBranch key={reply.id} comment={reply} entryId={entryId} />
|
|
32
|
-
))}
|
|
33
|
-
{replies.status === "CanLoadMore" && (
|
|
34
|
-
<Button
|
|
35
|
-
label={messages.comments.loadMore}
|
|
36
|
-
onPress={() => replies.loadMore(hooks.pageSizes.replies)}
|
|
37
|
-
/>
|
|
38
|
-
)}
|
|
39
|
-
</Comment.Children>
|
|
40
|
-
);
|
|
41
|
-
}
|