@btst/stack 2.11.8 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/stack/src/plugins/comments/api/query-key-defs.cjs +2 -1
- package/dist/packages/stack/src/plugins/comments/api/query-key-defs.mjs +2 -1
- package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.cjs +6 -2
- package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.mjs +6 -2
- package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.cjs +5 -3
- package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.mjs +5 -3
- package/dist/plugins/comments/api/index.d.cts +4 -4
- package/dist/plugins/comments/api/index.d.mts +4 -4
- package/dist/plugins/comments/api/index.d.ts +4 -4
- package/dist/plugins/comments/client/components/index.d.cts +11 -0
- package/dist/plugins/comments/client/components/index.d.mts +11 -0
- package/dist/plugins/comments/client/components/index.d.ts +11 -0
- package/dist/plugins/comments/client/hooks/index.d.cts +17 -1
- package/dist/plugins/comments/client/hooks/index.d.mts +17 -1
- package/dist/plugins/comments/client/hooks/index.d.ts +17 -1
- package/dist/plugins/comments/client/index.d.cts +9 -0
- package/dist/plugins/comments/client/index.d.mts +9 -0
- package/dist/plugins/comments/client/index.d.ts +9 -0
- package/dist/plugins/comments/query-keys.cjs +1 -0
- package/dist/plugins/comments/query-keys.d.cts +3 -2
- package/dist/plugins/comments/query-keys.d.mts +3 -2
- package/dist/plugins/comments/query-keys.d.ts +3 -2
- package/dist/plugins/comments/query-keys.mjs +1 -0
- package/dist/shared/{stack.CJE9sAjV.d.ts → stack.Bq3kiUaM.d.ts} +1 -1
- package/dist/shared/{stack.C-b3Sn8j.d.cts → stack.C7KNNiJm.d.cts} +3 -1
- package/dist/shared/{stack.C-b3Sn8j.d.mts → stack.C7KNNiJm.d.mts} +3 -1
- package/dist/shared/{stack.C-b3Sn8j.d.ts → stack.C7KNNiJm.d.ts} +3 -1
- package/dist/shared/{stack.CmHRdhl8.d.cts → stack.CCncwK6g.d.cts} +1 -1
- package/dist/shared/{stack.Dcz6636A.d.mts → stack.CO1g47IS.d.mts} +1 -1
- package/package.json +1 -1
- package/src/plugins/comments/api/query-key-defs.ts +5 -1
- package/src/plugins/comments/client/components/comment-thread.tsx +15 -0
- package/src/plugins/comments/client/hooks/use-comments.tsx +26 -3
- package/src/plugins/comments/client/overrides.ts +10 -0
- package/src/plugins/comments/query-keys.ts +2 -0
|
@@ -27,6 +27,7 @@ function commentsThreadDiscriminator(params) {
|
|
|
27
27
|
parentId: params?.parentId,
|
|
28
28
|
status: params?.status,
|
|
29
29
|
currentUserId: params?.currentUserId,
|
|
30
|
+
sort: params?.sort,
|
|
30
31
|
limit: params?.limit ?? 20
|
|
31
32
|
};
|
|
32
33
|
}
|
|
@@ -48,7 +49,7 @@ const COMMENTS_QUERY_KEYS = {
|
|
|
48
49
|
commentCount: (params) => ["comments", "count", commentCountDiscriminator(params)],
|
|
49
50
|
/**
|
|
50
51
|
* Key for the infinite thread query (top-level comments, load-more).
|
|
51
|
-
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, limit }]
|
|
52
|
+
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, sort, limit }]
|
|
52
53
|
* Offset is excluded — it is driven by `pageParam`, not baked into the key.
|
|
53
54
|
*/
|
|
54
55
|
commentsThread: (params) => ["commentsThread", "list", commentsThreadDiscriminator(params)]
|
|
@@ -25,6 +25,7 @@ function commentsThreadDiscriminator(params) {
|
|
|
25
25
|
parentId: params?.parentId,
|
|
26
26
|
status: params?.status,
|
|
27
27
|
currentUserId: params?.currentUserId,
|
|
28
|
+
sort: params?.sort,
|
|
28
29
|
limit: params?.limit ?? 20
|
|
29
30
|
};
|
|
30
31
|
}
|
|
@@ -46,7 +47,7 @@ const COMMENTS_QUERY_KEYS = {
|
|
|
46
47
|
commentCount: (params) => ["comments", "count", commentCountDiscriminator(params)],
|
|
47
48
|
/**
|
|
48
49
|
* Key for the infinite thread query (top-level comments, load-more).
|
|
49
|
-
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, limit }]
|
|
50
|
+
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, sort, limit }]
|
|
50
51
|
* Offset is excluded — it is driven by `pageParam`, not baked into the key.
|
|
51
52
|
*/
|
|
52
53
|
commentsThread: (params) => ["commentsThread", "list", commentsThreadDiscriminator(params)]
|
|
@@ -195,12 +195,14 @@ function CommentThreadInner({
|
|
|
195
195
|
localization: localizationProp,
|
|
196
196
|
pageSize: pageSizeProp,
|
|
197
197
|
allowPosting: allowPostingProp,
|
|
198
|
-
allowEditing: allowEditingProp
|
|
198
|
+
allowEditing: allowEditingProp,
|
|
199
|
+
sort: sortProp
|
|
199
200
|
}) {
|
|
200
201
|
const overrides = context.usePluginOverrides("comments", {});
|
|
201
202
|
const pageSize = pageSizeProp ?? overrides.defaultCommentPageSize ?? DEFAULT_PAGE_SIZE;
|
|
202
203
|
const allowPosting = allowPostingProp ?? overrides.allowPosting ?? true;
|
|
203
204
|
const allowEditing = allowEditingProp ?? overrides.allowEditing ?? true;
|
|
205
|
+
const sort = sortProp ?? overrides.defaultCommentSort ?? "desc";
|
|
204
206
|
const loc = { ...index.COMMENTS_LOCALIZATION, ...localizationProp };
|
|
205
207
|
const [replyingTo, setReplyingTo] = React.useState(null);
|
|
206
208
|
const [expandedReplies, setExpandedReplies] = React.useState(
|
|
@@ -222,6 +224,7 @@ function CommentThreadInner({
|
|
|
222
224
|
status: "approved",
|
|
223
225
|
parentId: null,
|
|
224
226
|
currentUserId,
|
|
227
|
+
sort,
|
|
225
228
|
pageSize
|
|
226
229
|
});
|
|
227
230
|
const postMutation = useComments.usePostComment(config, {
|
|
@@ -229,7 +232,8 @@ function CommentThreadInner({
|
|
|
229
232
|
resourceType,
|
|
230
233
|
currentUserId,
|
|
231
234
|
infiniteKey: threadQueryKey,
|
|
232
|
-
pageSize
|
|
235
|
+
pageSize,
|
|
236
|
+
sort
|
|
233
237
|
});
|
|
234
238
|
const handlePost = async (body) => {
|
|
235
239
|
if (!currentUserId) return;
|
|
@@ -193,12 +193,14 @@ function CommentThreadInner({
|
|
|
193
193
|
localization: localizationProp,
|
|
194
194
|
pageSize: pageSizeProp,
|
|
195
195
|
allowPosting: allowPostingProp,
|
|
196
|
-
allowEditing: allowEditingProp
|
|
196
|
+
allowEditing: allowEditingProp,
|
|
197
|
+
sort: sortProp
|
|
197
198
|
}) {
|
|
198
199
|
const overrides = usePluginOverrides("comments", {});
|
|
199
200
|
const pageSize = pageSizeProp ?? overrides.defaultCommentPageSize ?? DEFAULT_PAGE_SIZE;
|
|
200
201
|
const allowPosting = allowPostingProp ?? overrides.allowPosting ?? true;
|
|
201
202
|
const allowEditing = allowEditingProp ?? overrides.allowEditing ?? true;
|
|
203
|
+
const sort = sortProp ?? overrides.defaultCommentSort ?? "desc";
|
|
202
204
|
const loc = { ...COMMENTS_LOCALIZATION, ...localizationProp };
|
|
203
205
|
const [replyingTo, setReplyingTo] = useState(null);
|
|
204
206
|
const [expandedReplies, setExpandedReplies] = useState(
|
|
@@ -220,6 +222,7 @@ function CommentThreadInner({
|
|
|
220
222
|
status: "approved",
|
|
221
223
|
parentId: null,
|
|
222
224
|
currentUserId,
|
|
225
|
+
sort,
|
|
223
226
|
pageSize
|
|
224
227
|
});
|
|
225
228
|
const postMutation = usePostComment(config, {
|
|
@@ -227,7 +230,8 @@ function CommentThreadInner({
|
|
|
227
230
|
resourceType,
|
|
228
231
|
currentUserId,
|
|
229
232
|
infiniteKey: threadQueryKey,
|
|
230
|
-
pageSize
|
|
233
|
+
pageSize,
|
|
234
|
+
sort
|
|
231
235
|
});
|
|
232
236
|
const handlePost = async (body) => {
|
|
233
237
|
if (!currentUserId) return;
|
|
@@ -84,6 +84,7 @@ function useInfiniteComments(config, params, options) {
|
|
|
84
84
|
parentId: params.parentId ?? null,
|
|
85
85
|
status: params.status,
|
|
86
86
|
currentUserId: params.currentUserId,
|
|
87
|
+
sort: params.sort,
|
|
87
88
|
limit: pageSize
|
|
88
89
|
});
|
|
89
90
|
const query = reactQuery.useInfiniteQuery({
|
|
@@ -183,6 +184,7 @@ function usePostComment(config, params) {
|
|
|
183
184
|
};
|
|
184
185
|
if (isInfinitePost(input.parentId)) {
|
|
185
186
|
const previous2 = queryClient.getQueryData(listKey);
|
|
187
|
+
const isDesc = params.sort === "desc";
|
|
186
188
|
queryClient.setQueryData(
|
|
187
189
|
listKey,
|
|
188
190
|
(old) => {
|
|
@@ -199,15 +201,15 @@ function usePostComment(config, params) {
|
|
|
199
201
|
pageParams: [0]
|
|
200
202
|
};
|
|
201
203
|
}
|
|
202
|
-
const
|
|
204
|
+
const targetIdx = isDesc ? 0 : old.pages.length - 1;
|
|
203
205
|
return {
|
|
204
206
|
...old,
|
|
205
207
|
// Increment `total` on every page so the header count (which reads
|
|
206
208
|
// pages[0].total) stays in sync even after multiple pages are loaded.
|
|
207
209
|
pages: old.pages.map(
|
|
208
|
-
(page, idx) => idx ===
|
|
210
|
+
(page, idx) => idx === targetIdx ? {
|
|
209
211
|
...page,
|
|
210
|
-
items: [...page.items, optimistic],
|
|
212
|
+
items: isDesc ? [optimistic, ...page.items] : [...page.items, optimistic],
|
|
211
213
|
total: page.total + 1
|
|
212
214
|
} : { ...page, total: page.total + 1 }
|
|
213
215
|
)
|
|
@@ -82,6 +82,7 @@ function useInfiniteComments(config, params, options) {
|
|
|
82
82
|
parentId: params.parentId ?? null,
|
|
83
83
|
status: params.status,
|
|
84
84
|
currentUserId: params.currentUserId,
|
|
85
|
+
sort: params.sort,
|
|
85
86
|
limit: pageSize
|
|
86
87
|
});
|
|
87
88
|
const query = useInfiniteQuery({
|
|
@@ -181,6 +182,7 @@ function usePostComment(config, params) {
|
|
|
181
182
|
};
|
|
182
183
|
if (isInfinitePost(input.parentId)) {
|
|
183
184
|
const previous2 = queryClient.getQueryData(listKey);
|
|
185
|
+
const isDesc = params.sort === "desc";
|
|
184
186
|
queryClient.setQueryData(
|
|
185
187
|
listKey,
|
|
186
188
|
(old) => {
|
|
@@ -197,15 +199,15 @@ function usePostComment(config, params) {
|
|
|
197
199
|
pageParams: [0]
|
|
198
200
|
};
|
|
199
201
|
}
|
|
200
|
-
const
|
|
202
|
+
const targetIdx = isDesc ? 0 : old.pages.length - 1;
|
|
201
203
|
return {
|
|
202
204
|
...old,
|
|
203
205
|
// Increment `total` on every page so the header count (which reads
|
|
204
206
|
// pages[0].total) stays in sync even after multiple pages are loaded.
|
|
205
207
|
pages: old.pages.map(
|
|
206
|
-
(page, idx) => idx ===
|
|
208
|
+
(page, idx) => idx === targetIdx ? {
|
|
207
209
|
...page,
|
|
208
|
-
items: [...page.items, optimistic],
|
|
210
|
+
items: isDesc ? [optimistic, ...page.items] : [...page.items, optimistic],
|
|
209
211
|
total: page.total + 1
|
|
210
212
|
} : { ...page, total: page.total + 1 }
|
|
211
213
|
)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a as CommentListParamsSchema, b as CommentCountQuerySchema } from '../../../shared/stack.
|
|
2
|
-
export { d as CommentsApiContext, C as CommentsApiRouter, e as CommentsBackendOptions, c as commentsBackendPlugin } from '../../../shared/stack.
|
|
1
|
+
import { a as CommentListParamsSchema, b as CommentCountQuerySchema } from '../../../shared/stack.CCncwK6g.cjs';
|
|
2
|
+
export { d as CommentsApiContext, C as CommentsApiRouter, e as CommentsBackendOptions, c as commentsBackendPlugin } from '../../../shared/stack.CCncwK6g.cjs';
|
|
3
3
|
import { DBAdapter } from '@btst/db';
|
|
4
|
-
import { C as CommentListResult, S as SerializedComment, d as Comment } from '../../../shared/stack.
|
|
5
|
-
export { e as COMMENTS_QUERY_KEYS } from '../../../shared/stack.
|
|
4
|
+
import { C as CommentListResult, S as SerializedComment, d as Comment } from '../../../shared/stack.C7KNNiJm.cjs';
|
|
5
|
+
export { e as COMMENTS_QUERY_KEYS } from '../../../shared/stack.C7KNNiJm.cjs';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import '@btst/stack/plugins/api';
|
|
8
8
|
import 'better-call';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a as CommentListParamsSchema, b as CommentCountQuerySchema } from '../../../shared/stack.
|
|
2
|
-
export { d as CommentsApiContext, C as CommentsApiRouter, e as CommentsBackendOptions, c as commentsBackendPlugin } from '../../../shared/stack.
|
|
1
|
+
import { a as CommentListParamsSchema, b as CommentCountQuerySchema } from '../../../shared/stack.CO1g47IS.mjs';
|
|
2
|
+
export { d as CommentsApiContext, C as CommentsApiRouter, e as CommentsBackendOptions, c as commentsBackendPlugin } from '../../../shared/stack.CO1g47IS.mjs';
|
|
3
3
|
import { DBAdapter } from '@btst/db';
|
|
4
|
-
import { C as CommentListResult, S as SerializedComment, d as Comment } from '../../../shared/stack.
|
|
5
|
-
export { e as COMMENTS_QUERY_KEYS } from '../../../shared/stack.
|
|
4
|
+
import { C as CommentListResult, S as SerializedComment, d as Comment } from '../../../shared/stack.C7KNNiJm.mjs';
|
|
5
|
+
export { e as COMMENTS_QUERY_KEYS } from '../../../shared/stack.C7KNNiJm.mjs';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import '@btst/stack/plugins/api';
|
|
8
8
|
import 'better-call';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a as CommentListParamsSchema, b as CommentCountQuerySchema } from '../../../shared/stack.
|
|
2
|
-
export { d as CommentsApiContext, C as CommentsApiRouter, e as CommentsBackendOptions, c as commentsBackendPlugin } from '../../../shared/stack.
|
|
1
|
+
import { a as CommentListParamsSchema, b as CommentCountQuerySchema } from '../../../shared/stack.Bq3kiUaM.js';
|
|
2
|
+
export { d as CommentsApiContext, C as CommentsApiRouter, e as CommentsBackendOptions, c as commentsBackendPlugin } from '../../../shared/stack.Bq3kiUaM.js';
|
|
3
3
|
import { DBAdapter } from '@btst/db';
|
|
4
|
-
import { C as CommentListResult, S as SerializedComment, d as Comment } from '../../../shared/stack.
|
|
5
|
-
export { e as COMMENTS_QUERY_KEYS } from '../../../shared/stack.
|
|
4
|
+
import { C as CommentListResult, S as SerializedComment, d as Comment } from '../../../shared/stack.C7KNNiJm.js';
|
|
5
|
+
export { e as COMMENTS_QUERY_KEYS } from '../../../shared/stack.C7KNNiJm.js';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import '@btst/stack/plugins/api';
|
|
8
8
|
import 'better-call';
|
|
@@ -59,6 +59,17 @@ interface CommentThreadProps {
|
|
|
59
59
|
* Defaults to true.
|
|
60
60
|
*/
|
|
61
61
|
allowEditing?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Sort direction for top-level comments by `createdAt`.
|
|
64
|
+
* - `"desc"` (default): newest first.
|
|
65
|
+
* - `"asc"`: oldest first.
|
|
66
|
+
*
|
|
67
|
+
* Replies inside each thread always render chronologically (oldest → newest)
|
|
68
|
+
* and are unaffected by this prop.
|
|
69
|
+
*
|
|
70
|
+
* Overrides the global `defaultCommentSort` from `CommentsPluginOverrides`.
|
|
71
|
+
*/
|
|
72
|
+
sort?: "asc" | "desc";
|
|
62
73
|
}
|
|
63
74
|
declare function CommentThread(props: CommentThreadProps): react_jsx_runtime.JSX.Element;
|
|
64
75
|
|
|
@@ -59,6 +59,17 @@ interface CommentThreadProps {
|
|
|
59
59
|
* Defaults to true.
|
|
60
60
|
*/
|
|
61
61
|
allowEditing?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Sort direction for top-level comments by `createdAt`.
|
|
64
|
+
* - `"desc"` (default): newest first.
|
|
65
|
+
* - `"asc"`: oldest first.
|
|
66
|
+
*
|
|
67
|
+
* Replies inside each thread always render chronologically (oldest → newest)
|
|
68
|
+
* and are unaffected by this prop.
|
|
69
|
+
*
|
|
70
|
+
* Overrides the global `defaultCommentSort` from `CommentsPluginOverrides`.
|
|
71
|
+
*/
|
|
72
|
+
sort?: "asc" | "desc";
|
|
62
73
|
}
|
|
63
74
|
declare function CommentThread(props: CommentThreadProps): react_jsx_runtime.JSX.Element;
|
|
64
75
|
|
|
@@ -59,6 +59,17 @@ interface CommentThreadProps {
|
|
|
59
59
|
* Defaults to true.
|
|
60
60
|
*/
|
|
61
61
|
allowEditing?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Sort direction for top-level comments by `createdAt`.
|
|
64
|
+
* - `"desc"` (default): newest first.
|
|
65
|
+
* - `"asc"`: oldest first.
|
|
66
|
+
*
|
|
67
|
+
* Replies inside each thread always render chronologically (oldest → newest)
|
|
68
|
+
* and are unaffected by this prop.
|
|
69
|
+
*
|
|
70
|
+
* Overrides the global `defaultCommentSort` from `CommentsPluginOverrides`.
|
|
71
|
+
*/
|
|
72
|
+
sort?: "asc" | "desc";
|
|
62
73
|
}
|
|
63
74
|
declare function CommentThread(props: CommentThreadProps): react_jsx_runtime.JSX.Element;
|
|
64
75
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CommentListResult, S as SerializedComment, a as CommentsThreadDiscriminator } from '../../../../shared/stack.
|
|
1
|
+
import { C as CommentListResult, S as SerializedComment, a as CommentsThreadDiscriminator } from '../../../../shared/stack.C7KNNiJm.cjs';
|
|
2
2
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
3
3
|
import { InfiniteData } from '@tanstack/react-query';
|
|
4
4
|
|
|
@@ -81,6 +81,11 @@ declare function useInfiniteComments(config: CommentsClientConfig, params: {
|
|
|
81
81
|
parentId?: string | null;
|
|
82
82
|
status?: "pending" | "approved" | "spam";
|
|
83
83
|
currentUserId?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Sort direction by `createdAt`. Default: `"asc"` (oldest first) — matches
|
|
86
|
+
* the server-side default. Pass `"desc"` for newest-first threads.
|
|
87
|
+
*/
|
|
88
|
+
sort?: "asc" | "desc";
|
|
84
89
|
pageSize?: number;
|
|
85
90
|
}, options?: {
|
|
86
91
|
enabled?: boolean;
|
|
@@ -129,6 +134,17 @@ declare function usePostComment(config: CommentsClientConfig, params: {
|
|
|
129
134
|
* `nextOffset` from `lastPage.limit` instead of a hardcoded fallback.
|
|
130
135
|
*/
|
|
131
136
|
pageSize?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Sort direction of the surrounding infinite thread.
|
|
139
|
+
* - `"asc"` (default): newest comments belong on the LAST page → optimistic
|
|
140
|
+
* item is appended to `pages[last].items`.
|
|
141
|
+
* - `"desc"`: newest comments belong on the FIRST page → optimistic item
|
|
142
|
+
* is prepended to `pages[0].items`.
|
|
143
|
+
*
|
|
144
|
+
* Must match the `sort` passed to `useInfiniteComments` so the optimistic
|
|
145
|
+
* item appears in the same position the server will place it.
|
|
146
|
+
*/
|
|
147
|
+
sort?: "asc" | "desc";
|
|
132
148
|
}): _tanstack_react_query.UseMutationResult<SerializedComment, Error, {
|
|
133
149
|
body: string;
|
|
134
150
|
parentId?: string | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CommentListResult, S as SerializedComment, a as CommentsThreadDiscriminator } from '../../../../shared/stack.
|
|
1
|
+
import { C as CommentListResult, S as SerializedComment, a as CommentsThreadDiscriminator } from '../../../../shared/stack.C7KNNiJm.mjs';
|
|
2
2
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
3
3
|
import { InfiniteData } from '@tanstack/react-query';
|
|
4
4
|
|
|
@@ -81,6 +81,11 @@ declare function useInfiniteComments(config: CommentsClientConfig, params: {
|
|
|
81
81
|
parentId?: string | null;
|
|
82
82
|
status?: "pending" | "approved" | "spam";
|
|
83
83
|
currentUserId?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Sort direction by `createdAt`. Default: `"asc"` (oldest first) — matches
|
|
86
|
+
* the server-side default. Pass `"desc"` for newest-first threads.
|
|
87
|
+
*/
|
|
88
|
+
sort?: "asc" | "desc";
|
|
84
89
|
pageSize?: number;
|
|
85
90
|
}, options?: {
|
|
86
91
|
enabled?: boolean;
|
|
@@ -129,6 +134,17 @@ declare function usePostComment(config: CommentsClientConfig, params: {
|
|
|
129
134
|
* `nextOffset` from `lastPage.limit` instead of a hardcoded fallback.
|
|
130
135
|
*/
|
|
131
136
|
pageSize?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Sort direction of the surrounding infinite thread.
|
|
139
|
+
* - `"asc"` (default): newest comments belong on the LAST page → optimistic
|
|
140
|
+
* item is appended to `pages[last].items`.
|
|
141
|
+
* - `"desc"`: newest comments belong on the FIRST page → optimistic item
|
|
142
|
+
* is prepended to `pages[0].items`.
|
|
143
|
+
*
|
|
144
|
+
* Must match the `sort` passed to `useInfiniteComments` so the optimistic
|
|
145
|
+
* item appears in the same position the server will place it.
|
|
146
|
+
*/
|
|
147
|
+
sort?: "asc" | "desc";
|
|
132
148
|
}): _tanstack_react_query.UseMutationResult<SerializedComment, Error, {
|
|
133
149
|
body: string;
|
|
134
150
|
parentId?: string | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CommentListResult, S as SerializedComment, a as CommentsThreadDiscriminator } from '../../../../shared/stack.
|
|
1
|
+
import { C as CommentListResult, S as SerializedComment, a as CommentsThreadDiscriminator } from '../../../../shared/stack.C7KNNiJm.js';
|
|
2
2
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
3
3
|
import { InfiniteData } from '@tanstack/react-query';
|
|
4
4
|
|
|
@@ -81,6 +81,11 @@ declare function useInfiniteComments(config: CommentsClientConfig, params: {
|
|
|
81
81
|
parentId?: string | null;
|
|
82
82
|
status?: "pending" | "approved" | "spam";
|
|
83
83
|
currentUserId?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Sort direction by `createdAt`. Default: `"asc"` (oldest first) — matches
|
|
86
|
+
* the server-side default. Pass `"desc"` for newest-first threads.
|
|
87
|
+
*/
|
|
88
|
+
sort?: "asc" | "desc";
|
|
84
89
|
pageSize?: number;
|
|
85
90
|
}, options?: {
|
|
86
91
|
enabled?: boolean;
|
|
@@ -129,6 +134,17 @@ declare function usePostComment(config: CommentsClientConfig, params: {
|
|
|
129
134
|
* `nextOffset` from `lastPage.limit` instead of a hardcoded fallback.
|
|
130
135
|
*/
|
|
131
136
|
pageSize?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Sort direction of the surrounding infinite thread.
|
|
139
|
+
* - `"asc"` (default): newest comments belong on the LAST page → optimistic
|
|
140
|
+
* item is appended to `pages[last].items`.
|
|
141
|
+
* - `"desc"`: newest comments belong on the FIRST page → optimistic item
|
|
142
|
+
* is prepended to `pages[0].items`.
|
|
143
|
+
*
|
|
144
|
+
* Must match the `sort` passed to `useInfiniteComments` so the optimistic
|
|
145
|
+
* item appears in the same position the server will place it.
|
|
146
|
+
*/
|
|
147
|
+
sort?: "asc" | "desc";
|
|
132
148
|
}): _tanstack_react_query.UseMutationResult<SerializedComment, Error, {
|
|
133
149
|
body: string;
|
|
134
150
|
parentId?: string | null;
|
|
@@ -197,6 +197,15 @@ interface CommentsPluginOverrides {
|
|
|
197
197
|
* Defaults to 100 when not set.
|
|
198
198
|
*/
|
|
199
199
|
defaultCommentPageSize?: number;
|
|
200
|
+
/**
|
|
201
|
+
* Default sort direction (by `createdAt`) for top-level comments in
|
|
202
|
+
* `CommentThread`.
|
|
203
|
+
* - `"desc"` (default): newest comments first.
|
|
204
|
+
* - `"asc"`: oldest comments first.
|
|
205
|
+
*
|
|
206
|
+
* Can be overridden per-instance via the `sort` prop on `CommentThread`.
|
|
207
|
+
*/
|
|
208
|
+
defaultCommentSort?: "asc" | "desc";
|
|
200
209
|
/**
|
|
201
210
|
* When false, the comment form and reply buttons are hidden in all
|
|
202
211
|
* `CommentThread` instances. Users can still read existing comments.
|
|
@@ -197,6 +197,15 @@ interface CommentsPluginOverrides {
|
|
|
197
197
|
* Defaults to 100 when not set.
|
|
198
198
|
*/
|
|
199
199
|
defaultCommentPageSize?: number;
|
|
200
|
+
/**
|
|
201
|
+
* Default sort direction (by `createdAt`) for top-level comments in
|
|
202
|
+
* `CommentThread`.
|
|
203
|
+
* - `"desc"` (default): newest comments first.
|
|
204
|
+
* - `"asc"`: oldest comments first.
|
|
205
|
+
*
|
|
206
|
+
* Can be overridden per-instance via the `sort` prop on `CommentThread`.
|
|
207
|
+
*/
|
|
208
|
+
defaultCommentSort?: "asc" | "desc";
|
|
200
209
|
/**
|
|
201
210
|
* When false, the comment form and reply buttons are hidden in all
|
|
202
211
|
* `CommentThread` instances. Users can still read existing comments.
|
|
@@ -197,6 +197,15 @@ interface CommentsPluginOverrides {
|
|
|
197
197
|
* Defaults to 100 when not set.
|
|
198
198
|
*/
|
|
199
199
|
defaultCommentPageSize?: number;
|
|
200
|
+
/**
|
|
201
|
+
* Default sort direction (by `createdAt`) for top-level comments in
|
|
202
|
+
* `CommentThread`.
|
|
203
|
+
* - `"desc"` (default): newest comments first.
|
|
204
|
+
* - `"asc"`: oldest comments first.
|
|
205
|
+
*
|
|
206
|
+
* Can be overridden per-instance via the `sort` prop on `CommentThread`.
|
|
207
|
+
*/
|
|
208
|
+
defaultCommentSort?: "asc" | "desc";
|
|
200
209
|
/**
|
|
201
210
|
* When false, the comment form and reply buttons are hidden in all
|
|
202
211
|
* `CommentThread` instances. Users can still read existing comments.
|
|
@@ -90,6 +90,7 @@ function createCommentsThreadQueries(client, headers) {
|
|
|
90
90
|
// The server resolves the caller's identity server-side via the
|
|
91
91
|
// resolveCurrentUserId hook. It is still included in the queryKey
|
|
92
92
|
// above for client-side cache segregation.
|
|
93
|
+
sort: params?.sort,
|
|
93
94
|
limit: params?.limit ?? 20,
|
|
94
95
|
offset: pageParam ?? 0
|
|
95
96
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.
|
|
2
|
+
import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.C7KNNiJm.cjs';
|
|
3
3
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
4
|
-
import { C as CommentsApiRouter } from '../../shared/stack.
|
|
4
|
+
import { C as CommentsApiRouter } from '../../shared/stack.CCncwK6g.cjs';
|
|
5
5
|
import '@btst/stack/plugins/api';
|
|
6
6
|
import 'better-call';
|
|
7
7
|
import 'zod';
|
|
@@ -56,6 +56,7 @@ declare function createCommentsQueryKeys(client: ReturnType<typeof createApiClie
|
|
|
56
56
|
parentId?: string | null;
|
|
57
57
|
status?: "pending" | "approved" | "spam";
|
|
58
58
|
currentUserId?: string;
|
|
59
|
+
sort?: "asc" | "desc";
|
|
59
60
|
limit?: number;
|
|
60
61
|
} | undefined) => Omit<Omit<{
|
|
61
62
|
queryKey: readonly ["commentsThread", "list", CommentsThreadDiscriminator];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.
|
|
2
|
+
import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.C7KNNiJm.mjs';
|
|
3
3
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
4
|
-
import { C as CommentsApiRouter } from '../../shared/stack.
|
|
4
|
+
import { C as CommentsApiRouter } from '../../shared/stack.CO1g47IS.mjs';
|
|
5
5
|
import '@btst/stack/plugins/api';
|
|
6
6
|
import 'better-call';
|
|
7
7
|
import 'zod';
|
|
@@ -56,6 +56,7 @@ declare function createCommentsQueryKeys(client: ReturnType<typeof createApiClie
|
|
|
56
56
|
parentId?: string | null;
|
|
57
57
|
status?: "pending" | "approved" | "spam";
|
|
58
58
|
currentUserId?: string;
|
|
59
|
+
sort?: "asc" | "desc";
|
|
59
60
|
limit?: number;
|
|
60
61
|
} | undefined) => Omit<Omit<{
|
|
61
62
|
queryKey: readonly ["commentsThread", "list", CommentsThreadDiscriminator];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.
|
|
2
|
+
import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.C7KNNiJm.js';
|
|
3
3
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
4
|
-
import { C as CommentsApiRouter } from '../../shared/stack.
|
|
4
|
+
import { C as CommentsApiRouter } from '../../shared/stack.Bq3kiUaM.js';
|
|
5
5
|
import '@btst/stack/plugins/api';
|
|
6
6
|
import 'better-call';
|
|
7
7
|
import 'zod';
|
|
@@ -56,6 +56,7 @@ declare function createCommentsQueryKeys(client: ReturnType<typeof createApiClie
|
|
|
56
56
|
parentId?: string | null;
|
|
57
57
|
status?: "pending" | "approved" | "spam";
|
|
58
58
|
currentUserId?: string;
|
|
59
|
+
sort?: "asc" | "desc";
|
|
59
60
|
limit?: number;
|
|
60
61
|
} | undefined) => Omit<Omit<{
|
|
61
62
|
queryKey: readonly ["commentsThread", "list", CommentsThreadDiscriminator];
|
|
@@ -88,6 +88,7 @@ function createCommentsThreadQueries(client, headers) {
|
|
|
88
88
|
// The server resolves the caller's identity server-side via the
|
|
89
89
|
// resolveCurrentUserId hook. It is still included in the queryKey
|
|
90
90
|
// above for client-side cache segregation.
|
|
91
|
+
sort: params?.sort,
|
|
91
92
|
limit: params?.limit ?? 20,
|
|
92
93
|
offset: pageParam ?? 0
|
|
93
94
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
|
|
2
|
-
import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.
|
|
2
|
+
import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.C7KNNiJm.js';
|
|
3
3
|
import * as better_call from 'better-call';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
@@ -90,6 +90,7 @@ interface CommentsThreadDiscriminator {
|
|
|
90
90
|
parentId: string | null | undefined;
|
|
91
91
|
status: string | undefined;
|
|
92
92
|
currentUserId: string | undefined;
|
|
93
|
+
sort: string | undefined;
|
|
93
94
|
limit: number;
|
|
94
95
|
}
|
|
95
96
|
/** Full query key builders — use with queryClient.setQueryData() */
|
|
@@ -125,7 +126,7 @@ declare const COMMENTS_QUERY_KEYS: {
|
|
|
125
126
|
}) => readonly ["comments", "count", CommentCountDiscriminator];
|
|
126
127
|
/**
|
|
127
128
|
* Key for the infinite thread query (top-level comments, load-more).
|
|
128
|
-
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, limit }]
|
|
129
|
+
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, sort, limit }]
|
|
129
130
|
* Offset is excluded — it is driven by `pageParam`, not baked into the key.
|
|
130
131
|
*/
|
|
131
132
|
commentsThread: (params?: {
|
|
@@ -134,6 +135,7 @@ declare const COMMENTS_QUERY_KEYS: {
|
|
|
134
135
|
parentId?: string | null;
|
|
135
136
|
status?: string;
|
|
136
137
|
currentUserId?: string;
|
|
138
|
+
sort?: string;
|
|
137
139
|
limit?: number;
|
|
138
140
|
}) => readonly ["commentsThread", "list", CommentsThreadDiscriminator];
|
|
139
141
|
};
|
|
@@ -90,6 +90,7 @@ interface CommentsThreadDiscriminator {
|
|
|
90
90
|
parentId: string | null | undefined;
|
|
91
91
|
status: string | undefined;
|
|
92
92
|
currentUserId: string | undefined;
|
|
93
|
+
sort: string | undefined;
|
|
93
94
|
limit: number;
|
|
94
95
|
}
|
|
95
96
|
/** Full query key builders — use with queryClient.setQueryData() */
|
|
@@ -125,7 +126,7 @@ declare const COMMENTS_QUERY_KEYS: {
|
|
|
125
126
|
}) => readonly ["comments", "count", CommentCountDiscriminator];
|
|
126
127
|
/**
|
|
127
128
|
* Key for the infinite thread query (top-level comments, load-more).
|
|
128
|
-
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, limit }]
|
|
129
|
+
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, sort, limit }]
|
|
129
130
|
* Offset is excluded — it is driven by `pageParam`, not baked into the key.
|
|
130
131
|
*/
|
|
131
132
|
commentsThread: (params?: {
|
|
@@ -134,6 +135,7 @@ declare const COMMENTS_QUERY_KEYS: {
|
|
|
134
135
|
parentId?: string | null;
|
|
135
136
|
status?: string;
|
|
136
137
|
currentUserId?: string;
|
|
138
|
+
sort?: string;
|
|
137
139
|
limit?: number;
|
|
138
140
|
}) => readonly ["commentsThread", "list", CommentsThreadDiscriminator];
|
|
139
141
|
};
|
|
@@ -90,6 +90,7 @@ interface CommentsThreadDiscriminator {
|
|
|
90
90
|
parentId: string | null | undefined;
|
|
91
91
|
status: string | undefined;
|
|
92
92
|
currentUserId: string | undefined;
|
|
93
|
+
sort: string | undefined;
|
|
93
94
|
limit: number;
|
|
94
95
|
}
|
|
95
96
|
/** Full query key builders — use with queryClient.setQueryData() */
|
|
@@ -125,7 +126,7 @@ declare const COMMENTS_QUERY_KEYS: {
|
|
|
125
126
|
}) => readonly ["comments", "count", CommentCountDiscriminator];
|
|
126
127
|
/**
|
|
127
128
|
* Key for the infinite thread query (top-level comments, load-more).
|
|
128
|
-
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, limit }]
|
|
129
|
+
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, sort, limit }]
|
|
129
130
|
* Offset is excluded — it is driven by `pageParam`, not baked into the key.
|
|
130
131
|
*/
|
|
131
132
|
commentsThread: (params?: {
|
|
@@ -134,6 +135,7 @@ declare const COMMENTS_QUERY_KEYS: {
|
|
|
134
135
|
parentId?: string | null;
|
|
135
136
|
status?: string;
|
|
136
137
|
currentUserId?: string;
|
|
138
|
+
sort?: string;
|
|
137
139
|
limit?: number;
|
|
138
140
|
}) => readonly ["commentsThread", "list", CommentsThreadDiscriminator];
|
|
139
141
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
|
|
2
|
-
import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.
|
|
2
|
+
import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.C7KNNiJm.cjs';
|
|
3
3
|
import * as better_call from 'better-call';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
|
|
2
|
-
import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.
|
|
2
|
+
import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.C7KNNiJm.mjs';
|
|
3
3
|
import * as better_call from 'better-call';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
package/package.json
CHANGED
|
@@ -71,6 +71,7 @@ export interface CommentsThreadDiscriminator {
|
|
|
71
71
|
parentId: string | null | undefined;
|
|
72
72
|
status: string | undefined;
|
|
73
73
|
currentUserId: string | undefined;
|
|
74
|
+
sort: string | undefined;
|
|
74
75
|
limit: number;
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -80,6 +81,7 @@ export function commentsThreadDiscriminator(params?: {
|
|
|
80
81
|
parentId?: string | null;
|
|
81
82
|
status?: string;
|
|
82
83
|
currentUserId?: string;
|
|
84
|
+
sort?: string;
|
|
83
85
|
limit?: number;
|
|
84
86
|
}): CommentsThreadDiscriminator {
|
|
85
87
|
return {
|
|
@@ -88,6 +90,7 @@ export function commentsThreadDiscriminator(params?: {
|
|
|
88
90
|
parentId: params?.parentId,
|
|
89
91
|
status: params?.status,
|
|
90
92
|
currentUserId: params?.currentUserId,
|
|
93
|
+
sort: params?.sort,
|
|
91
94
|
limit: params?.limit ?? 20,
|
|
92
95
|
};
|
|
93
96
|
}
|
|
@@ -128,7 +131,7 @@ export const COMMENTS_QUERY_KEYS = {
|
|
|
128
131
|
|
|
129
132
|
/**
|
|
130
133
|
* Key for the infinite thread query (top-level comments, load-more).
|
|
131
|
-
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, limit }]
|
|
134
|
+
* Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, sort, limit }]
|
|
132
135
|
* Offset is excluded — it is driven by `pageParam`, not baked into the key.
|
|
133
136
|
*/
|
|
134
137
|
commentsThread: (params?: {
|
|
@@ -137,6 +140,7 @@ export const COMMENTS_QUERY_KEYS = {
|
|
|
137
140
|
parentId?: string | null;
|
|
138
141
|
status?: string;
|
|
139
142
|
currentUserId?: string;
|
|
143
|
+
sort?: string;
|
|
140
144
|
limit?: number;
|
|
141
145
|
}) =>
|
|
142
146
|
["commentsThread", "list", commentsThreadDiscriminator(params)] as const,
|
|
@@ -98,6 +98,17 @@ export interface CommentThreadProps {
|
|
|
98
98
|
* Defaults to true.
|
|
99
99
|
*/
|
|
100
100
|
allowEditing?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Sort direction for top-level comments by `createdAt`.
|
|
103
|
+
* - `"desc"` (default): newest first.
|
|
104
|
+
* - `"asc"`: oldest first.
|
|
105
|
+
*
|
|
106
|
+
* Replies inside each thread always render chronologically (oldest → newest)
|
|
107
|
+
* and are unaffected by this prop.
|
|
108
|
+
*
|
|
109
|
+
* Overrides the global `defaultCommentSort` from `CommentsPluginOverrides`.
|
|
110
|
+
*/
|
|
111
|
+
sort?: "asc" | "desc";
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
const DEFAULT_RENDERER: ComponentType<CommentRendererProps> = ({ body }) => (
|
|
@@ -325,6 +336,7 @@ function CommentThreadInner({
|
|
|
325
336
|
pageSize: pageSizeProp,
|
|
326
337
|
allowPosting: allowPostingProp,
|
|
327
338
|
allowEditing: allowEditingProp,
|
|
339
|
+
sort: sortProp,
|
|
328
340
|
}: CommentThreadProps) {
|
|
329
341
|
const overrides = usePluginOverrides<
|
|
330
342
|
CommentsPluginOverrides,
|
|
@@ -334,6 +346,7 @@ function CommentThreadInner({
|
|
|
334
346
|
pageSizeProp ?? overrides.defaultCommentPageSize ?? DEFAULT_PAGE_SIZE;
|
|
335
347
|
const allowPosting = allowPostingProp ?? overrides.allowPosting ?? true;
|
|
336
348
|
const allowEditing = allowEditingProp ?? overrides.allowEditing ?? true;
|
|
349
|
+
const sort = sortProp ?? overrides.defaultCommentSort ?? "desc";
|
|
337
350
|
const loc = { ...COMMENTS_LOCALIZATION, ...localizationProp };
|
|
338
351
|
const [replyingTo, setReplyingTo] = useState<string | null>(null);
|
|
339
352
|
const [expandedReplies, setExpandedReplies] = useState<Set<string>>(
|
|
@@ -357,6 +370,7 @@ function CommentThreadInner({
|
|
|
357
370
|
status: "approved",
|
|
358
371
|
parentId: null,
|
|
359
372
|
currentUserId,
|
|
373
|
+
sort,
|
|
360
374
|
pageSize,
|
|
361
375
|
});
|
|
362
376
|
|
|
@@ -366,6 +380,7 @@ function CommentThreadInner({
|
|
|
366
380
|
currentUserId,
|
|
367
381
|
infiniteKey: threadQueryKey,
|
|
368
382
|
pageSize,
|
|
383
|
+
sort,
|
|
369
384
|
});
|
|
370
385
|
|
|
371
386
|
const handlePost = async (body: string) => {
|
|
@@ -164,6 +164,11 @@ export function useInfiniteComments(
|
|
|
164
164
|
parentId?: string | null;
|
|
165
165
|
status?: "pending" | "approved" | "spam";
|
|
166
166
|
currentUserId?: string;
|
|
167
|
+
/**
|
|
168
|
+
* Sort direction by `createdAt`. Default: `"asc"` (oldest first) — matches
|
|
169
|
+
* the server-side default. Pass `"desc"` for newest-first threads.
|
|
170
|
+
*/
|
|
171
|
+
sort?: "asc" | "desc";
|
|
167
172
|
pageSize?: number;
|
|
168
173
|
},
|
|
169
174
|
options?: { enabled?: boolean },
|
|
@@ -178,6 +183,7 @@ export function useInfiniteComments(
|
|
|
178
183
|
parentId: params.parentId ?? null,
|
|
179
184
|
status: params.status,
|
|
180
185
|
currentUserId: params.currentUserId,
|
|
186
|
+
sort: params.sort,
|
|
181
187
|
limit: pageSize,
|
|
182
188
|
});
|
|
183
189
|
|
|
@@ -266,6 +272,17 @@ export function usePostComment(
|
|
|
266
272
|
* `nextOffset` from `lastPage.limit` instead of a hardcoded fallback.
|
|
267
273
|
*/
|
|
268
274
|
pageSize?: number;
|
|
275
|
+
/**
|
|
276
|
+
* Sort direction of the surrounding infinite thread.
|
|
277
|
+
* - `"asc"` (default): newest comments belong on the LAST page → optimistic
|
|
278
|
+
* item is appended to `pages[last].items`.
|
|
279
|
+
* - `"desc"`: newest comments belong on the FIRST page → optimistic item
|
|
280
|
+
* is prepended to `pages[0].items`.
|
|
281
|
+
*
|
|
282
|
+
* Must match the `sort` passed to `useInfiniteComments` so the optimistic
|
|
283
|
+
* item appears in the same position the server will place it.
|
|
284
|
+
*/
|
|
285
|
+
sort?: "asc" | "desc";
|
|
269
286
|
},
|
|
270
287
|
) {
|
|
271
288
|
const queryClient = useQueryClient();
|
|
@@ -350,6 +367,7 @@ export function usePostComment(
|
|
|
350
367
|
const previous =
|
|
351
368
|
queryClient.getQueryData<InfiniteData<CommentListResult>>(listKey);
|
|
352
369
|
|
|
370
|
+
const isDesc = params.sort === "desc";
|
|
353
371
|
queryClient.setQueryData<InfiniteData<CommentListResult>>(
|
|
354
372
|
listKey,
|
|
355
373
|
(old) => {
|
|
@@ -366,16 +384,21 @@ export function usePostComment(
|
|
|
366
384
|
pageParams: [0],
|
|
367
385
|
};
|
|
368
386
|
}
|
|
369
|
-
|
|
387
|
+
// For asc (oldest-first) threads the new comment lives at the end →
|
|
388
|
+
// append to the last page. For desc (newest-first) threads it lives
|
|
389
|
+
// at the top → prepend to the first page.
|
|
390
|
+
const targetIdx = isDesc ? 0 : old.pages.length - 1;
|
|
370
391
|
return {
|
|
371
392
|
...old,
|
|
372
393
|
// Increment `total` on every page so the header count (which reads
|
|
373
394
|
// pages[0].total) stays in sync even after multiple pages are loaded.
|
|
374
395
|
pages: old.pages.map((page, idx) =>
|
|
375
|
-
idx ===
|
|
396
|
+
idx === targetIdx
|
|
376
397
|
? {
|
|
377
398
|
...page,
|
|
378
|
-
items:
|
|
399
|
+
items: isDesc
|
|
400
|
+
? [optimistic, ...page.items]
|
|
401
|
+
: [...page.items, optimistic],
|
|
379
402
|
total: page.total + 1,
|
|
380
403
|
}
|
|
381
404
|
: { ...page, total: page.total + 1 },
|
|
@@ -75,6 +75,16 @@ export interface CommentsPluginOverrides {
|
|
|
75
75
|
*/
|
|
76
76
|
defaultCommentPageSize?: number;
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Default sort direction (by `createdAt`) for top-level comments in
|
|
80
|
+
* `CommentThread`.
|
|
81
|
+
* - `"desc"` (default): newest comments first.
|
|
82
|
+
* - `"asc"`: oldest comments first.
|
|
83
|
+
*
|
|
84
|
+
* Can be overridden per-instance via the `sort` prop on `CommentThread`.
|
|
85
|
+
*/
|
|
86
|
+
defaultCommentSort?: "asc" | "desc";
|
|
87
|
+
|
|
78
88
|
/**
|
|
79
89
|
* When false, the comment form and reply buttons are hidden in all
|
|
80
90
|
* `CommentThread` instances. Users can still read existing comments.
|
|
@@ -142,6 +142,7 @@ function createCommentsThreadQueries(
|
|
|
142
142
|
parentId?: string | null;
|
|
143
143
|
status?: "pending" | "approved" | "spam";
|
|
144
144
|
currentUserId?: string;
|
|
145
|
+
sort?: "asc" | "desc";
|
|
145
146
|
limit?: number;
|
|
146
147
|
}) => ({
|
|
147
148
|
// Offset is excluded from the key — it is driven by pageParam.
|
|
@@ -162,6 +163,7 @@ function createCommentsThreadQueries(
|
|
|
162
163
|
// The server resolves the caller's identity server-side via the
|
|
163
164
|
// resolveCurrentUserId hook. It is still included in the queryKey
|
|
164
165
|
// above for client-side cache segregation.
|
|
166
|
+
sort: params?.sort,
|
|
165
167
|
limit: params?.limit ?? 20,
|
|
166
168
|
offset: pageParam ?? 0,
|
|
167
169
|
},
|