@btst/stack 2.7.0 → 2.8.1

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.
Files changed (181) hide show
  1. package/README.md +1 -0
  2. package/dist/packages/stack/src/plugins/blog/client/components/loading/post-navigation-skeleton.cjs +13 -0
  3. package/dist/packages/stack/src/plugins/blog/client/components/loading/post-navigation-skeleton.mjs +11 -0
  4. package/dist/packages/stack/src/plugins/blog/client/components/loading/recent-posts-carousel-skeleton.cjs +17 -0
  5. package/dist/packages/stack/src/plugins/blog/client/components/loading/recent-posts-carousel-skeleton.mjs +15 -0
  6. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +18 -7
  7. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +18 -7
  8. package/dist/packages/stack/src/plugins/blog/client/components/shared/post-navigation.cjs +48 -52
  9. package/dist/packages/stack/src/plugins/blog/client/components/shared/post-navigation.mjs +49 -53
  10. package/dist/packages/stack/src/plugins/blog/client/components/shared/recent-posts-carousel.cjs +34 -37
  11. package/dist/packages/stack/src/plugins/blog/client/components/shared/recent-posts-carousel.mjs +35 -38
  12. package/dist/packages/stack/src/plugins/blog/client/hooks/blog-hooks.cjs +4 -21
  13. package/dist/packages/stack/src/plugins/blog/client/hooks/blog-hooks.mjs +4 -21
  14. package/dist/packages/stack/src/plugins/comments/api/getters.cjs +284 -0
  15. package/dist/packages/stack/src/plugins/comments/api/getters.mjs +280 -0
  16. package/dist/packages/stack/src/plugins/comments/api/mutations.cjs +118 -0
  17. package/dist/packages/stack/src/plugins/comments/api/mutations.mjs +112 -0
  18. package/dist/packages/stack/src/plugins/comments/api/plugin.cjs +335 -0
  19. package/dist/packages/stack/src/plugins/comments/api/plugin.mjs +333 -0
  20. package/dist/packages/stack/src/plugins/comments/api/query-key-defs.cjs +60 -0
  21. package/dist/packages/stack/src/plugins/comments/api/query-key-defs.mjs +55 -0
  22. package/dist/packages/stack/src/plugins/comments/api/serializers.cjs +23 -0
  23. package/dist/packages/stack/src/plugins/comments/api/serializers.mjs +21 -0
  24. package/dist/packages/stack/src/plugins/comments/client/components/comment-count.cjs +46 -0
  25. package/dist/packages/stack/src/plugins/comments/client/components/comment-count.mjs +44 -0
  26. package/dist/packages/stack/src/plugins/comments/client/components/comment-form.cjs +86 -0
  27. package/dist/packages/stack/src/plugins/comments/client/components/comment-form.mjs +84 -0
  28. package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.cjs +540 -0
  29. package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.mjs +538 -0
  30. package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.cjs +64 -0
  31. package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.internal.cjs +426 -0
  32. package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.internal.mjs +424 -0
  33. package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.mjs +62 -0
  34. package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.cjs +66 -0
  35. package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.internal.cjs +256 -0
  36. package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.internal.mjs +254 -0
  37. package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.mjs +64 -0
  38. package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.cjs +86 -0
  39. package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.internal.cjs +191 -0
  40. package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.internal.mjs +189 -0
  41. package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.mjs +84 -0
  42. package/dist/packages/stack/src/plugins/comments/client/components/shared/page-wrapper.cjs +27 -0
  43. package/dist/packages/stack/src/plugins/comments/client/components/shared/page-wrapper.mjs +25 -0
  44. package/dist/packages/stack/src/plugins/comments/client/components/shared/pagination.cjs +37 -0
  45. package/dist/packages/stack/src/plugins/comments/client/components/shared/pagination.mjs +35 -0
  46. package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.cjs +476 -0
  47. package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.mjs +464 -0
  48. package/dist/packages/stack/src/plugins/comments/client/localization/comments-moderation.cjs +67 -0
  49. package/dist/packages/stack/src/plugins/comments/client/localization/comments-moderation.mjs +65 -0
  50. package/dist/packages/stack/src/plugins/comments/client/localization/comments-my.cjs +27 -0
  51. package/dist/packages/stack/src/plugins/comments/client/localization/comments-my.mjs +25 -0
  52. package/dist/packages/stack/src/plugins/comments/client/localization/comments-thread.cjs +30 -0
  53. package/dist/packages/stack/src/plugins/comments/client/localization/comments-thread.mjs +28 -0
  54. package/dist/packages/stack/src/plugins/comments/client/localization/index.cjs +13 -0
  55. package/dist/packages/stack/src/plugins/comments/client/localization/index.mjs +11 -0
  56. package/dist/packages/stack/src/plugins/comments/client/plugin.cjs +116 -0
  57. package/dist/packages/stack/src/plugins/comments/client/plugin.mjs +114 -0
  58. package/dist/packages/stack/src/plugins/comments/client/utils.cjs +41 -0
  59. package/dist/packages/stack/src/plugins/comments/client/utils.mjs +37 -0
  60. package/dist/packages/stack/src/plugins/comments/db.cjs +75 -0
  61. package/dist/packages/stack/src/plugins/comments/db.mjs +73 -0
  62. package/dist/packages/stack/src/plugins/comments/schemas.cjs +45 -0
  63. package/dist/packages/stack/src/plugins/comments/schemas.mjs +38 -0
  64. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +0 -1
  65. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +0 -1
  66. package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.internal.cjs +39 -22
  67. package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.internal.mjs +40 -23
  68. package/dist/packages/ui/src/components/avatar.mjs +1 -1
  69. package/dist/packages/ui/src/components/pagination-controls.cjs +64 -0
  70. package/dist/packages/ui/src/components/pagination-controls.mjs +62 -0
  71. package/dist/packages/ui/src/components/when-visible.cjs +39 -0
  72. package/dist/packages/ui/src/components/when-visible.mjs +37 -0
  73. package/dist/plugins/blog/client/hooks/index.d.cts +1 -1
  74. package/dist/plugins/blog/client/hooks/index.d.mts +1 -1
  75. package/dist/plugins/blog/client/hooks/index.d.ts +1 -1
  76. package/dist/plugins/blog/client/index.d.cts +24 -2
  77. package/dist/plugins/blog/client/index.d.mts +24 -2
  78. package/dist/plugins/blog/client/index.d.ts +24 -2
  79. package/dist/plugins/comments/api/index.cjs +21 -0
  80. package/dist/plugins/comments/api/index.d.cts +126 -0
  81. package/dist/plugins/comments/api/index.d.mts +126 -0
  82. package/dist/plugins/comments/api/index.d.ts +126 -0
  83. package/dist/plugins/comments/api/index.mjs +5 -0
  84. package/dist/plugins/comments/client/components/index.cjs +15 -0
  85. package/dist/plugins/comments/client/components/index.d.cts +125 -0
  86. package/dist/plugins/comments/client/components/index.d.mts +125 -0
  87. package/dist/plugins/comments/client/components/index.d.ts +125 -0
  88. package/dist/plugins/comments/client/components/index.mjs +5 -0
  89. package/dist/plugins/comments/client/hooks/index.cjs +17 -0
  90. package/dist/plugins/comments/client/hooks/index.d.cts +200 -0
  91. package/dist/plugins/comments/client/hooks/index.d.mts +200 -0
  92. package/dist/plugins/comments/client/hooks/index.d.ts +200 -0
  93. package/dist/plugins/comments/client/hooks/index.mjs +1 -0
  94. package/dist/plugins/comments/client/index.cjs +9 -0
  95. package/dist/plugins/comments/client/index.d.cts +262 -0
  96. package/dist/plugins/comments/client/index.d.mts +262 -0
  97. package/dist/plugins/comments/client/index.d.ts +262 -0
  98. package/dist/plugins/comments/client/index.mjs +2 -0
  99. package/dist/plugins/comments/client.css +2 -0
  100. package/dist/plugins/comments/query-keys.cjs +113 -0
  101. package/dist/plugins/comments/query-keys.d.cts +71 -0
  102. package/dist/plugins/comments/query-keys.d.mts +71 -0
  103. package/dist/plugins/comments/query-keys.d.ts +71 -0
  104. package/dist/plugins/comments/query-keys.mjs +111 -0
  105. package/dist/plugins/comments/style.css +15 -0
  106. package/dist/plugins/kanban/api/index.d.cts +1 -1
  107. package/dist/plugins/kanban/api/index.d.mts +1 -1
  108. package/dist/plugins/kanban/api/index.d.ts +1 -1
  109. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  110. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  111. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  112. package/dist/plugins/kanban/client/index.d.cts +1 -1
  113. package/dist/plugins/kanban/client/index.d.mts +1 -1
  114. package/dist/plugins/kanban/client/index.d.ts +1 -1
  115. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  116. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  117. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  118. package/dist/shared/{stack.FeaWkglm.d.ts → stack.BxFl46lB.d.cts} +24 -1
  119. package/dist/shared/stack.C-b3Sn8j.d.cts +142 -0
  120. package/dist/shared/stack.C-b3Sn8j.d.mts +142 -0
  121. package/dist/shared/stack.C-b3Sn8j.d.ts +142 -0
  122. package/dist/shared/stack.CJE9sAjV.d.ts +335 -0
  123. package/dist/shared/stack.CmHRdhl8.d.cts +335 -0
  124. package/dist/shared/{stack.CNLHlv7r.d.mts → stack.DOZ1EXjM.d.mts} +6 -12
  125. package/dist/shared/{stack.FeaWkglm.d.mts → stack.DRpeDS6X.d.ts} +24 -1
  126. package/dist/shared/{stack.CQAZwXhV.d.cts → stack.DX-tQ93o.d.cts} +6 -12
  127. package/dist/shared/stack.Dcz6636A.d.mts +335 -0
  128. package/dist/shared/{stack.FeaWkglm.d.cts → stack.Jb0kQDJC.d.mts} +24 -1
  129. package/dist/shared/stack.Ldfkr5b2.d.cts +112 -0
  130. package/dist/shared/stack.Ldfkr5b2.d.mts +112 -0
  131. package/dist/shared/stack.Ldfkr5b2.d.ts +112 -0
  132. package/dist/shared/{stack.D3BsrpAz.d.ts → stack.VF6FhyZw.d.ts} +6 -12
  133. package/package.json +69 -4
  134. package/src/plugins/blog/client/components/loading/post-navigation-skeleton.tsx +10 -0
  135. package/src/plugins/blog/client/components/loading/recent-posts-carousel-skeleton.tsx +18 -0
  136. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +23 -8
  137. package/src/plugins/blog/client/components/shared/post-navigation.tsx +0 -5
  138. package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +1 -5
  139. package/src/plugins/blog/client/hooks/blog-hooks.tsx +8 -33
  140. package/src/plugins/blog/client/overrides.ts +26 -1
  141. package/src/plugins/cms/client/components/shared/pagination.tsx +14 -42
  142. package/src/plugins/comments/api/getters.ts +444 -0
  143. package/src/plugins/comments/api/index.ts +21 -0
  144. package/src/plugins/comments/api/mutations.ts +206 -0
  145. package/src/plugins/comments/api/plugin.ts +628 -0
  146. package/src/plugins/comments/api/query-key-defs.ts +143 -0
  147. package/src/plugins/comments/api/serializers.ts +37 -0
  148. package/src/plugins/comments/client/components/comment-count.tsx +66 -0
  149. package/src/plugins/comments/client/components/comment-form.tsx +112 -0
  150. package/src/plugins/comments/client/components/comment-thread.tsx +799 -0
  151. package/src/plugins/comments/client/components/index.tsx +11 -0
  152. package/src/plugins/comments/client/components/pages/moderation-page.internal.tsx +550 -0
  153. package/src/plugins/comments/client/components/pages/moderation-page.tsx +70 -0
  154. package/src/plugins/comments/client/components/pages/my-comments-page.internal.tsx +367 -0
  155. package/src/plugins/comments/client/components/pages/my-comments-page.tsx +72 -0
  156. package/src/plugins/comments/client/components/pages/resource-comments-page.internal.tsx +225 -0
  157. package/src/plugins/comments/client/components/pages/resource-comments-page.tsx +97 -0
  158. package/src/plugins/comments/client/components/shared/page-wrapper.tsx +32 -0
  159. package/src/plugins/comments/client/components/shared/pagination.tsx +44 -0
  160. package/src/plugins/comments/client/hooks/index.tsx +13 -0
  161. package/src/plugins/comments/client/hooks/use-comments.tsx +717 -0
  162. package/src/plugins/comments/client/index.ts +14 -0
  163. package/src/plugins/comments/client/localization/comments-moderation.ts +75 -0
  164. package/src/plugins/comments/client/localization/comments-my.ts +32 -0
  165. package/src/plugins/comments/client/localization/comments-thread.ts +32 -0
  166. package/src/plugins/comments/client/localization/index.ts +11 -0
  167. package/src/plugins/comments/client/overrides.ts +164 -0
  168. package/src/plugins/comments/client/plugin.tsx +195 -0
  169. package/src/plugins/comments/client/utils.ts +67 -0
  170. package/src/plugins/comments/client.css +2 -0
  171. package/src/plugins/comments/db.ts +77 -0
  172. package/src/plugins/comments/query-keys.ts +189 -0
  173. package/src/plugins/comments/schemas.ts +72 -0
  174. package/src/plugins/comments/style.css +15 -0
  175. package/src/plugins/comments/types.ts +73 -0
  176. package/src/plugins/kanban/client/components/forms/task-form.tsx +0 -1
  177. package/src/plugins/kanban/client/components/pages/board-page.internal.tsx +46 -27
  178. package/src/plugins/kanban/client/overrides.ts +27 -1
  179. package/dist/shared/{stack.Rtcvl8sS.d.cts → stack.BOokfhZD.d.cts} +3 -3
  180. package/dist/shared/{stack.D4Cea8II.d.ts → stack.BvCR4-9H.d.ts} +3 -3
  181. package/dist/shared/{stack.HE_IvqV5.d.mts → stack.CWxAl9K3.d.mts} +3 -3
