@btst/stack 2.7.0 → 2.8.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/README.md +1 -0
- package/dist/packages/stack/src/plugins/blog/client/components/loading/post-navigation-skeleton.cjs +13 -0
- package/dist/packages/stack/src/plugins/blog/client/components/loading/post-navigation-skeleton.mjs +11 -0
- package/dist/packages/stack/src/plugins/blog/client/components/loading/recent-posts-carousel-skeleton.cjs +17 -0
- package/dist/packages/stack/src/plugins/blog/client/components/loading/recent-posts-carousel-skeleton.mjs +15 -0
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +18 -7
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +18 -7
- package/dist/packages/stack/src/plugins/blog/client/components/shared/post-navigation.cjs +48 -52
- package/dist/packages/stack/src/plugins/blog/client/components/shared/post-navigation.mjs +49 -53
- package/dist/packages/stack/src/plugins/blog/client/components/shared/recent-posts-carousel.cjs +34 -37
- package/dist/packages/stack/src/plugins/blog/client/components/shared/recent-posts-carousel.mjs +35 -38
- package/dist/packages/stack/src/plugins/blog/client/hooks/blog-hooks.cjs +4 -21
- package/dist/packages/stack/src/plugins/blog/client/hooks/blog-hooks.mjs +4 -21
- package/dist/packages/stack/src/plugins/comments/api/getters.cjs +284 -0
- package/dist/packages/stack/src/plugins/comments/api/getters.mjs +280 -0
- package/dist/packages/stack/src/plugins/comments/api/mutations.cjs +118 -0
- package/dist/packages/stack/src/plugins/comments/api/mutations.mjs +112 -0
- package/dist/packages/stack/src/plugins/comments/api/plugin.cjs +335 -0
- package/dist/packages/stack/src/plugins/comments/api/plugin.mjs +333 -0
- package/dist/packages/stack/src/plugins/comments/api/query-key-defs.cjs +60 -0
- package/dist/packages/stack/src/plugins/comments/api/query-key-defs.mjs +55 -0
- package/dist/packages/stack/src/plugins/comments/api/serializers.cjs +23 -0
- package/dist/packages/stack/src/plugins/comments/api/serializers.mjs +21 -0
- package/dist/packages/stack/src/plugins/comments/client/components/comment-count.cjs +46 -0
- package/dist/packages/stack/src/plugins/comments/client/components/comment-count.mjs +44 -0
- package/dist/packages/stack/src/plugins/comments/client/components/comment-form.cjs +86 -0
- package/dist/packages/stack/src/plugins/comments/client/components/comment-form.mjs +84 -0
- package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.cjs +540 -0
- package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.mjs +538 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.cjs +64 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.internal.cjs +426 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.internal.mjs +424 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.mjs +62 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.cjs +66 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.internal.cjs +256 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.internal.mjs +254 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.mjs +64 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.cjs +86 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.internal.cjs +191 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.internal.mjs +189 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.mjs +84 -0
- package/dist/packages/stack/src/plugins/comments/client/components/shared/page-wrapper.cjs +27 -0
- package/dist/packages/stack/src/plugins/comments/client/components/shared/page-wrapper.mjs +25 -0
- package/dist/packages/stack/src/plugins/comments/client/components/shared/pagination.cjs +37 -0
- package/dist/packages/stack/src/plugins/comments/client/components/shared/pagination.mjs +35 -0
- package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.cjs +476 -0
- package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.mjs +464 -0
- package/dist/packages/stack/src/plugins/comments/client/localization/comments-moderation.cjs +67 -0
- package/dist/packages/stack/src/plugins/comments/client/localization/comments-moderation.mjs +65 -0
- package/dist/packages/stack/src/plugins/comments/client/localization/comments-my.cjs +27 -0
- package/dist/packages/stack/src/plugins/comments/client/localization/comments-my.mjs +25 -0
- package/dist/packages/stack/src/plugins/comments/client/localization/comments-thread.cjs +30 -0
- package/dist/packages/stack/src/plugins/comments/client/localization/comments-thread.mjs +28 -0
- package/dist/packages/stack/src/plugins/comments/client/localization/index.cjs +13 -0
- package/dist/packages/stack/src/plugins/comments/client/localization/index.mjs +11 -0
- package/dist/packages/stack/src/plugins/comments/client/plugin.cjs +116 -0
- package/dist/packages/stack/src/plugins/comments/client/plugin.mjs +114 -0
- package/dist/packages/stack/src/plugins/comments/client/utils.cjs +41 -0
- package/dist/packages/stack/src/plugins/comments/client/utils.mjs +37 -0
- package/dist/packages/stack/src/plugins/comments/db.cjs +75 -0
- package/dist/packages/stack/src/plugins/comments/db.mjs +73 -0
- package/dist/packages/stack/src/plugins/comments/schemas.cjs +45 -0
- package/dist/packages/stack/src/plugins/comments/schemas.mjs +38 -0
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +0 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +0 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.internal.cjs +39 -22
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.internal.mjs +40 -23
- package/dist/packages/ui/src/components/avatar.mjs +1 -1
- package/dist/packages/ui/src/components/pagination-controls.cjs +64 -0
- package/dist/packages/ui/src/components/pagination-controls.mjs +62 -0
- package/dist/packages/ui/src/components/when-visible.cjs +39 -0
- package/dist/packages/ui/src/components/when-visible.mjs +37 -0
- package/dist/plugins/blog/client/hooks/index.d.cts +1 -1
- package/dist/plugins/blog/client/hooks/index.d.mts +1 -1
- package/dist/plugins/blog/client/hooks/index.d.ts +1 -1
- package/dist/plugins/blog/client/index.d.cts +24 -2
- package/dist/plugins/blog/client/index.d.mts +24 -2
- package/dist/plugins/blog/client/index.d.ts +24 -2
- package/dist/plugins/comments/api/index.cjs +21 -0
- package/dist/plugins/comments/api/index.d.cts +126 -0
- package/dist/plugins/comments/api/index.d.mts +126 -0
- package/dist/plugins/comments/api/index.d.ts +126 -0
- package/dist/plugins/comments/api/index.mjs +5 -0
- package/dist/plugins/comments/client/components/index.cjs +15 -0
- package/dist/plugins/comments/client/components/index.d.cts +125 -0
- package/dist/plugins/comments/client/components/index.d.mts +125 -0
- package/dist/plugins/comments/client/components/index.d.ts +125 -0
- package/dist/plugins/comments/client/components/index.mjs +5 -0
- package/dist/plugins/comments/client/hooks/index.cjs +17 -0
- package/dist/plugins/comments/client/hooks/index.d.cts +200 -0
- package/dist/plugins/comments/client/hooks/index.d.mts +200 -0
- package/dist/plugins/comments/client/hooks/index.d.ts +200 -0
- package/dist/plugins/comments/client/hooks/index.mjs +1 -0
- package/dist/plugins/comments/client/index.cjs +9 -0
- package/dist/plugins/comments/client/index.d.cts +262 -0
- package/dist/plugins/comments/client/index.d.mts +262 -0
- package/dist/plugins/comments/client/index.d.ts +262 -0
- package/dist/plugins/comments/client/index.mjs +2 -0
- package/dist/plugins/comments/client.css +2 -0
- package/dist/plugins/comments/query-keys.cjs +113 -0
- package/dist/plugins/comments/query-keys.d.cts +71 -0
- package/dist/plugins/comments/query-keys.d.mts +71 -0
- package/dist/plugins/comments/query-keys.d.ts +71 -0
- package/dist/plugins/comments/query-keys.mjs +111 -0
- package/dist/plugins/comments/style.css +15 -0
- package/dist/plugins/kanban/api/index.d.cts +1 -1
- package/dist/plugins/kanban/api/index.d.mts +1 -1
- package/dist/plugins/kanban/api/index.d.ts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
- package/dist/plugins/kanban/client/index.d.cts +1 -1
- package/dist/plugins/kanban/client/index.d.mts +1 -1
- package/dist/plugins/kanban/client/index.d.ts +1 -1
- package/dist/plugins/kanban/query-keys.d.cts +1 -1
- package/dist/plugins/kanban/query-keys.d.mts +1 -1
- package/dist/plugins/kanban/query-keys.d.ts +1 -1
- package/dist/shared/{stack.FeaWkglm.d.ts → stack.BxFl46lB.d.cts} +24 -1
- package/dist/shared/stack.C-b3Sn8j.d.cts +142 -0
- package/dist/shared/stack.C-b3Sn8j.d.mts +142 -0
- package/dist/shared/stack.C-b3Sn8j.d.ts +142 -0
- package/dist/shared/stack.CJE9sAjV.d.ts +335 -0
- package/dist/shared/stack.CmHRdhl8.d.cts +335 -0
- package/dist/shared/{stack.CNLHlv7r.d.mts → stack.DOZ1EXjM.d.mts} +6 -12
- package/dist/shared/{stack.FeaWkglm.d.mts → stack.DRpeDS6X.d.ts} +24 -1
- package/dist/shared/{stack.CQAZwXhV.d.cts → stack.DX-tQ93o.d.cts} +6 -12
- package/dist/shared/stack.Dcz6636A.d.mts +335 -0
- package/dist/shared/{stack.FeaWkglm.d.cts → stack.Jb0kQDJC.d.mts} +24 -1
- package/dist/shared/stack.Ldfkr5b2.d.cts +112 -0
- package/dist/shared/stack.Ldfkr5b2.d.mts +112 -0
- package/dist/shared/stack.Ldfkr5b2.d.ts +112 -0
- package/dist/shared/{stack.D3BsrpAz.d.ts → stack.VF6FhyZw.d.ts} +6 -12
- package/package.json +67 -2
- package/src/plugins/blog/client/components/loading/post-navigation-skeleton.tsx +10 -0
- package/src/plugins/blog/client/components/loading/recent-posts-carousel-skeleton.tsx +18 -0
- package/src/plugins/blog/client/components/pages/post-page.internal.tsx +23 -8
- package/src/plugins/blog/client/components/shared/post-navigation.tsx +0 -5
- package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +1 -5
- package/src/plugins/blog/client/hooks/blog-hooks.tsx +8 -33
- package/src/plugins/blog/client/overrides.ts +26 -1
- package/src/plugins/cms/client/components/shared/pagination.tsx +14 -42
- package/src/plugins/comments/api/getters.ts +444 -0
- package/src/plugins/comments/api/index.ts +21 -0
- package/src/plugins/comments/api/mutations.ts +206 -0
- package/src/plugins/comments/api/plugin.ts +628 -0
- package/src/plugins/comments/api/query-key-defs.ts +143 -0
- package/src/plugins/comments/api/serializers.ts +37 -0
- package/src/plugins/comments/client/components/comment-count.tsx +66 -0
- package/src/plugins/comments/client/components/comment-form.tsx +112 -0
- package/src/plugins/comments/client/components/comment-thread.tsx +799 -0
- package/src/plugins/comments/client/components/index.tsx +11 -0
- package/src/plugins/comments/client/components/pages/moderation-page.internal.tsx +550 -0
- package/src/plugins/comments/client/components/pages/moderation-page.tsx +70 -0
- package/src/plugins/comments/client/components/pages/my-comments-page.internal.tsx +367 -0
- package/src/plugins/comments/client/components/pages/my-comments-page.tsx +72 -0
- package/src/plugins/comments/client/components/pages/resource-comments-page.internal.tsx +225 -0
- package/src/plugins/comments/client/components/pages/resource-comments-page.tsx +97 -0
- package/src/plugins/comments/client/components/shared/page-wrapper.tsx +32 -0
- package/src/plugins/comments/client/components/shared/pagination.tsx +44 -0
- package/src/plugins/comments/client/hooks/index.tsx +13 -0
- package/src/plugins/comments/client/hooks/use-comments.tsx +717 -0
- package/src/plugins/comments/client/index.ts +14 -0
- package/src/plugins/comments/client/localization/comments-moderation.ts +75 -0
- package/src/plugins/comments/client/localization/comments-my.ts +32 -0
- package/src/plugins/comments/client/localization/comments-thread.ts +32 -0
- package/src/plugins/comments/client/localization/index.ts +11 -0
- package/src/plugins/comments/client/overrides.ts +164 -0
- package/src/plugins/comments/client/plugin.tsx +195 -0
- package/src/plugins/comments/client/utils.ts +67 -0
- package/src/plugins/comments/client.css +2 -0
- package/src/plugins/comments/db.ts +77 -0
- package/src/plugins/comments/query-keys.ts +189 -0
- package/src/plugins/comments/schemas.ts +72 -0
- package/src/plugins/comments/style.css +15 -0
- package/src/plugins/comments/types.ts +73 -0
- package/src/plugins/kanban/client/components/forms/task-form.tsx +0 -1
- package/src/plugins/kanban/client/components/pages/board-page.internal.tsx +46 -27
- package/src/plugins/kanban/client/overrides.ts +27 -1
- package/dist/shared/{stack.Rtcvl8sS.d.cts → stack.BOokfhZD.d.cts} +3 -3
- package/dist/shared/{stack.D4Cea8II.d.ts → stack.BvCR4-9H.d.ts} +3 -3
- package/dist/shared/{stack.HE_IvqV5.d.mts → stack.CWxAl9K3.d.mts} +3 -3
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mergeQueryKeys,
|
|
3
|
+
createQueryKeys,
|
|
4
|
+
} from "@lukemorales/query-key-factory";
|
|
5
|
+
import type { CommentsApiRouter } from "./api";
|
|
6
|
+
import { createApiClient } from "@btst/stack/plugins/client";
|
|
7
|
+
import type { CommentListResult } from "./types";
|
|
8
|
+
import {
|
|
9
|
+
commentsListDiscriminator,
|
|
10
|
+
commentCountDiscriminator,
|
|
11
|
+
commentsThreadDiscriminator,
|
|
12
|
+
} from "./api/query-key-defs";
|
|
13
|
+
import { toError } from "./client/utils";
|
|
14
|
+
|
|
15
|
+
interface CommentsListParams {
|
|
16
|
+
resourceId?: string;
|
|
17
|
+
resourceType?: string;
|
|
18
|
+
parentId?: string | null;
|
|
19
|
+
status?: "pending" | "approved" | "spam";
|
|
20
|
+
currentUserId?: string;
|
|
21
|
+
authorId?: string;
|
|
22
|
+
sort?: "asc" | "desc";
|
|
23
|
+
limit?: number;
|
|
24
|
+
offset?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface CommentCountParams {
|
|
28
|
+
resourceId: string;
|
|
29
|
+
resourceType: string;
|
|
30
|
+
status?: "pending" | "approved" | "spam";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isErrorResponse(
|
|
34
|
+
response: unknown,
|
|
35
|
+
): response is { error: unknown; data?: never } {
|
|
36
|
+
return (
|
|
37
|
+
typeof response === "object" &&
|
|
38
|
+
response !== null &&
|
|
39
|
+
"error" in response &&
|
|
40
|
+
(response as Record<string, unknown>).error !== null &&
|
|
41
|
+
(response as Record<string, unknown>).error !== undefined
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createCommentsQueryKeys(
|
|
46
|
+
client: ReturnType<typeof createApiClient<CommentsApiRouter>>,
|
|
47
|
+
headers?: HeadersInit,
|
|
48
|
+
) {
|
|
49
|
+
return mergeQueryKeys(
|
|
50
|
+
createCommentsQueries(client, headers),
|
|
51
|
+
createCommentCountQueries(client, headers),
|
|
52
|
+
createCommentsThreadQueries(client, headers),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createCommentsQueries(
|
|
57
|
+
client: ReturnType<typeof createApiClient<CommentsApiRouter>>,
|
|
58
|
+
headers?: HeadersInit,
|
|
59
|
+
) {
|
|
60
|
+
return createQueryKeys("comments", {
|
|
61
|
+
list: (params?: CommentsListParams) => ({
|
|
62
|
+
queryKey: [commentsListDiscriminator(params)],
|
|
63
|
+
queryFn: async (): Promise<CommentListResult> => {
|
|
64
|
+
const response = await client("/comments", {
|
|
65
|
+
method: "GET",
|
|
66
|
+
query: {
|
|
67
|
+
resourceId: params?.resourceId,
|
|
68
|
+
resourceType: params?.resourceType,
|
|
69
|
+
parentId: params?.parentId === null ? "null" : params?.parentId,
|
|
70
|
+
status: params?.status,
|
|
71
|
+
// currentUserId is intentionally NOT sent to the server.
|
|
72
|
+
// The server resolves the caller's identity server-side via the
|
|
73
|
+
// resolveCurrentUserId hook. Sending it would allow any caller to
|
|
74
|
+
// impersonate another user and read their pending comments.
|
|
75
|
+
// It is still included in the queryKey above for client-side
|
|
76
|
+
// cache segregation (different users get different cache entries).
|
|
77
|
+
authorId: params?.authorId,
|
|
78
|
+
sort: params?.sort,
|
|
79
|
+
limit: params?.limit ?? 20,
|
|
80
|
+
offset: params?.offset ?? 0,
|
|
81
|
+
},
|
|
82
|
+
headers,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (isErrorResponse(response)) {
|
|
86
|
+
throw toError((response as { error: unknown }).error);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const data = (response as { data?: unknown }).data as
|
|
90
|
+
| CommentListResult
|
|
91
|
+
| undefined;
|
|
92
|
+
return data ?? { items: [], total: 0, limit: 20, offset: 0 };
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createCommentCountQueries(
|
|
99
|
+
client: ReturnType<typeof createApiClient<CommentsApiRouter>>,
|
|
100
|
+
headers?: HeadersInit,
|
|
101
|
+
) {
|
|
102
|
+
return createQueryKeys("commentCount", {
|
|
103
|
+
byResource: (params: CommentCountParams) => ({
|
|
104
|
+
queryKey: [commentCountDiscriminator(params)],
|
|
105
|
+
queryFn: async (): Promise<number> => {
|
|
106
|
+
const response = await client("/comments/count", {
|
|
107
|
+
method: "GET",
|
|
108
|
+
query: {
|
|
109
|
+
resourceId: params.resourceId,
|
|
110
|
+
resourceType: params.resourceType,
|
|
111
|
+
status: params.status,
|
|
112
|
+
},
|
|
113
|
+
headers,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (isErrorResponse(response)) {
|
|
117
|
+
throw toError((response as { error: unknown }).error);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const data = (response as { data?: unknown }).data as
|
|
121
|
+
| { count: number }
|
|
122
|
+
| undefined;
|
|
123
|
+
return data?.count ?? 0;
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Factory for the infinite thread query key family.
|
|
131
|
+
* Mirrors the blog's `createPostsQueries` pattern: the key is stable (no offset),
|
|
132
|
+
* and pages are fetched via `pageParam` passed to the queryFn.
|
|
133
|
+
*/
|
|
134
|
+
function createCommentsThreadQueries(
|
|
135
|
+
client: ReturnType<typeof createApiClient<CommentsApiRouter>>,
|
|
136
|
+
headers?: HeadersInit,
|
|
137
|
+
) {
|
|
138
|
+
return createQueryKeys("commentsThread", {
|
|
139
|
+
list: (params?: {
|
|
140
|
+
resourceId?: string;
|
|
141
|
+
resourceType?: string;
|
|
142
|
+
parentId?: string | null;
|
|
143
|
+
status?: "pending" | "approved" | "spam";
|
|
144
|
+
currentUserId?: string;
|
|
145
|
+
limit?: number;
|
|
146
|
+
}) => ({
|
|
147
|
+
// Offset is excluded from the key — it is driven by pageParam.
|
|
148
|
+
queryKey: [commentsThreadDiscriminator(params)],
|
|
149
|
+
queryFn: async ({
|
|
150
|
+
pageParam,
|
|
151
|
+
}: {
|
|
152
|
+
pageParam?: number;
|
|
153
|
+
} = {}): Promise<CommentListResult> => {
|
|
154
|
+
const response = await client("/comments", {
|
|
155
|
+
method: "GET",
|
|
156
|
+
query: {
|
|
157
|
+
resourceId: params?.resourceId,
|
|
158
|
+
resourceType: params?.resourceType,
|
|
159
|
+
parentId: params?.parentId === null ? "null" : params?.parentId,
|
|
160
|
+
status: params?.status,
|
|
161
|
+
// currentUserId is intentionally NOT sent to the server.
|
|
162
|
+
// The server resolves the caller's identity server-side via the
|
|
163
|
+
// resolveCurrentUserId hook. It is still included in the queryKey
|
|
164
|
+
// above for client-side cache segregation.
|
|
165
|
+
limit: params?.limit ?? 20,
|
|
166
|
+
offset: pageParam ?? 0,
|
|
167
|
+
},
|
|
168
|
+
headers,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (isErrorResponse(response)) {
|
|
172
|
+
throw toError((response as { error: unknown }).error);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const data = (response as { data?: unknown }).data as
|
|
176
|
+
| CommentListResult
|
|
177
|
+
| undefined;
|
|
178
|
+
return (
|
|
179
|
+
data ?? {
|
|
180
|
+
items: [],
|
|
181
|
+
total: 0,
|
|
182
|
+
limit: params?.limit ?? 20,
|
|
183
|
+
offset: pageParam ?? 0,
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
},
|
|
187
|
+
}),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const CommentStatusSchema = z.enum(["pending", "approved", "spam"]);
|
|
4
|
+
|
|
5
|
+
// ============ Comment Schemas ============
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Schema for the POST /comments request body.
|
|
9
|
+
* authorId is intentionally absent — the server resolves identity from the
|
|
10
|
+
* session inside onBeforePost and injects it. Never trust authorId from the
|
|
11
|
+
* client body.
|
|
12
|
+
*/
|
|
13
|
+
export const createCommentSchema = z.object({
|
|
14
|
+
resourceId: z.string().min(1, "Resource ID is required"),
|
|
15
|
+
resourceType: z.string().min(1, "Resource type is required"),
|
|
16
|
+
parentId: z.string().optional().nullable(),
|
|
17
|
+
body: z.string().min(1, "Body is required").max(10000, "Comment too long"),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Internal schema used after the authorId has been resolved server-side.
|
|
22
|
+
* This is what gets passed to createComment() in mutations.ts.
|
|
23
|
+
*/
|
|
24
|
+
export const createCommentInternalSchema = createCommentSchema.extend({
|
|
25
|
+
authorId: z.string().min(1, "Author ID is required"),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const updateCommentSchema = z.object({
|
|
29
|
+
body: z.string().min(1, "Body is required").max(10000, "Comment too long"),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const updateCommentStatusSchema = z.object({
|
|
33
|
+
status: CommentStatusSchema,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ============ Query Schemas ============
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Schema for GET /comments query parameters.
|
|
40
|
+
*
|
|
41
|
+
* `currentUserId` is intentionally absent — it is never accepted from the client.
|
|
42
|
+
* The server always resolves the caller's identity via the `resolveCurrentUserId`
|
|
43
|
+
* hook and injects it internally. Accepting it from the client would allow any
|
|
44
|
+
* anonymous caller to supply an arbitrary user ID and read that user's pending
|
|
45
|
+
* (pre-moderation) comments.
|
|
46
|
+
*/
|
|
47
|
+
export const CommentListQuerySchema = z.object({
|
|
48
|
+
resourceId: z.string().optional(),
|
|
49
|
+
resourceType: z.string().optional(),
|
|
50
|
+
parentId: z.string().optional().nullable(),
|
|
51
|
+
status: CommentStatusSchema.optional(),
|
|
52
|
+
authorId: z.string().optional(),
|
|
53
|
+
sort: z.enum(["asc", "desc"]).optional(),
|
|
54
|
+
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
55
|
+
offset: z.coerce.number().int().min(0).optional(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Internal params schema used by `listComments()` and the `api` factory.
|
|
60
|
+
* Extends the HTTP query schema with `currentUserId`, which is always injected
|
|
61
|
+
* server-side (either by the HTTP handler via `resolveCurrentUserId`, or by a
|
|
62
|
+
* trusted server-side caller such as a Server Component or cron job).
|
|
63
|
+
*/
|
|
64
|
+
export const CommentListParamsSchema = CommentListQuerySchema.extend({
|
|
65
|
+
currentUserId: z.string().optional(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const CommentCountQuerySchema = z.object({
|
|
69
|
+
resourceId: z.string().min(1),
|
|
70
|
+
resourceType: z.string().min(1),
|
|
71
|
+
status: CommentStatusSchema.optional(),
|
|
72
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@import "./client.css";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Comments Plugin CSS - Includes Tailwind class scanning
|
|
5
|
+
*
|
|
6
|
+
* When consumed from npm, Tailwind v4 will automatically scan this package's
|
|
7
|
+
* source files for Tailwind classes. Consumers only need:
|
|
8
|
+
* @import "@btst/stack/plugins/comments/css";
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* Scan this package's source files for Tailwind classes */
|
|
12
|
+
@source "../../../src/**/*.{ts,tsx}";
|
|
13
|
+
|
|
14
|
+
/* Scan UI package components (when installed as npm package the UI package will be in this dir) */
|
|
15
|
+
@source "../../packages/ui/src";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment status values
|
|
3
|
+
*/
|
|
4
|
+
export type CommentStatus = "pending" | "approved" | "spam";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A comment record as stored in the database
|
|
8
|
+
*/
|
|
9
|
+
export type Comment = {
|
|
10
|
+
id: string;
|
|
11
|
+
resourceId: string;
|
|
12
|
+
resourceType: string;
|
|
13
|
+
parentId: string | null;
|
|
14
|
+
authorId: string;
|
|
15
|
+
body: string;
|
|
16
|
+
status: CommentStatus;
|
|
17
|
+
likes: number;
|
|
18
|
+
editedAt?: Date;
|
|
19
|
+
createdAt: Date;
|
|
20
|
+
updatedAt: Date;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A like record linking an author to a comment
|
|
25
|
+
*/
|
|
26
|
+
export type CommentLike = {
|
|
27
|
+
id: string;
|
|
28
|
+
commentId: string;
|
|
29
|
+
authorId: string;
|
|
30
|
+
createdAt: Date;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A comment enriched with server-resolved author info and like status.
|
|
35
|
+
* All dates are ISO strings (safe for serialisation over HTTP / React Query cache).
|
|
36
|
+
*/
|
|
37
|
+
export interface SerializedComment {
|
|
38
|
+
id: string;
|
|
39
|
+
resourceId: string;
|
|
40
|
+
resourceType: string;
|
|
41
|
+
parentId: string | null;
|
|
42
|
+
authorId: string;
|
|
43
|
+
/** Resolved from resolveUser(authorId). Falls back to "[deleted]" when user cannot be found. */
|
|
44
|
+
resolvedAuthorName: string;
|
|
45
|
+
/** Resolved avatar URL or null */
|
|
46
|
+
resolvedAvatarUrl: string | null;
|
|
47
|
+
body: string;
|
|
48
|
+
status: CommentStatus;
|
|
49
|
+
/** Denormalized counter — updated atomically on toggleLike */
|
|
50
|
+
likes: number;
|
|
51
|
+
/** True when the currentUserId query param matches an existing commentLike row */
|
|
52
|
+
isLikedByCurrentUser: boolean;
|
|
53
|
+
/** ISO string set when the comment body was edited; null for unedited comments */
|
|
54
|
+
editedAt: string | null;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
/**
|
|
58
|
+
* Number of direct replies visible to the requesting user.
|
|
59
|
+
* Includes approved replies plus any pending replies authored by `currentUserId`.
|
|
60
|
+
* Always 0 for reply comments (non-null parentId).
|
|
61
|
+
*/
|
|
62
|
+
replyCount: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Paginated list result for comments
|
|
67
|
+
*/
|
|
68
|
+
export interface CommentListResult {
|
|
69
|
+
items: SerializedComment[];
|
|
70
|
+
total: number;
|
|
71
|
+
limit: number;
|
|
72
|
+
offset: number;
|
|
73
|
+
}
|
|
@@ -66,8 +66,11 @@ export function BoardPage({ boardId }: BoardPageProps) {
|
|
|
66
66
|
throw error;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const {
|
|
70
|
-
|
|
69
|
+
const {
|
|
70
|
+
Link: OverrideLink,
|
|
71
|
+
navigate: overrideNavigate,
|
|
72
|
+
taskDetailBottomSlot,
|
|
73
|
+
} = usePluginOverrides<KanbanPluginOverrides>("kanban");
|
|
71
74
|
const navigate =
|
|
72
75
|
overrideNavigate ||
|
|
73
76
|
((path: string) => {
|
|
@@ -540,33 +543,49 @@ export function BoardPage({ boardId }: BoardPageProps) {
|
|
|
540
543
|
<DialogDescription>Update task details.</DialogDescription>
|
|
541
544
|
</DialogHeader>
|
|
542
545
|
{modalState.type === "editTask" && (
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
refetch();
|
|
555
|
-
}}
|
|
556
|
-
onDelete={async () => {
|
|
557
|
-
try {
|
|
558
|
-
await deleteTask(modalState.taskId);
|
|
546
|
+
<>
|
|
547
|
+
<TaskForm
|
|
548
|
+
columnId={modalState.columnId}
|
|
549
|
+
boardId={boardId}
|
|
550
|
+
taskId={modalState.taskId}
|
|
551
|
+
task={board.columns
|
|
552
|
+
?.find((c) => c.id === modalState.columnId)
|
|
553
|
+
?.tasks?.find((t) => t.id === modalState.taskId)}
|
|
554
|
+
columns={board.columns || []}
|
|
555
|
+
onClose={closeModal}
|
|
556
|
+
onSuccess={() => {
|
|
559
557
|
closeModal();
|
|
560
558
|
refetch();
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
559
|
+
}}
|
|
560
|
+
onDelete={async () => {
|
|
561
|
+
try {
|
|
562
|
+
await deleteTask(modalState.taskId);
|
|
563
|
+
closeModal();
|
|
564
|
+
refetch();
|
|
565
|
+
} catch (error) {
|
|
566
|
+
const message =
|
|
567
|
+
error instanceof Error
|
|
568
|
+
? error.message
|
|
569
|
+
: "Failed to delete task";
|
|
570
|
+
toast.error(message);
|
|
571
|
+
}
|
|
572
|
+
}}
|
|
573
|
+
/>
|
|
574
|
+
{taskDetailBottomSlot &&
|
|
575
|
+
(() => {
|
|
576
|
+
const task = board.columns
|
|
577
|
+
?.find((c) => c.id === modalState.columnId)
|
|
578
|
+
?.tasks?.find((t) => t.id === modalState.taskId);
|
|
579
|
+
return task ? (
|
|
580
|
+
<div
|
|
581
|
+
className="mt-4 pt-4 border-t"
|
|
582
|
+
data-testid="task-detail-bottom-slot"
|
|
583
|
+
>
|
|
584
|
+
{taskDetailBottomSlot(task)}
|
|
585
|
+
</div>
|
|
586
|
+
) : null;
|
|
587
|
+
})()}
|
|
588
|
+
</>
|
|
570
589
|
)}
|
|
571
590
|
</DialogContent>
|
|
572
591
|
</Dialog>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { ComponentType } from "react";
|
|
1
|
+
import type { ComponentType, ReactNode } from "react";
|
|
2
2
|
import type { KanbanLocalization } from "./localization";
|
|
3
|
+
import type { SerializedTask } from "../types";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* User information for assignee display/selection
|
|
@@ -142,4 +143,29 @@ export interface KanbanPluginOverrides {
|
|
|
142
143
|
* @param context - Route context
|
|
143
144
|
*/
|
|
144
145
|
onBeforeNewBoardPageRendered?: (context: RouteContext) => boolean;
|
|
146
|
+
|
|
147
|
+
// ============ Slot Overrides ============
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Optional slot rendered at the bottom of the task detail dialog.
|
|
151
|
+
* Use this to inject a comment thread or any custom content without
|
|
152
|
+
* coupling the kanban plugin to the comments plugin.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* kanban: {
|
|
157
|
+
* taskDetailBottomSlot: (task) => (
|
|
158
|
+
* <CommentThread
|
|
159
|
+
* resourceId={task.id}
|
|
160
|
+
* resourceType="kanban-task"
|
|
161
|
+
* apiBaseURL={apiBaseURL}
|
|
162
|
+
* apiBasePath="/api/data"
|
|
163
|
+
* currentUserId={session?.userId}
|
|
164
|
+
* loginHref="/login"
|
|
165
|
+
* />
|
|
166
|
+
* ),
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
taskDetailBottomSlot?: (task: SerializedTask) => ReactNode;
|
|
145
171
|
}
|
|
@@ -28,8 +28,8 @@ declare const createColumnSchema: z.ZodObject<{
|
|
|
28
28
|
title: z.ZodString;
|
|
29
29
|
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
30
30
|
updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
31
|
-
order: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
32
31
|
boardId: z.ZodString;
|
|
32
|
+
order: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
33
33
|
}, z.core.$strip>;
|
|
34
34
|
declare const updateColumnSchema: z.ZodObject<{
|
|
35
35
|
createdAt: z.ZodOptional<z.ZodOptional<z.ZodCoercedDate<unknown>>>;
|
|
@@ -404,14 +404,14 @@ declare const kanbanBackendPlugin: (hooks?: KanbanBackendHooks) => _btst_stack_p
|
|
|
404
404
|
title?: string | undefined;
|
|
405
405
|
createdAt?: unknown;
|
|
406
406
|
updatedAt?: unknown;
|
|
407
|
-
order?: number | undefined;
|
|
408
407
|
boardId?: string | undefined;
|
|
408
|
+
order?: number | undefined;
|
|
409
409
|
}, {
|
|
410
410
|
title?: string | undefined;
|
|
411
411
|
createdAt?: unknown;
|
|
412
412
|
updatedAt?: unknown;
|
|
413
|
-
order?: number | undefined;
|
|
414
413
|
boardId?: string | undefined;
|
|
414
|
+
order?: number | undefined;
|
|
415
415
|
}>;
|
|
416
416
|
}, Column>;
|
|
417
417
|
readonly deleteColumn: better_call.StrictEndpoint<"/columns/:id", {} & {
|
|
@@ -28,8 +28,8 @@ declare const createColumnSchema: z.ZodObject<{
|
|
|
28
28
|
title: z.ZodString;
|
|
29
29
|
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
30
30
|
updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
31
|
-
order: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
32
31
|
boardId: z.ZodString;
|
|
32
|
+
order: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
33
33
|
}, z.core.$strip>;
|
|
34
34
|
declare const updateColumnSchema: z.ZodObject<{
|
|
35
35
|
createdAt: z.ZodOptional<z.ZodOptional<z.ZodCoercedDate<unknown>>>;
|
|
@@ -404,14 +404,14 @@ declare const kanbanBackendPlugin: (hooks?: KanbanBackendHooks) => _btst_stack_p
|
|
|
404
404
|
title?: string | undefined;
|
|
405
405
|
createdAt?: unknown;
|
|
406
406
|
updatedAt?: unknown;
|
|
407
|
-
order?: number | undefined;
|
|
408
407
|
boardId?: string | undefined;
|
|
408
|
+
order?: number | undefined;
|
|
409
409
|
}, {
|
|
410
410
|
title?: string | undefined;
|
|
411
411
|
createdAt?: unknown;
|
|
412
412
|
updatedAt?: unknown;
|
|
413
|
-
order?: number | undefined;
|
|
414
413
|
boardId?: string | undefined;
|
|
414
|
+
order?: number | undefined;
|
|
415
415
|
}>;
|
|
416
416
|
}, Column>;
|
|
417
417
|
readonly deleteColumn: better_call.StrictEndpoint<"/columns/:id", {} & {
|
|
@@ -28,8 +28,8 @@ declare const createColumnSchema: z.ZodObject<{
|
|
|
28
28
|
title: z.ZodString;
|
|
29
29
|
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
30
30
|
updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
31
|
-
order: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
32
31
|
boardId: z.ZodString;
|
|
32
|
+
order: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
33
33
|
}, z.core.$strip>;
|
|
34
34
|
declare const updateColumnSchema: z.ZodObject<{
|
|
35
35
|
createdAt: z.ZodOptional<z.ZodOptional<z.ZodCoercedDate<unknown>>>;
|
|
@@ -404,14 +404,14 @@ declare const kanbanBackendPlugin: (hooks?: KanbanBackendHooks) => _btst_stack_p
|
|
|
404
404
|
title?: string | undefined;
|
|
405
405
|
createdAt?: unknown;
|
|
406
406
|
updatedAt?: unknown;
|
|
407
|
-
order?: number | undefined;
|
|
408
407
|
boardId?: string | undefined;
|
|
408
|
+
order?: number | undefined;
|
|
409
409
|
}, {
|
|
410
410
|
title?: string | undefined;
|
|
411
411
|
createdAt?: unknown;
|
|
412
412
|
updatedAt?: unknown;
|
|
413
|
-
order?: number | undefined;
|
|
414
413
|
boardId?: string | undefined;
|
|
414
|
+
order?: number | undefined;
|
|
415
415
|
}>;
|
|
416
416
|
}, Column>;
|
|
417
417
|
readonly deleteColumn: better_call.StrictEndpoint<"/columns/:id", {} & {
|