@livo-tv/sdk 1.10.0 → 1.10.1-pr.6.4bf5bbd

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 CHANGED
@@ -11,7 +11,25 @@ import "@livo-tv/sdk/styles.css";
11
11
  <LivoStudio guestToken="..." theme={{ primary: "#2563eb" }} />
12
12
  ```
13
13
 
14
- `@livo-tv/sdk` and `@livo-tv/sdk/studio` are client entries (the published bundles start with `"use client"`). Import them from `"use client"` files in Next.js App Router — do not import `@livo-tv/sdk/studio` from a Server Component page. Tailwind hosts must `@source` `node_modules/@livo-tv/sdk/dist/*.{js,cjs,mjs}` like `@livo-tv/blocks`. RealtimeKit is an optional peer (`@cloudflare/realtimekit`). Root `<LivoStudio>` remains the iframe fallback.
14
+ `@livo-tv/sdk`, `@livo-tv/sdk/studio`, and `@livo-tv/sdk/condo` (`LivoCommunity`) are client entries (the published bundles start with `"use client"`). Import them from `"use client"` files in Next.js App Router — do not import those entries from a Server Component page. Tailwind hosts must `@source` `node_modules/@livo-tv/sdk/dist/*.{js,cjs,mjs}` like `@livo-tv/blocks`. RealtimeKit is an optional peer (`@cloudflare/realtimekit`). Root `<LivoStudio>` remains the iframe fallback.
15
+
16
+ Community (condo) — comments on every video, live Q&A only while a stream or webinar is `preview` / `public`. After the event, Q&A stays closed. Comments stay closed unless the organizer sets `commentsAfterEnd`. Pass a public `apiUrl` for viewers (`mode="viewer"`, polls). Pass JWT / `X-Api-Key` headers and `mode="control"` for settings, pin/hide/delete, and Q&A highlight/dismiss/answer. Unregistered posters send `displayName`, optional `picture` URL, and `guestId`.
17
+
18
+ ```tsx
19
+ import { LivoCommunity } from "@livo-tv/sdk/condo";
20
+ import "@livo-tv/sdk/styles.css";
21
+
22
+ <LivoCommunity
23
+ apiUrl="https://api.livo.tv"
24
+ targetKind="stream"
25
+ targetId={stream.id}
26
+ flags={stream.community}
27
+ mode="viewer"
28
+ displayName="Ada"
29
+ picture="https://cdn.example/ada.jpg"
30
+ guestId="guest-ada"
31
+ />;
32
+ ```
15
33
 
16
34
  Server (Node / Workers):
17
35
 
@@ -0,0 +1,648 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/condo/community.tsx
8
+
9
+ // src/condo/types.ts
10
+ function isRecord(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+ function asString(value) {
14
+ return typeof value === "string" ? value : null;
15
+ }
16
+ function asNumber(value) {
17
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
18
+ }
19
+ function parseCommunityFlags(value, kind) {
20
+ if (!isRecord(value)) return null;
21
+ if (kind === "vod") {
22
+ if (typeof value.commentsEnabled !== "boolean") return null;
23
+ return {
24
+ commentsEnabled: value.commentsEnabled,
25
+ commentsOpen: typeof value.commentsOpen === "boolean" ? value.commentsOpen : value.commentsEnabled
26
+ };
27
+ }
28
+ if (typeof value.commentsEnabled !== "boolean" || typeof value.commentsAfterEnd !== "boolean" || typeof value.qaEnabled !== "boolean") {
29
+ return null;
30
+ }
31
+ return {
32
+ commentsEnabled: value.commentsEnabled,
33
+ commentsAfterEnd: value.commentsAfterEnd,
34
+ commentsOpen: typeof value.commentsOpen === "boolean" ? value.commentsOpen : value.commentsEnabled,
35
+ qaEnabled: value.qaEnabled,
36
+ qaOpen: typeof value.qaOpen === "boolean" ? value.qaOpen : false
37
+ };
38
+ }
39
+ function parseAnswer(value) {
40
+ if (!isRecord(value)) return null;
41
+ const body = asString(value.body);
42
+ const answeredAt = asNumber(value.answeredAt);
43
+ if (!body || answeredAt == null) return null;
44
+ return {
45
+ body,
46
+ displayName: asString(value.displayName),
47
+ picture: asString(value.picture),
48
+ answeredAt
49
+ };
50
+ }
51
+ function parseCommunityItem(value) {
52
+ if (!isRecord(value)) return null;
53
+ const id = asString(value.id);
54
+ const body = asString(value.body);
55
+ const displayName = asString(value.displayName);
56
+ const guestId = asString(value.guestId);
57
+ const type = value.type === "question" ? "question" : "comment";
58
+ const createdAt = asNumber(value.createdAt);
59
+ if (!id || !body || !displayName || !guestId || createdAt == null) {
60
+ return null;
61
+ }
62
+ const replies = Array.isArray(value.replies) ? value.replies.map((row) => parseCommunityItem(row)).filter((row) => row != null) : [];
63
+ return {
64
+ id,
65
+ type,
66
+ parentId: asString(value.parentId),
67
+ body,
68
+ displayName,
69
+ picture: asString(value.picture),
70
+ guestId,
71
+ upvoteCount: asNumber(value.upvoteCount) ?? 0,
72
+ pinnedAt: asNumber(value.pinnedAt),
73
+ highlightedAt: asNumber(value.highlightedAt),
74
+ hiddenAt: asNumber(value.hiddenAt),
75
+ dismissedAt: asNumber(value.dismissedAt),
76
+ answer: parseAnswer(value.answer),
77
+ createdAt,
78
+ updatedAt: asNumber(value.updatedAt) ?? createdAt,
79
+ replies
80
+ };
81
+ }
82
+ function parseCommunityList(value) {
83
+ if (!isRecord(value) || !Array.isArray(value.items)) {
84
+ return { items: [], nextCursor: null };
85
+ }
86
+ return {
87
+ items: value.items.map((row) => parseCommunityItem(row)).filter((row) => row != null),
88
+ nextCursor: asString(value.nextCursor)
89
+ };
90
+ }
91
+ function hasQa(flags) {
92
+ return "qaEnabled" in flags;
93
+ }
94
+
95
+ // src/condo/api.ts
96
+ var CommunityApiError = class extends Error {
97
+ status;
98
+ body;
99
+ constructor(status, message, body) {
100
+ super(message);
101
+ this.name = "CommunityApiError";
102
+ this.status = status;
103
+ this.body = body;
104
+ }
105
+ };
106
+ function collectionPath(kind, id, mode) {
107
+ const plural = kind === "stream" ? "streams" : "vods";
108
+ const prefix = mode === "viewer" ? "/public" : "";
109
+ return `${prefix}/${plural}/${encodeURIComponent(id)}/community`;
110
+ }
111
+ function createCommunityClient(options) {
112
+ const apiUrl = options.apiUrl.replace(/\/$/, "");
113
+ const fetchFn = options.fetchFn ?? fetch;
114
+ const mode = options.mode ?? "viewer";
115
+ const base = collectionPath(options.targetKind, options.targetId, mode);
116
+ async function request(path, init = {}, parse) {
117
+ const headers = new Headers(init.headers);
118
+ for (const [key, value] of Object.entries(options.headers ?? {})) {
119
+ headers.set(key, value);
120
+ }
121
+ headers.set("accept", "application/json");
122
+ if (init.body && !headers.has("content-type")) {
123
+ headers.set("content-type", "application/json");
124
+ }
125
+ const res = await fetchFn(`${apiUrl}${path}`, { ...init, headers });
126
+ const body = await res.json().catch(() => ({}));
127
+ if (!res.ok) {
128
+ const error = typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" ? body.error : `Livo community ${res.status}`;
129
+ throw new CommunityApiError(res.status, error, body);
130
+ }
131
+ return parse(body);
132
+ }
133
+ return {
134
+ list(input = {}) {
135
+ const params = new URLSearchParams();
136
+ if (input.type) params.set("type", input.type);
137
+ if (input.q) params.set("q", input.q);
138
+ if (input.cursor) params.set("cursor", input.cursor);
139
+ const query = params.toString();
140
+ return request(
141
+ `${base}${query ? `?${query}` : ""}`,
142
+ {},
143
+ (value) => parseCommunityList(value)
144
+ );
145
+ },
146
+ create(input) {
147
+ return request(
148
+ base,
149
+ { method: "POST", body: JSON.stringify(input) },
150
+ (value) => {
151
+ const record = typeof value === "object" && value !== null && "item" in value ? value.item : value;
152
+ const item = parseCommunityItem(record);
153
+ if (!item) throw new CommunityApiError(500, "Invalid item", value);
154
+ return item;
155
+ }
156
+ );
157
+ },
158
+ patch(itemId, input) {
159
+ return request(
160
+ `${base}/${encodeURIComponent(itemId)}`,
161
+ { method: "PATCH", body: JSON.stringify(input) },
162
+ (value) => {
163
+ const record = typeof value === "object" && value !== null && "item" in value ? value.item : value;
164
+ const item = parseCommunityItem(record);
165
+ if (!item) throw new CommunityApiError(500, "Invalid item", value);
166
+ return item;
167
+ }
168
+ );
169
+ },
170
+ upvote(itemId, guestId) {
171
+ return request(
172
+ `${base}/${encodeURIComponent(itemId)}/upvote`,
173
+ {
174
+ method: "POST",
175
+ body: JSON.stringify({ guestId })
176
+ },
177
+ (value) => {
178
+ const record = typeof value === "object" && value !== null && "item" in value ? value.item : value;
179
+ const item = parseCommunityItem(record);
180
+ if (!item) throw new CommunityApiError(500, "Invalid item", value);
181
+ return item;
182
+ }
183
+ );
184
+ },
185
+ patchSettings(input) {
186
+ const plural = options.targetKind === "stream" ? "streams" : "vods";
187
+ return request(
188
+ `/${plural}/${encodeURIComponent(options.targetId)}`,
189
+ { method: "PATCH", body: JSON.stringify(input) },
190
+ (value) => {
191
+ const record = typeof value === "object" && value !== null ? value : null;
192
+ const nested = record && typeof record.stream === "object" && record.stream ? record.stream.community : record && typeof record.vod === "object" && record.vod ? record.vod.community : record?.community;
193
+ return parseCommunityFlags(nested, options.targetKind);
194
+ }
195
+ );
196
+ }
197
+ };
198
+ }
199
+
200
+ // src/condo/gates.ts
201
+ function commentsOpen(flags) {
202
+ return flags.commentsOpen;
203
+ }
204
+ function qaOpen(flags) {
205
+ return hasQa(flags) && flags.qaOpen;
206
+ }
207
+ function canPost(flags, type) {
208
+ if (type === "question") return qaOpen(flags);
209
+ return commentsOpen(flags);
210
+ }
211
+ function showQaTab(flags) {
212
+ return hasQa(flags) && flags.qaEnabled;
213
+ }
214
+
215
+ // src/condo/identity.ts
216
+ var STORAGE_KEY = "livo-community-guest-id";
217
+ function randomGuestId() {
218
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
219
+ return crypto.randomUUID();
220
+ }
221
+ return `guest-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
222
+ }
223
+ function communityGuestId(storage) {
224
+ const store = storage ?? (typeof localStorage === "undefined" ? void 0 : localStorage);
225
+ if (!store) return randomGuestId();
226
+ const existing = store.getItem(STORAGE_KEY)?.trim();
227
+ if (existing) return existing;
228
+ const created = randomGuestId();
229
+ store.setItem(STORAGE_KEY, created);
230
+ return created;
231
+ }
232
+
233
+ // src/condo/labels.ts
234
+ var communityLabels = {
235
+ comments: "Comments",
236
+ qa: "Q&A",
237
+ composer: "Write a comment",
238
+ questionComposer: "Ask a question",
239
+ reply: "Reply",
240
+ send: "Send",
241
+ name: "Display name",
242
+ namePlaceholder: "Your name",
243
+ closed: "Comments are closed",
244
+ qaClosed: "Q&A is closed",
245
+ emptyComments: "No comments yet",
246
+ emptyQuestions: "No questions yet",
247
+ pin: "Pin",
248
+ unpin: "Unpin",
249
+ hide: "Hide",
250
+ unhide: "Unhide",
251
+ remove: "Delete",
252
+ highlight: "Highlight",
253
+ dismiss: "Dismiss",
254
+ answer: "Answer",
255
+ upvote: "Upvote",
256
+ settings: "Community",
257
+ commentsEnabled: "Comments",
258
+ commentsAfterEnd: "Keep comments open after the event",
259
+ qaEnabled: "Live Q&A",
260
+ commentsHelp: "Viewers can comment on this video.",
261
+ commentsAfterEndHelp: "When off, comments close as soon as the live event ends.",
262
+ qaHelp: "Questions are only accepted while the event is live."
263
+ };
264
+ function mergeLabels(partial) {
265
+ return { ...communityLabels, ...partial };
266
+ }
267
+ function ItemRow({
268
+ item,
269
+ mode,
270
+ labels,
271
+ onReply,
272
+ onPatch,
273
+ onUpvote
274
+ }) {
275
+ const [answer, setAnswer] = react.useState("");
276
+ return /* @__PURE__ */ jsxRuntime.jsxs(
277
+ "article",
278
+ {
279
+ className: "livo-condo-item",
280
+ "data-highlighted": item.highlightedAt ? "true" : void 0,
281
+ "data-hidden": item.hiddenAt ? "true" : void 0,
282
+ children: [
283
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "livo-condo-item__meta", children: [
284
+ item.picture ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.picture, alt: "", className: "livo-condo-avatar" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "livo-condo-avatar livo-condo-avatar--fallback", children: item.displayName.slice(0, 1).toUpperCase() }),
285
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: item.displayName }),
286
+ item.pinnedAt ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "livo-condo-chip", children: "Pinned" }) : null,
287
+ item.highlightedAt ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "livo-condo-chip", children: "Live" }) : null
288
+ ] }),
289
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "livo-condo-item__body", children: item.body }),
290
+ item.answer ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "livo-condo-answer", children: [
291
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
292
+ item.answer.displayName ?? "Host",
293
+ ":"
294
+ ] }),
295
+ " ",
296
+ item.answer.body
297
+ ] }) : null,
298
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "livo-condo-item__actions", children: [
299
+ item.type === "question" && onUpvote ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => onUpvote(item.id), children: [
300
+ labels.upvote,
301
+ " ",
302
+ item.upvoteCount
303
+ ] }) : null,
304
+ item.type === "comment" && !item.parentId && onReply ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => onReply(item.id), children: labels.reply }) : null,
305
+ mode === "control" && item.type === "comment" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
306
+ /* @__PURE__ */ jsxRuntime.jsx(
307
+ "button",
308
+ {
309
+ type: "button",
310
+ onClick: () => onPatch(item.id, { pinned: item.pinnedAt == null }),
311
+ children: item.pinnedAt ? labels.unpin : labels.pin
312
+ }
313
+ ),
314
+ /* @__PURE__ */ jsxRuntime.jsx(
315
+ "button",
316
+ {
317
+ type: "button",
318
+ onClick: () => onPatch(item.id, { hidden: item.hiddenAt == null }),
319
+ children: item.hiddenAt ? labels.unhide : labels.hide
320
+ }
321
+ ),
322
+ /* @__PURE__ */ jsxRuntime.jsx(
323
+ "button",
324
+ {
325
+ type: "button",
326
+ onClick: () => onPatch(item.id, { deleted: true }),
327
+ children: labels.remove
328
+ }
329
+ )
330
+ ] }) : null,
331
+ mode === "control" && item.type === "question" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
332
+ /* @__PURE__ */ jsxRuntime.jsx(
333
+ "button",
334
+ {
335
+ type: "button",
336
+ onClick: () => onPatch(item.id, { highlighted: item.highlightedAt == null }),
337
+ children: labels.highlight
338
+ }
339
+ ),
340
+ /* @__PURE__ */ jsxRuntime.jsx(
341
+ "button",
342
+ {
343
+ type: "button",
344
+ onClick: () => onPatch(item.id, { dismissed: item.dismissedAt == null }),
345
+ children: labels.dismiss
346
+ }
347
+ )
348
+ ] }) : null
349
+ ] }),
350
+ mode === "control" && item.type === "question" ? /* @__PURE__ */ jsxRuntime.jsxs(
351
+ "form",
352
+ {
353
+ className: "livo-condo-answer-form",
354
+ onSubmit: (event) => {
355
+ event.preventDefault();
356
+ const body = answer.trim();
357
+ if (!body) return;
358
+ onPatch(item.id, { answer: { body } });
359
+ setAnswer("");
360
+ },
361
+ children: [
362
+ /* @__PURE__ */ jsxRuntime.jsx(
363
+ "input",
364
+ {
365
+ value: answer,
366
+ onChange: (event) => setAnswer(event.target.value),
367
+ placeholder: labels.answer,
368
+ "aria-label": labels.answer
369
+ }
370
+ ),
371
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", children: labels.send })
372
+ ]
373
+ }
374
+ ) : null,
375
+ item.replies?.map((reply) => /* @__PURE__ */ jsxRuntime.jsx(
376
+ ItemRow,
377
+ {
378
+ item: reply,
379
+ mode,
380
+ labels,
381
+ onPatch
382
+ },
383
+ reply.id
384
+ ))
385
+ ]
386
+ }
387
+ );
388
+ }
389
+ function LivoCommunity({
390
+ apiUrl,
391
+ targetKind,
392
+ targetId,
393
+ flags,
394
+ mode = "viewer",
395
+ fetchFn,
396
+ headers,
397
+ displayName: displayNameProp,
398
+ picture,
399
+ guestId: guestIdProp,
400
+ pollMs = 5e3,
401
+ labels: labelsProp,
402
+ className,
403
+ onFlagsChange
404
+ }) {
405
+ const labels = react.useMemo(() => mergeLabels(labelsProp), [labelsProp]);
406
+ const client = react.useMemo(
407
+ () => createCommunityClient({
408
+ apiUrl,
409
+ targetKind,
410
+ targetId,
411
+ mode,
412
+ fetchFn,
413
+ headers
414
+ }),
415
+ [apiUrl, fetchFn, headers, mode, targetId, targetKind]
416
+ );
417
+ const guestId = guestIdProp ?? communityGuestId();
418
+ const [tab, setTab] = react.useState("comment");
419
+ const [items, setItems] = react.useState([]);
420
+ const [body, setBody] = react.useState("");
421
+ const [name, setName] = react.useState(displayNameProp ?? "");
422
+ const [replyTo, setReplyTo] = react.useState(null);
423
+ const [error, setError] = react.useState(null);
424
+ const [settings, setSettings] = react.useState(flags);
425
+ react.useEffect(() => {
426
+ setSettings(flags);
427
+ }, [flags]);
428
+ react.useEffect(() => {
429
+ if (!showQaTab(settings) && tab === "question") setTab("comment");
430
+ }, [settings, tab]);
431
+ const load = react.useCallback(async () => {
432
+ const listed = await client.list({
433
+ type: showQaTab(settings) ? tab : "comment"
434
+ });
435
+ setItems(listed.items);
436
+ }, [client, settings, tab]);
437
+ react.useEffect(() => {
438
+ let cancelled = false;
439
+ const tick = async () => {
440
+ try {
441
+ await load();
442
+ if (!cancelled) setError(null);
443
+ } catch (err) {
444
+ if (!cancelled) {
445
+ setError(err instanceof Error ? err.message : "Failed to load");
446
+ }
447
+ }
448
+ };
449
+ void tick();
450
+ if (pollMs <= 0) return;
451
+ const id = window.setInterval(() => void tick(), pollMs);
452
+ return () => {
453
+ cancelled = true;
454
+ window.clearInterval(id);
455
+ };
456
+ }, [load, pollMs]);
457
+ const open = tab === "question" ? qaOpen(settings) : commentsOpen(settings);
458
+ async function submit(event) {
459
+ event.preventDefault();
460
+ const text = body.trim();
461
+ const displayName = (displayNameProp ?? name).trim();
462
+ if (!text || !displayName) return;
463
+ try {
464
+ await client.create({
465
+ type: tab,
466
+ body: text,
467
+ displayName,
468
+ picture: picture ?? void 0,
469
+ guestId,
470
+ parentId: tab === "comment" ? replyTo ?? void 0 : void 0
471
+ });
472
+ setBody("");
473
+ setReplyTo(null);
474
+ await load();
475
+ setError(null);
476
+ } catch (err) {
477
+ setError(
478
+ err instanceof CommunityApiError ? err.message : "Could not post"
479
+ );
480
+ }
481
+ }
482
+ async function handlePatch(itemId, patch) {
483
+ try {
484
+ await client.patch(itemId, patch);
485
+ await load();
486
+ } catch (err) {
487
+ setError(err instanceof Error ? err.message : "Could not update");
488
+ }
489
+ }
490
+ async function handleUpvote(itemId) {
491
+ try {
492
+ await client.upvote(itemId, guestId);
493
+ await load();
494
+ } catch (err) {
495
+ setError(err instanceof Error ? err.message : "Could not upvote");
496
+ }
497
+ }
498
+ async function updateSetting(patch) {
499
+ try {
500
+ const parsed = await client.patchSettings(patch);
501
+ const next = parsed ?? { ...settings, ...patch };
502
+ setSettings(next);
503
+ onFlagsChange?.(next);
504
+ } catch (err) {
505
+ setError(err instanceof Error ? err.message : "Could not save");
506
+ }
507
+ }
508
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: ["livo-condo", className].filter(Boolean).join(" "), children: [
509
+ mode === "control" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "livo-condo-settings", children: [
510
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: labels.settings }),
511
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
512
+ /* @__PURE__ */ jsxRuntime.jsx(
513
+ "input",
514
+ {
515
+ type: "checkbox",
516
+ checked: settings.commentsEnabled,
517
+ onChange: (event) => void updateSetting({ commentsEnabled: event.target.checked })
518
+ }
519
+ ),
520
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
521
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: labels.commentsEnabled }),
522
+ /* @__PURE__ */ jsxRuntime.jsx("small", { children: labels.commentsHelp })
523
+ ] })
524
+ ] }),
525
+ hasQa(settings) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
526
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
527
+ /* @__PURE__ */ jsxRuntime.jsx(
528
+ "input",
529
+ {
530
+ type: "checkbox",
531
+ checked: settings.commentsAfterEnd,
532
+ onChange: (event) => void updateSetting({
533
+ commentsAfterEnd: event.target.checked
534
+ })
535
+ }
536
+ ),
537
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
538
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: labels.commentsAfterEnd }),
539
+ /* @__PURE__ */ jsxRuntime.jsx("small", { children: labels.commentsAfterEndHelp })
540
+ ] })
541
+ ] }),
542
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
543
+ /* @__PURE__ */ jsxRuntime.jsx(
544
+ "input",
545
+ {
546
+ type: "checkbox",
547
+ checked: settings.qaEnabled,
548
+ onChange: (event) => void updateSetting({ qaEnabled: event.target.checked })
549
+ }
550
+ ),
551
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
552
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: labels.qaEnabled }),
553
+ /* @__PURE__ */ jsxRuntime.jsx("small", { children: labels.qaHelp })
554
+ ] })
555
+ ] })
556
+ ] }) : null
557
+ ] }) : null,
558
+ showQaTab(settings) ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "livo-condo-tabs", role: "tablist", children: [
559
+ /* @__PURE__ */ jsxRuntime.jsx(
560
+ "button",
561
+ {
562
+ type: "button",
563
+ role: "tab",
564
+ "aria-selected": tab === "comment",
565
+ onClick: () => setTab("comment"),
566
+ children: labels.comments
567
+ }
568
+ ),
569
+ /* @__PURE__ */ jsxRuntime.jsx(
570
+ "button",
571
+ {
572
+ type: "button",
573
+ role: "tab",
574
+ "aria-selected": tab === "question",
575
+ onClick: () => setTab("question"),
576
+ children: labels.qa
577
+ }
578
+ )
579
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "livo-condo-title", children: labels.comments }),
580
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "livo-condo-list", children: items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "livo-condo-empty", children: tab === "question" ? labels.emptyQuestions : labels.emptyComments }) : items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
581
+ ItemRow,
582
+ {
583
+ item,
584
+ mode,
585
+ labels,
586
+ onReply: canPost(settings, "comment") ? setReplyTo : void 0,
587
+ onPatch: handlePatch,
588
+ onUpvote: tab === "question" ? handleUpvote : void 0
589
+ },
590
+ item.id
591
+ )) }),
592
+ open ? /* @__PURE__ */ jsxRuntime.jsxs(
593
+ "form",
594
+ {
595
+ className: "livo-condo-composer",
596
+ onSubmit: (event) => void submit(event),
597
+ children: [
598
+ displayNameProp ? null : /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
599
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "livo-sr-only", children: labels.name }),
600
+ /* @__PURE__ */ jsxRuntime.jsx(
601
+ "input",
602
+ {
603
+ value: name,
604
+ onChange: (event) => setName(event.target.value),
605
+ placeholder: labels.namePlaceholder,
606
+ required: true,
607
+ maxLength: 80
608
+ }
609
+ )
610
+ ] }),
611
+ replyTo ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "livo-condo-replying", children: [
612
+ labels.reply,
613
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setReplyTo(null), children: "\xD7" })
614
+ ] }) : null,
615
+ /* @__PURE__ */ jsxRuntime.jsx(
616
+ "textarea",
617
+ {
618
+ value: body,
619
+ onChange: (event) => setBody(event.target.value),
620
+ placeholder: tab === "question" ? labels.questionComposer : labels.composer,
621
+ required: true,
622
+ maxLength: 2e3,
623
+ rows: 3
624
+ }
625
+ ),
626
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", children: labels.send })
627
+ ]
628
+ }
629
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "livo-condo-closed", role: "status", children: tab === "question" ? labels.qaClosed : labels.closed }),
630
+ error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "livo-condo-error", role: "alert", children: error }) : null
631
+ ] });
632
+ }
633
+
634
+ exports.CommunityApiError = CommunityApiError;
635
+ exports.LivoCommunity = LivoCommunity;
636
+ exports.canPost = canPost;
637
+ exports.commentsOpen = commentsOpen;
638
+ exports.communityGuestId = communityGuestId;
639
+ exports.communityLabels = communityLabels;
640
+ exports.createCommunityClient = createCommunityClient;
641
+ exports.hasQa = hasQa;
642
+ exports.parseCommunityFlags = parseCommunityFlags;
643
+ exports.parseCommunityItem = parseCommunityItem;
644
+ exports.parseCommunityList = parseCommunityList;
645
+ exports.qaOpen = qaOpen;
646
+ exports.showQaTab = showQaTab;
647
+ //# sourceMappingURL=index.cjs.map
648
+ //# sourceMappingURL=index.cjs.map