@@ -0,0 +1,84 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useState } from 'react';
4
+ import { Button } from '../../../../../../ui/src/components/button.mjs';
5
+ import { Textarea } from '../../../../../../ui/src/components/textarea.mjs';
6
+ import { COMMENTS_LOCALIZATION } from '../localization/index.mjs';
7
+
8
+ function CommentForm({
9
+ authorId: _authorId,
10
+ initialBody = "",
11
+ submitLabel,
12
+ onSubmit,
13
+ onCancel,
14
+ InputComponent,
15
+ localization: localizationProp
16
+ }) {
17
+ const loc = { ...COMMENTS_LOCALIZATION, ...localizationProp };
18
+ const [body, setBody] = useState(initialBody);
19
+ const [isPending, setIsPending] = useState(false);
20
+ const [error, setError] = useState(null);
21
+ const resolvedSubmitLabel = submitLabel ?? loc.COMMENTS_FORM_POST_COMMENT;
22
+ const handleSubmit = async (e) => {
23
+ e.preventDefault();
24
+ if (!body.trim()) return;
25
+ setError(null);
26
+ setIsPending(true);
27
+ try {
28
+ await onSubmit(body.trim());
29
+ setBody("");
30
+ } catch (err) {
31
+ setError(
32
+ err instanceof Error ? err.message : loc.COMMENTS_FORM_SUBMIT_ERROR
33
+ );
34
+ } finally {
35
+ setIsPending(false);
36
+ }
37
+ };
38
+ return /* @__PURE__ */ jsxs(
39
+ "form",
40
+ {
41
+ "data-testid": "comment-form",
42
+ onSubmit: handleSubmit,
43
+ className: "flex flex-col gap-2",
44
+ children: [
45
+ InputComponent ? /* @__PURE__ */ jsx(
46
+ InputComponent,
47
+ {
48
+ value: body,
49
+ onChange: setBody,
50
+ disabled: isPending,
51
+ placeholder: loc.COMMENTS_FORM_PLACEHOLDER
52
+ }
53
+ ) : /* @__PURE__ */ jsx(
54
+ Textarea,
55
+ {
56
+ value: body,
57
+ onChange: (e) => setBody(e.target.value),
58
+ placeholder: loc.COMMENTS_FORM_PLACEHOLDER,
59
+ disabled: isPending,
60
+ rows: 3,
61
+ className: "resize-none"
62
+ }
63
+ ),
64
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
65
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-end", children: [
66
+ onCancel && /* @__PURE__ */ jsx(
67
+ Button,
68
+ {
69
+ type: "button",
70
+ variant: "ghost",
71
+ size: "sm",
72
+ onClick: onCancel,
73
+ disabled: isPending,
74
+ children: loc.COMMENTS_FORM_CANCEL
75
+ }
76
+ ),
77
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", disabled: isPending || !body.trim(), children: isPending ? loc.COMMENTS_FORM_POSTING : resolvedSubmitLabel })
78
+ ] })
79
+ ]
80
+ }
81
+ );
82
+ }
83
+
84
+ export { CommentForm };
@@ -0,0 +1,540 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const whenVisible = require('../../../../../../ui/src/components/when-visible.cjs');
7
+ const avatar = require('../../../../../../ui/src/components/avatar.cjs');
8
+ const badge = require('../../../../../../ui/src/components/badge.cjs');
9
+ const button = require('../../../../../../ui/src/components/button.cjs');
10
+ const separator = require('../../../../../../ui/src/components/separator.cjs');
11
+ const LucideIcons = require('lucide-react');
12
+ const dateFns = require('date-fns');
13
+ const utils = require('../utils.cjs');
14
+ const commentForm = require('./comment-form.cjs');
15
+ const useComments = require('../hooks/use-comments.cjs');
16
+ const index = require('../localization/index.cjs');
17
+ const context = require('@btst/stack/context');
18
+
19
+ const DEFAULT_RENDERER = ({ body }) => /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm whitespace-pre-wrap wrap-break-word", children: body });
20
+ function CommentCard({
21
+ comment,
22
+ currentUserId,
23
+ apiBaseURL,
24
+ apiBasePath,
25
+ resourceId,
26
+ resourceType,
27
+ headers,
28
+ components,
29
+ loc,
30
+ infiniteKey,
31
+ onReplyClick,
32
+ allowPosting,
33
+ allowEditing
34
+ }) {
35
+ const [isEditing, setIsEditing] = React.useState(false);
36
+ const Renderer = components?.Renderer ?? DEFAULT_RENDERER;
37
+ const config = { apiBaseURL, apiBasePath, headers };
38
+ const updateMutation = useComments.useUpdateComment(config);
39
+ const deleteMutation = useComments.useDeleteComment(config);
40
+ const toggleLikeMutation = useComments.useToggleLike(config, {
41
+ resourceId,
42
+ resourceType,
43
+ parentId: comment.parentId,
44
+ currentUserId,
45
+ infiniteKey
46
+ });
47
+ const isOwn = currentUserId && comment.authorId === currentUserId;
48
+ const isPending = comment.status === "pending";
49
+ const isApproved = comment.status === "approved";
50
+ const handleEdit = async (body) => {
51
+ await updateMutation.mutateAsync({ id: comment.id, body });
52
+ setIsEditing(false);
53
+ };
54
+ const handleDelete = async () => {
55
+ if (!window.confirm(loc.COMMENTS_DELETE_CONFIRM)) return;
56
+ await deleteMutation.mutateAsync(comment.id);
57
+ };
58
+ const handleLike = () => {
59
+ if (!currentUserId) return;
60
+ toggleLikeMutation.mutate({
61
+ commentId: comment.id,
62
+ authorId: currentUserId
63
+ });
64
+ };
65
+ return /* @__PURE__ */ jsxRuntime.jsxs(
66
+ "div",
67
+ {
68
+ className: "flex gap-3 py-3",
69
+ "data-testid": "comment-card",
70
+ "data-comment-id": comment.id,
71
+ children: [
72
+ /* @__PURE__ */ jsxRuntime.jsxs(avatar.Avatar, { className: "h-8 w-8 shrink-0 mt-0.5", children: [
73
+ comment.resolvedAvatarUrl && /* @__PURE__ */ jsxRuntime.jsx(
74
+ avatar.AvatarImage,
75
+ {
76
+ src: comment.resolvedAvatarUrl,
77
+ alt: comment.resolvedAuthorName
78
+ }
79
+ ),
80
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.AvatarFallback, { className: "text-xs", children: utils.getInitials(comment.resolvedAuthorName) })
81
+ ] }),
82
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
83
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-1", children: [
84
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: comment.resolvedAuthorName }),
85
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: dateFns.formatDistanceToNow(new Date(comment.createdAt), {
86
+ addSuffix: true
87
+ }) }),
88
+ comment.editedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground italic", children: loc.COMMENTS_EDITED_BADGE }),
89
+ isPending && isOwn && /* @__PURE__ */ jsxRuntime.jsx(
90
+ badge.Badge,
91
+ {
92
+ variant: "secondary",
93
+ className: "text-xs",
94
+ "data-testid": "pending-badge",
95
+ children: loc.COMMENTS_PENDING_BADGE
96
+ }
97
+ )
98
+ ] }),
99
+ isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
100
+ commentForm.CommentForm,
101
+ {
102
+ authorId: currentUserId ?? "",
103
+ initialBody: comment.body,
104
+ submitLabel: loc.COMMENTS_SAVE_EDIT,
105
+ InputComponent: components?.Input,
106
+ localization: loc,
107
+ onSubmit: handleEdit,
108
+ onCancel: () => setIsEditing(false)
109
+ }
110
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Renderer, { body: comment.body }),
111
+ !isEditing && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 mt-2", children: [
112
+ currentUserId && isApproved && /* @__PURE__ */ jsxRuntime.jsxs(
113
+ button.Button,
114
+ {
115
+ variant: "ghost",
116
+ size: "sm",
117
+ className: "h-7 px-2 text-xs gap-1",
118
+ onClick: handleLike,
119
+ "aria-label": comment.isLikedByCurrentUser ? loc.COMMENTS_UNLIKE_ARIA : loc.COMMENTS_LIKE_ARIA,
120
+ "data-testid": "like-button",
121
+ children: [
122
+ /* @__PURE__ */ jsxRuntime.jsx(
123
+ LucideIcons.Heart,
124
+ {
125
+ className: `h-3.5 w-3.5 ${comment.isLikedByCurrentUser ? "fill-current text-red-500" : ""}`
126
+ }
127
+ ),
128
+ comment.likes > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "like-count", children: comment.likes })
129
+ ]
130
+ }
131
+ ),
132
+ allowPosting && currentUserId && !comment.parentId && isApproved && /* @__PURE__ */ jsxRuntime.jsxs(
133
+ button.Button,
134
+ {
135
+ variant: "ghost",
136
+ size: "sm",
137
+ className: "h-7 px-2 text-xs",
138
+ onClick: () => onReplyClick(comment.id),
139
+ "data-testid": "reply-button",
140
+ children: [
141
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.MessageSquare, { className: "h-3.5 w-3.5 mr-1" }),
142
+ loc.COMMENTS_REPLY_BUTTON
143
+ ]
144
+ }
145
+ ),
146
+ isOwn && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
147
+ allowEditing && isApproved && /* @__PURE__ */ jsxRuntime.jsxs(
148
+ button.Button,
149
+ {
150
+ variant: "ghost",
151
+ size: "sm",
152
+ className: "h-7 px-2 text-xs",
153
+ onClick: () => setIsEditing(true),
154
+ "data-testid": "edit-button",
155
+ children: [
156
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Pencil, { className: "h-3.5 w-3.5 mr-1" }),
157
+ loc.COMMENTS_EDIT_BUTTON
158
+ ]
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsxRuntime.jsxs(
162
+ button.Button,
163
+ {
164
+ variant: "ghost",
165
+ size: "sm",
166
+ className: "h-7 px-2 text-xs text-destructive hover:text-destructive",
167
+ onClick: handleDelete,
168
+ disabled: deleteMutation.isPending,
169
+ "data-testid": "delete-button",
170
+ children: [
171
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.X, { className: "h-3.5 w-3.5 mr-1" }),
172
+ loc.COMMENTS_DELETE_BUTTON
173
+ ]
174
+ }
175
+ )
176
+ ] })
177
+ ] })
178
+ ] })
179
+ ]
180
+ }
181
+ );
182
+ }
183
+ const DEFAULT_PAGE_SIZE = 100;
184
+ const REPLIES_PAGE_SIZE = 20;
185
+ const OPTIMISTIC_ID_PREFIX = "optimistic-";
186
+ function CommentThreadInner({
187
+ resourceId,
188
+ resourceType,
189
+ apiBaseURL,
190
+ apiBasePath,
191
+ currentUserId,
192
+ loginHref,
193
+ headers,
194
+ components,
195
+ localization: localizationProp,
196
+ pageSize: pageSizeProp,
197
+ allowPosting: allowPostingProp,
198
+ allowEditing: allowEditingProp
199
+ }) {
200
+ const overrides = context.usePluginOverrides("comments", {});
201
+ const pageSize = pageSizeProp ?? overrides.defaultCommentPageSize ?? DEFAULT_PAGE_SIZE;
202
+ const allowPosting = allowPostingProp ?? overrides.allowPosting ?? true;
203
+ const allowEditing = allowEditingProp ?? overrides.allowEditing ?? true;
204
+ const loc = { ...index.COMMENTS_LOCALIZATION, ...localizationProp };
205
+ const [replyingTo, setReplyingTo] = React.useState(null);
206
+ const [expandedReplies, setExpandedReplies] = React.useState(
207
+ /* @__PURE__ */ new Set()
208
+ );
209
+ const [replyOffsets, setReplyOffsets] = React.useState({});
210
+ const config = { apiBaseURL, apiBasePath, headers };
211
+ const {
212
+ comments,
213
+ total,
214
+ isLoading,
215
+ loadMore,
216
+ hasMore,
217
+ isLoadingMore,
218
+ queryKey: threadQueryKey
219
+ } = useComments.useInfiniteComments(config, {
220
+ resourceId,
221
+ resourceType,
222
+ status: "approved",
223
+ parentId: null,
224
+ currentUserId,
225
+ pageSize
226
+ });
227
+ const postMutation = useComments.usePostComment(config, {
228
+ resourceId,
229
+ resourceType,
230
+ currentUserId,
231
+ infiniteKey: threadQueryKey,
232
+ pageSize
233
+ });
234
+ const handlePost = async (body) => {
235
+ if (!currentUserId) return;
236
+ await postMutation.mutateAsync({
237
+ body,
238
+ parentId: null
239
+ });
240
+ };
241
+ const handleReply = async (body, parentId) => {
242
+ if (!currentUserId) return;
243
+ await postMutation.mutateAsync({
244
+ body,
245
+ parentId,
246
+ limit: REPLIES_PAGE_SIZE,
247
+ offset: replyOffsets[parentId] ?? 0
248
+ });
249
+ setReplyingTo(null);
250
+ setExpandedReplies((prev) => new Set(prev).add(parentId));
251
+ };
252
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", "data-testid": "comment-thread", children: [
253
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-4", children: [
254
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.MessageSquare, { className: "h-5 w-5 text-muted-foreground" }),
255
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-sm", children: total === 0 ? loc.COMMENTS_TITLE : `${total} ${loc.COMMENTS_TITLE}` })
256
+ ] }),
257
+ isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: [1, 2].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 py-3 animate-pulse", children: [
258
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 w-8 rounded-full bg-muted shrink-0" }),
259
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-2", children: [
260
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-24 rounded bg-muted" }),
261
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-full rounded bg-muted" }),
262
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-3/4 rounded bg-muted" })
263
+ ] })
264
+ ] }, i)) }),
265
+ !isLoading && comments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border", children: comments.map((comment) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
266
+ /* @__PURE__ */ jsxRuntime.jsx(
267
+ CommentCard,
268
+ {
269
+ comment,
270
+ currentUserId,
271
+ apiBaseURL,
272
+ apiBasePath,
273
+ resourceId,
274
+ resourceType,
275
+ headers,
276
+ components,
277
+ loc,
278
+ infiniteKey: threadQueryKey,
279
+ onReplyClick: (parentId) => {
280
+ setReplyingTo(replyingTo === parentId ? null : parentId);
281
+ },
282
+ allowPosting,
283
+ allowEditing
284
+ }
285
+ ),
286
+ /* @__PURE__ */ jsxRuntime.jsx(
287
+ RepliesSection,
288
+ {
289
+ parentId: comment.id,
290
+ resourceId,
291
+ resourceType,
292
+ apiBaseURL,
293
+ apiBasePath,
294
+ currentUserId,
295
+ headers,
296
+ components,
297
+ loc,
298
+ expanded: expandedReplies.has(comment.id),
299
+ replyCount: comment.replyCount,
300
+ onToggle: () => {
301
+ const isExpanded = expandedReplies.has(comment.id);
302
+ if (!isExpanded) {
303
+ setReplyOffsets((prev) => {
304
+ if ((prev[comment.id] ?? 0) === 0) return prev;
305
+ return { ...prev, [comment.id]: 0 };
306
+ });
307
+ }
308
+ setExpandedReplies((prev) => {
309
+ const next = new Set(prev);
310
+ next.has(comment.id) ? next.delete(comment.id) : next.add(comment.id);
311
+ return next;
312
+ });
313
+ },
314
+ onOffsetChange: (offset) => {
315
+ setReplyOffsets((prev) => {
316
+ if (prev[comment.id] === offset) return prev;
317
+ return { ...prev, [comment.id]: offset };
318
+ });
319
+ },
320
+ allowEditing
321
+ }
322
+ ),
323
+ allowPosting && replyingTo === comment.id && currentUserId && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pl-11 pb-3", children: /* @__PURE__ */ jsxRuntime.jsx(
324
+ commentForm.CommentForm,
325
+ {
326
+ authorId: currentUserId,
327
+ parentId: comment.id,
328
+ submitLabel: loc.COMMENTS_FORM_POST_REPLY,
329
+ InputComponent: components?.Input,
330
+ localization: loc,
331
+ onSubmit: (body) => handleReply(body, comment.id),
332
+ onCancel: () => setReplyingTo(null)
333
+ }
334
+ ) })
335
+ ] }, comment.id)) }),
336
+ !isLoading && comments.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: loc.COMMENTS_EMPTY }),
337
+ hasMore && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center pt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
338
+ button.Button,
339
+ {
340
+ variant: "outline",
341
+ size: "sm",
342
+ onClick: () => loadMore(),
343
+ disabled: isLoadingMore,
344
+ "data-testid": "load-more-comments",
345
+ children: isLoadingMore ? loc.COMMENTS_LOADING_MORE : loc.COMMENTS_LOAD_MORE
346
+ }
347
+ ) }),
348
+ allowPosting && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
349
+ /* @__PURE__ */ jsxRuntime.jsx(separator.Separator, { className: "my-4" }),
350
+ currentUserId ? /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "comment-form-wrapper", children: /* @__PURE__ */ jsxRuntime.jsx(
351
+ commentForm.CommentForm,
352
+ {
353
+ authorId: currentUserId,
354
+ submitLabel: loc.COMMENTS_FORM_POST_COMMENT,
355
+ InputComponent: components?.Input,
356
+ localization: loc,
357
+ onSubmit: handlePost
358
+ }
359
+ ) }) : /* @__PURE__ */ jsxRuntime.jsxs(
360
+ "div",
361
+ {
362
+ className: "flex flex-col items-center gap-3 py-6 text-center border rounded-lg bg-muted/30",
363
+ "data-testid": "login-prompt",
364
+ children: [
365
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.LogIn, { className: "h-6 w-6 text-muted-foreground" }),
366
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: loc.COMMENTS_LOGIN_PROMPT }),
367
+ loginHref && /* @__PURE__ */ jsxRuntime.jsx(
368
+ "a",
369
+ {
370
+ href: loginHref,
371
+ className: "inline-flex items-center gap-1 text-sm font-medium text-primary underline underline-offset-4",
372
+ "data-testid": "login-link",
373
+ children: loc.COMMENTS_LOGIN_LINK
374
+ }
375
+ )
376
+ ]
377
+ }
378
+ )
379
+ ] })
380
+ ] });
381
+ }
382
+ function RepliesSection({
383
+ parentId,
384
+ resourceId,
385
+ resourceType,
386
+ apiBaseURL,
387
+ apiBasePath,
388
+ currentUserId,
389
+ headers,
390
+ components,
391
+ loc,
392
+ expanded,
393
+ replyCount,
394
+ onToggle,
395
+ onOffsetChange,
396
+ allowEditing
397
+ }) {
398
+ const config = { apiBaseURL, apiBasePath, headers };
399
+ const [replyOffset, setReplyOffset] = React.useState(0);
400
+ const [loadedReplies, setLoadedReplies] = React.useState([]);
401
+ const {
402
+ comments: repliesPage,
403
+ total: repliesTotal,
404
+ isFetching: isFetchingReplies
405
+ } = useComments.useComments(
406
+ config,
407
+ {
408
+ resourceId,
409
+ resourceType,
410
+ parentId,
411
+ status: "approved",
412
+ currentUserId,
413
+ limit: REPLIES_PAGE_SIZE,
414
+ offset: replyOffset
415
+ },
416
+ { enabled: expanded }
417
+ );
418
+ React.useEffect(() => {
419
+ if (expanded) {
420
+ setReplyOffset(0);
421
+ setLoadedReplies([]);
422
+ }
423
+ }, [expanded, parentId]);
424
+ React.useEffect(() => {
425
+ onOffsetChange(replyOffset);
426
+ }, [onOffsetChange, replyOffset]);
427
+ React.useEffect(() => {
428
+ if (!expanded) return;
429
+ setLoadedReplies((prev) => {
430
+ const byId = new Map(prev.map((item) => [item.id, item]));
431
+ for (const reply of repliesPage) {
432
+ byId.set(reply.id, reply);
433
+ }
434
+ const currentPageIds = new Set(repliesPage.map((reply) => reply.id));
435
+ const currentPageRealReplies = repliesPage.filter(
436
+ (reply) => !reply.id.startsWith(OPTIMISTIC_ID_PREFIX)
437
+ );
438
+ return Array.from(byId.values()).filter((reply) => {
439
+ if (!reply.id.startsWith(OPTIMISTIC_ID_PREFIX)) return true;
440
+ if (currentPageIds.has(reply.id)) return true;
441
+ return !currentPageRealReplies.some(
442
+ (realReply) => realReply.parentId === reply.parentId && realReply.authorId === reply.authorId && realReply.body === reply.body
443
+ );
444
+ });
445
+ });
446
+ }, [expanded, repliesPage]);
447
+ if (replyCount === 0 && !expanded) return null;
448
+ const displayCount = expanded ? loadedReplies.length || replyCount : replyCount;
449
+ const effectiveReplyTotal = repliesTotal || replyCount;
450
+ const hasMoreReplies = loadedReplies.length < effectiveReplyTotal;
451
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pl-11", children: [
452
+ /* @__PURE__ */ jsxRuntime.jsxs(
453
+ button.Button,
454
+ {
455
+ variant: "ghost",
456
+ size: "sm",
457
+ className: "h-7 px-2 text-xs mb-1",
458
+ onClick: onToggle,
459
+ "data-testid": expanded ? "hide-replies-button" : "show-replies-button",
460
+ children: [
461
+ expanded ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronUp, { className: "h-3 w-3 mr-1" }) : /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronDown, { className: "h-3 w-3 mr-1" }),
462
+ expanded ? loc.COMMENTS_HIDE_REPLIES : `${displayCount} ${displayCount === 1 ? loc.COMMENTS_REPLIES_SINGULAR : loc.COMMENTS_REPLIES_PLURAL}`
463
+ ]
464
+ }
465
+ ),
466
+ expanded && /* @__PURE__ */ jsxRuntime.jsxs(
467
+ "div",
468
+ {
469
+ className: "border-l-2 border-border pl-3 space-y-0",
470
+ "data-testid": "replies-list",
471
+ children: [
472
+ loadedReplies.map((reply) => /* @__PURE__ */ jsxRuntime.jsx(
473
+ CommentCard,
474
+ {
475
+ comment: reply,
476
+ currentUserId,
477
+ apiBaseURL,
478
+ apiBasePath,
479
+ resourceId,
480
+ resourceType,
481
+ headers,
482
+ components,
483
+ loc,
484
+ onReplyClick: () => {
485
+ },
486
+ allowPosting: false,
487
+ allowEditing
488
+ },
489
+ reply.id
490
+ )),
491
+ hasMoreReplies && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2", children: /* @__PURE__ */ jsxRuntime.jsx(
492
+ button.Button,
493
+ {
494
+ variant: "ghost",
495
+ size: "sm",
496
+ className: "h-7 px-2 text-xs",
497
+ onClick: () => setReplyOffset((prev) => prev + REPLIES_PAGE_SIZE),
498
+ disabled: isFetchingReplies,
499
+ "data-testid": "load-more-replies",
500
+ children: isFetchingReplies ? loc.COMMENTS_LOADING_MORE : loc.COMMENTS_LOAD_MORE
501
+ }
502
+ ) })
503
+ ]
504
+ }
505
+ )
506
+ ] });
507
+ }
508
+ function CommentThreadSkeleton() {
509
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
510
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-4", children: [
511
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-5 w-5 rounded bg-muted animate-pulse" }),
512
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-24 rounded bg-muted animate-pulse" })
513
+ ] }),
514
+ [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 py-3", children: [
515
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 w-8 rounded-full bg-muted shrink-0 mt-0.5 animate-pulse" }),
516
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-2", children: [
517
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
518
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-20 rounded bg-muted animate-pulse" }),
519
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-14 rounded bg-muted animate-pulse" })
520
+ ] }),
521
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-full rounded bg-muted animate-pulse" }),
522
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-4/5 rounded bg-muted animate-pulse" }),
523
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1 mt-1", children: [
524
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-7 w-12 rounded bg-muted animate-pulse" }),
525
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-7 w-14 rounded bg-muted animate-pulse" })
526
+ ] })
527
+ ] })
528
+ ] }, i)),
529
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px w-full bg-muted my-4" }),
530
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
531
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-20 w-full rounded-md border bg-muted animate-pulse" }),
532
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-9 w-28 rounded-md bg-muted animate-pulse" }) })
533
+ ] })
534
+ ] });
535
+ }
536
+ function CommentThread(props) {
537
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { id: "comments", className: props.className, children: /* @__PURE__ */ jsxRuntime.jsx(whenVisible.WhenVisible, { fallback: /* @__PURE__ */ jsxRuntime.jsx(CommentThreadSkeleton, {}), rootMargin: "300px", children: /* @__PURE__ */ jsxRuntime.jsx(CommentThreadInner, { ...props }) }) });
538
+ }
539
+
540
+ exports.CommentThread = CommentThread;