@blocklet/discuss-kit-ux-lite 2.6.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.
Files changed (186) hide show
  1. package/LICENSE +13 -0
  2. package/dist/axios.d.ts +8 -0
  3. package/dist/blocklets.d.ts +13 -0
  4. package/dist/blocklets.mjs +17 -0
  5. package/dist/components/api-error-handler/api-error-handler.d.ts +13 -0
  6. package/dist/components/api-error-handler/api-error-handler.mjs +25 -0
  7. package/dist/components/api-error-handler/default-handler.d.ts +8 -0
  8. package/dist/components/api-error-handler/default-handler.mjs +69 -0
  9. package/dist/components/api-error-handler/index.d.ts +2 -0
  10. package/dist/components/api-error-handler/index.mjs +2 -0
  11. package/dist/components/api-error-handler/json-validation-interceptor.d.ts +2 -0
  12. package/dist/components/api-error-handler/json-validation-interceptor.mjs +9 -0
  13. package/dist/components/arcsphere/index.d.ts +16 -0
  14. package/dist/components/arcsphere/index.mjs +52 -0
  15. package/dist/components/authz/access-control.d.ts +24 -0
  16. package/dist/components/authz/access-control.mjs +15 -0
  17. package/dist/components/authz/context.d.ts +13 -0
  18. package/dist/components/authz/context.mjs +30 -0
  19. package/dist/components/authz/index.d.ts +2 -0
  20. package/dist/components/authz/index.mjs +2 -0
  21. package/dist/components/auto-translate/api.d.ts +14 -0
  22. package/dist/components/auto-translate/api.mjs +23 -0
  23. package/dist/components/auto-translate/auto-translate-button.d.ts +5 -0
  24. package/dist/components/auto-translate/auto-translate-button.mjs +48 -0
  25. package/dist/components/auto-translate/editor-store-adaptor.d.ts +1 -0
  26. package/dist/components/auto-translate/editor-store-adaptor.mjs +14 -0
  27. package/dist/components/auto-translate/index.d.ts +6 -0
  28. package/dist/components/auto-translate/index.mjs +6 -0
  29. package/dist/components/auto-translate/languages.d.ts +13 -0
  30. package/dist/components/auto-translate/languages.mjs +61 -0
  31. package/dist/components/auto-translate/post-auto-translate-plugin.d.ts +14 -0
  32. package/dist/components/auto-translate/post-auto-translate-plugin.mjs +32 -0
  33. package/dist/components/auto-translate/store.d.ts +21 -0
  34. package/dist/components/auto-translate/store.mjs +23 -0
  35. package/dist/components/auto-translate/translate.d.ts +17 -0
  36. package/dist/components/auto-translate/translate.mjs +103 -0
  37. package/dist/components/auto-translate/utils.d.ts +4 -0
  38. package/dist/components/auto-translate/utils.mjs +14 -0
  39. package/dist/components/auto-translate/with-availibility-check.d.ts +2 -0
  40. package/dist/components/auto-translate/with-availibility-check.mjs +10 -0
  41. package/dist/components/avatars/author-info.d.ts +23 -0
  42. package/dist/components/avatars/author-info.mjs +178 -0
  43. package/dist/components/avatars/avatar.d.ts +7 -0
  44. package/dist/components/avatars/avatar.mjs +9 -0
  45. package/dist/components/avatars/avatars.d.ts +10 -0
  46. package/dist/components/avatars/avatars.mjs +49 -0
  47. package/dist/components/avatars/badge.d.ts +14 -0
  48. package/dist/components/avatars/badge.mjs +178 -0
  49. package/dist/components/avatars/index.d.ts +5 -0
  50. package/dist/components/avatars/index.mjs +5 -0
  51. package/dist/components/avatars/system-user.d.ts +10 -0
  52. package/dist/components/avatars/system-user.mjs +58 -0
  53. package/dist/components/button-group/button-group.d.ts +18 -0
  54. package/dist/components/button-group/button-group.mjs +195 -0
  55. package/dist/components/button-group/index.d.ts +1 -0
  56. package/dist/components/button-group/index.mjs +1 -0
  57. package/dist/components/confirm.d.ts +29 -0
  58. package/dist/components/confirm.mjs +103 -0
  59. package/dist/components/dayjs.d.ts +3 -0
  60. package/dist/components/dayjs.mjs +5 -0
  61. package/dist/components/default-editor-config-provider.d.ts +8 -0
  62. package/dist/components/default-editor-config-provider.mjs +108 -0
  63. package/dist/components/dirty-prompt.d.ts +8 -0
  64. package/dist/components/dirty-prompt.mjs +117 -0
  65. package/dist/components/editor/blocklet-editor.d.ts +1 -0
  66. package/dist/components/editor/blocklet-editor.mjs +1 -0
  67. package/dist/components/editor/editor.d.ts +17 -0
  68. package/dist/components/editor/editor.mjs +35 -0
  69. package/dist/components/editor/index.d.ts +4 -0
  70. package/dist/components/editor/index.mjs +3 -0
  71. package/dist/components/editor/lazy-editor.d.ts +3 -0
  72. package/dist/components/editor/lazy-editor.mjs +14 -0
  73. package/dist/components/editor/viewer.d.ts +10 -0
  74. package/dist/components/editor/viewer.mjs +18 -0
  75. package/dist/components/emoji-icon.d.ts +11 -0
  76. package/dist/components/emoji-icon.mjs +62 -0
  77. package/dist/components/empty-status/empty-status.d.ts +7 -0
  78. package/dist/components/empty-status/empty-status.mjs +24 -0
  79. package/dist/components/empty-status/index.d.ts +1 -0
  80. package/dist/components/empty-status/index.mjs +1 -0
  81. package/dist/components/hooks/changed.d.ts +10 -0
  82. package/dist/components/hooks/changed.mjs +34 -0
  83. package/dist/components/hooks/index.d.ts +6 -0
  84. package/dist/components/hooks/index.mjs +6 -0
  85. package/dist/components/hooks/interval.d.ts +6 -0
  86. package/dist/components/hooks/interval.mjs +16 -0
  87. package/dist/components/hooks/locale-context.d.ts +10 -0
  88. package/dist/components/hooks/locale-context.mjs +12 -0
  89. package/dist/components/hooks/measure.d.ts +5 -0
  90. package/dist/components/hooks/measure.mjs +8 -0
  91. package/dist/components/hooks/now.d.ts +1 -0
  92. package/dist/components/hooks/now.mjs +10 -0
  93. package/dist/components/hooks/responsive.d.ts +17 -0
  94. package/dist/components/hooks/responsive.mjs +25 -0
  95. package/dist/components/hooks/session.d.ts +12 -0
  96. package/dist/components/hooks/session.mjs +48 -0
  97. package/dist/components/hooks/use-event-callback.d.ts +1 -0
  98. package/dist/components/hooks/use-event-callback.mjs +14 -0
  99. package/dist/components/icon-button.d.ts +6 -0
  100. package/dist/components/icon-button.mjs +37 -0
  101. package/dist/components/input/auto-clear-plugin.d.ts +3 -0
  102. package/dist/components/input/auto-clear-plugin.mjs +20 -0
  103. package/dist/components/input/comment-input.d.ts +11 -0
  104. package/dist/components/input/comment-input.mjs +108 -0
  105. package/dist/components/input/index.d.ts +4 -0
  106. package/dist/components/input/index.mjs +4 -0
  107. package/dist/components/input/input.d.ts +29 -0
  108. package/dist/components/input/input.mjs +149 -0
  109. package/dist/components/input/post-edit.d.ts +10 -0
  110. package/dist/components/input/post-edit.mjs +49 -0
  111. package/dist/components/input/scrollable-editor-wrapper.d.ts +9 -0
  112. package/dist/components/input/scrollable-editor-wrapper.mjs +18 -0
  113. package/dist/components/input/shortcut-plugin.d.ts +7 -0
  114. package/dist/components/input/shortcut-plugin.mjs +28 -0
  115. package/dist/components/lexical.d.ts +10 -0
  116. package/dist/components/lexical.mjs +56 -0
  117. package/dist/components/locale/en.d.ts +117 -0
  118. package/dist/components/locale/en.mjs +116 -0
  119. package/dist/components/locale/index.d.ts +236 -0
  120. package/dist/components/locale/index.mjs +3 -0
  121. package/dist/components/locale/zh.d.ts +119 -0
  122. package/dist/components/locale/zh.mjs +118 -0
  123. package/dist/components/pagination.d.ts +12 -0
  124. package/dist/components/pagination.mjs +44 -0
  125. package/dist/components/posts/comment-list/comment-list.d.ts +5 -0
  126. package/dist/components/posts/comment-list/comment-list.mjs +163 -0
  127. package/dist/components/posts/comment-list/context.d.ts +76 -0
  128. package/dist/components/posts/comment-list/context.mjs +318 -0
  129. package/dist/components/posts/comment.d.ts +16 -0
  130. package/dist/components/posts/comment.mjs +184 -0
  131. package/dist/components/posts/index.d.ts +6 -0
  132. package/dist/components/posts/index.mjs +6 -0
  133. package/dist/components/posts/menu.d.ts +14 -0
  134. package/dist/components/posts/menu.mjs +83 -0
  135. package/dist/components/posts/post-content.d.ts +16 -0
  136. package/dist/components/posts/post-content.mjs +63 -0
  137. package/dist/components/posts/post.d.ts +26 -0
  138. package/dist/components/posts/post.mjs +198 -0
  139. package/dist/components/profile-card/index.d.ts +1 -0
  140. package/dist/components/profile-card/index.mjs +1 -0
  141. package/dist/components/profile-card/profile-card.d.ts +15 -0
  142. package/dist/components/profile-card/profile-card.mjs +140 -0
  143. package/dist/components/rating/binary-thumb.d.ts +9 -0
  144. package/dist/components/rating/binary-thumb.mjs +162 -0
  145. package/dist/components/rating/github-reaction-container.d.ts +9 -0
  146. package/dist/components/rating/github-reaction-container.mjs +46 -0
  147. package/dist/components/rating/github-reaction.d.ts +18 -0
  148. package/dist/components/rating/github-reaction.mjs +174 -0
  149. package/dist/components/rating/index.d.ts +3 -0
  150. package/dist/components/rating/index.mjs +3 -0
  151. package/dist/components/rating/rater-list.d.ts +10 -0
  152. package/dist/components/rating/rater-list.mjs +33 -0
  153. package/dist/components/rating/rating.d.ts +27 -0
  154. package/dist/components/rating/rating.mjs +50 -0
  155. package/dist/components/segmented-control.d.ts +14 -0
  156. package/dist/components/segmented-control.mjs +55 -0
  157. package/dist/components/shared/relative-time.d.ts +8 -0
  158. package/dist/components/shared/relative-time.mjs +21 -0
  159. package/dist/components/toast.d.ts +8 -0
  160. package/dist/components/toast.mjs +44 -0
  161. package/dist/components/uploader/index.d.ts +13 -0
  162. package/dist/components/uploader/index.mjs +70 -0
  163. package/dist/components/uploader/utils.d.ts +1 -0
  164. package/dist/components/uploader/utils.mjs +16 -0
  165. package/dist/components/utils.d.ts +22 -0
  166. package/dist/components/utils.mjs +103 -0
  167. package/dist/components/view-more.d.ts +4 -0
  168. package/dist/components/view-more.mjs +50 -0
  169. package/dist/constants.d.ts +5 -0
  170. package/dist/constants.mjs +14 -0
  171. package/dist/global.d.ts +1 -0
  172. package/dist/index.d.ts +29 -0
  173. package/dist/index.mjs +29 -0
  174. package/dist/preferences.d.ts +2 -0
  175. package/dist/preferences.mjs +9 -0
  176. package/dist/theme/index.d.ts +8 -0
  177. package/dist/theme/index.mjs +96 -0
  178. package/dist/theme/typography.d.ts +2 -0
  179. package/dist/theme/typography.mjs +66 -0
  180. package/dist/type-override.d.ts +7 -0
  181. package/dist/types.d.ts +84 -0
  182. package/dist/types.mjs +0 -0
  183. package/dist/vite-env.d.ts +1 -0
  184. package/dist/ws.d.ts +3 -0
  185. package/dist/ws.mjs +39 -0
  186. package/package.json +81 -0
@@ -0,0 +1,163 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment } from "react";
3
+ import Box from "@mui/material/Box";
4
+ import Button from "@mui/material/Button";
5
+ import { NavigateNext as NavigateNextIcon } from "@mui/icons-material";
6
+ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
7
+ import { styled } from "@mui/material/styles";
8
+ import { useCommentsContext } from "./context.mjs";
9
+ import Comment from "../comment.mjs";
10
+ const LoadMoreButtonWrapper = styled(Box)`
11
+ background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 11L8 5L16 11' stroke='%23e1e4e8' stroke-linecap='square' stroke-width='1.5' /%3E%3C/svg%3E");
12
+ background-repeat: repeat-x;
13
+ background-position: center;
14
+ `;
15
+ function SegmentalList({ ...rest }) {
16
+ const { t } = useLocaleContext();
17
+ const {
18
+ state: { comments, nextCursor, highlighted },
19
+ autoCollapse,
20
+ allowCopyLink,
21
+ showProfileCard,
22
+ interactive,
23
+ loadMore,
24
+ loadMoreForTailSection,
25
+ updateComment,
26
+ deleteComment,
27
+ reply,
28
+ togglePin,
29
+ loadMoreReplies,
30
+ api
31
+ } = useCommentsContext();
32
+ if (!highlighted) {
33
+ return null;
34
+ }
35
+ const headIndex = comments.findIndex((item) => item.id === highlighted.head);
36
+ const hiddenComments = highlighted.position - comments.findIndex((item) => item.id === highlighted.id) + headIndex;
37
+ return /* @__PURE__ */ jsxs("div", { ...rest, className: "comment-list", children: [
38
+ comments.map((comment) => {
39
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
40
+ comment.id === highlighted?.id && hiddenComments > 0 && nextCursor && /* @__PURE__ */ jsx(LoadMoreButtonWrapper, { sx: { my: 4, textAlign: "center" }, children: /* @__PURE__ */ jsxs(
41
+ Box,
42
+ {
43
+ sx: {
44
+ display: "inline-flex",
45
+ flexDirection: "column",
46
+ justifyContent: "center",
47
+ alignItems: "center",
48
+ px: 4,
49
+ pt: 1,
50
+ pb: 2,
51
+ border: 1,
52
+ borderColor: "grey.300",
53
+ borderRadius: 1,
54
+ bgcolor: "#fff"
55
+ },
56
+ children: [
57
+ /* @__PURE__ */ jsx(Button, { onClick: () => loadMore(nextCursor), variant: "text", color: "primary", sx: { fontSize: 13 }, children: t("loadMore") }),
58
+ /* @__PURE__ */ jsxs(Box, { sx: { fontSize: 12, color: "grey.500" }, children: [
59
+ hiddenComments,
60
+ " Comments"
61
+ ] })
62
+ ]
63
+ }
64
+ ) }),
65
+ /* @__PURE__ */ jsx(
66
+ Comment,
67
+ {
68
+ post: comment,
69
+ onDelete: deleteComment,
70
+ onContentUpdate: updateComment,
71
+ onRate: api.rate,
72
+ onUnrate: api.unrate,
73
+ onReply: reply,
74
+ onTogglePin: togglePin,
75
+ onLoadMoreReplies: loadMoreReplies,
76
+ autoCollapse,
77
+ allowCopyLink,
78
+ showProfileCard,
79
+ interactive
80
+ }
81
+ )
82
+ ] }, comment.id);
83
+ }),
84
+ highlighted.nextCursor && /* @__PURE__ */ jsx(Box, { sx: { my: 2, textAlign: "center" }, children: /* @__PURE__ */ jsx(
85
+ Button,
86
+ {
87
+ onClick: loadMoreForTailSection,
88
+ variant: "outlined",
89
+ color: "primary",
90
+ endIcon: /* @__PURE__ */ jsx(NavigateNextIcon, {}),
91
+ sx: { fontSize: 13 },
92
+ children: t("loadMore")
93
+ }
94
+ ) })
95
+ ] });
96
+ }
97
+ export default function CommentList(props) {
98
+ const { t } = useLocaleContext();
99
+ const {
100
+ state: { order, nextCursor, highlighted },
101
+ comments,
102
+ pinned,
103
+ autoCollapse,
104
+ allowCopyLink,
105
+ showProfileCard,
106
+ interactive,
107
+ loadMore,
108
+ updateComment,
109
+ deleteComment,
110
+ reply,
111
+ togglePin,
112
+ loadMoreReplies,
113
+ api,
114
+ renderComments,
115
+ renderDonation,
116
+ renderActions,
117
+ renderEditorPlugins,
118
+ enableAutoTranslate
119
+ } = useCommentsContext();
120
+ if (highlighted) {
121
+ return /* @__PURE__ */ jsx(SegmentalList, { ...props });
122
+ }
123
+ const renderComment = (comment, restProps) => /* @__PURE__ */ jsx(
124
+ Comment,
125
+ {
126
+ post: comment,
127
+ onDelete: deleteComment,
128
+ onContentUpdate: updateComment,
129
+ onRate: api.rate,
130
+ onUnrate: api.unrate,
131
+ onReply: reply,
132
+ onTogglePin: togglePin,
133
+ onLoadMoreReplies: loadMoreReplies,
134
+ autoCollapse,
135
+ allowCopyLink,
136
+ showProfileCard,
137
+ interactive,
138
+ renderDonation,
139
+ renderActions,
140
+ renderEditorPlugins,
141
+ enableAutoTranslate,
142
+ ...restProps
143
+ },
144
+ comment.id
145
+ );
146
+ const sortedPinned = pinned.sort((a, b) => (b.pinnedAt?.getTime() ?? 0) - (a.pinnedAt?.getTime() ?? 0));
147
+ return /* @__PURE__ */ jsxs("div", { ...props, children: [
148
+ /* @__PURE__ */ jsx(Box, { children: sortedPinned.map((post) => renderComment(post, {})) }),
149
+ renderComments ? renderComments({ order, comments, renderComment }) : comments?.map(renderComment),
150
+ nextCursor && /* @__PURE__ */ jsx(Box, { sx: { my: 2, textAlign: "center" }, children: /* @__PURE__ */ jsx(
151
+ Button,
152
+ {
153
+ className: "load-more",
154
+ onClick: () => loadMore(nextCursor),
155
+ variant: "outlined",
156
+ color: "primary",
157
+ endIcon: /* @__PURE__ */ jsx(NavigateNextIcon, {}),
158
+ sx: { fontSize: 13 },
159
+ children: t("loadMore")
160
+ }
161
+ ) })
162
+ ] });
163
+ }
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+ import type { Post, Target, CommentAPI } from '../../../types';
3
+ type Order = 'asc' | 'desc';
4
+ export type CommentsRender = (value: {
5
+ comments: Post[];
6
+ renderComment: (comment: Post, restProps: any) => React.ReactNode;
7
+ order: Order;
8
+ }) => React.ReactNode;
9
+ interface CommentsProviderProps {
10
+ target: Target;
11
+ api: CommentAPI;
12
+ flatView?: boolean;
13
+ children: React.ReactNode;
14
+ order?: Order;
15
+ autoCollapse?: boolean;
16
+ autoLoadComments?: boolean;
17
+ allowCopyLink?: boolean;
18
+ showProfileCard?: boolean;
19
+ interactive?: boolean;
20
+ containerRef: React.RefObject<HTMLDivElement | null>;
21
+ renderComments?: CommentsRender;
22
+ renderDonation?: (post: Post) => React.ReactNode;
23
+ renderActions?: (post: Post) => React.ReactNode;
24
+ renderEditorPlugins?: (post: Post) => React.ReactNode;
25
+ enableAutoTranslate?: boolean;
26
+ renderInnerFooter?: (post: Post) => React.ReactElement<any>;
27
+ }
28
+ interface State {
29
+ comments: Post[];
30
+ nextCursor: string | null;
31
+ total: number;
32
+ replyCursors: {
33
+ [key: string]: string | undefined | null;
34
+ };
35
+ highlighted: {
36
+ id: string;
37
+ position: number;
38
+ nextCursor: string | null;
39
+ head: string;
40
+ } | null;
41
+ initialized: boolean;
42
+ loading: boolean;
43
+ order: Order;
44
+ }
45
+ interface CommentsContextValue {
46
+ target: Target;
47
+ api: CommentAPI;
48
+ state: State;
49
+ comments: Post[];
50
+ pinned: Post[];
51
+ flatView?: boolean;
52
+ autoCollapse?: boolean;
53
+ allowCopyLink?: boolean;
54
+ showProfileCard?: boolean;
55
+ interactive?: boolean;
56
+ updateCommentState: (id: string, mapper: (post: Post) => Post) => void;
57
+ loadMore: (cursor: string) => void;
58
+ loadMoreForTailSection: () => void;
59
+ loadMoreReplies: (rootId: string) => void;
60
+ sort: (order: Order) => void;
61
+ updateComment: CommentAPI['updateComment'];
62
+ deleteComment: CommentAPI['deleteComment'];
63
+ reply: CommentAPI['reply'];
64
+ togglePin: CommentAPI['togglePin'];
65
+ findById: (id: string) => Post | undefined;
66
+ renderComments?: CommentsRender;
67
+ renderDonation?: (post: Post) => React.ReactNode;
68
+ renderActions?: (post: Post) => React.ReactNode;
69
+ renderEditorPlugins?: (post: Post) => React.ReactNode;
70
+ enableAutoTranslate?: boolean;
71
+ renderInnerFooter?: (post: Post) => React.ReactElement<any>;
72
+ }
73
+ export declare const CommentsContext: import("react").Context<CommentsContextValue>;
74
+ export declare const useCommentsContext: () => CommentsContextValue;
75
+ export declare function CommentsProvider({ target, api, flatView, children, order, autoLoadComments, autoCollapse, allowCopyLink, showProfileCard, interactive, containerRef, renderComments, renderDonation, renderActions, renderEditorPlugins, enableAutoTranslate, renderInnerFooter, }: CommentsProviderProps): JSX.Element;
76
+ export {};
@@ -0,0 +1,318 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, createContext, useContext, useMemo } from "react";
3
+ import { useSetState, useSize } from "ahooks";
4
+ import uniqBy from "lodash/uniqBy";
5
+ import orderBy from "lodash/orderBy";
6
+ import { getLastItem } from "../../utils.mjs";
7
+ const KEY_LS_ORDER = "comment-list-order";
8
+ const getInitialState = (overrides) => ({
9
+ comments: [],
10
+ nextCursor: null,
11
+ total: 0,
12
+ replyCursors: {},
13
+ highlighted: null,
14
+ initialized: false,
15
+ loading: true,
16
+ order: localStorage.getItem(KEY_LS_ORDER) === "asc" ? "asc" : "desc",
17
+ ...overrides
18
+ });
19
+ const uniqAndSort = (list, order) => {
20
+ const unique = uniqBy(list, "id");
21
+ const sorted = orderBy(unique, ["createdAt", "id"], [order, order]);
22
+ return sorted;
23
+ };
24
+ const getHighlightedId = () => {
25
+ const id = window.location.hash.substring(1);
26
+ return /^[\w-]+$/.test(id) ? id : "";
27
+ };
28
+ const useAutoScroll = (data, containerHeight) => {
29
+ const highlightedRef = useRef(getHighlightedId());
30
+ const lastHeightRef = useRef(containerHeight);
31
+ const autoScrolledRef = useRef(false);
32
+ const matchTargetedPost = (posts) => {
33
+ return posts.some((item) => {
34
+ if (item.id === highlightedRef.current) {
35
+ return true;
36
+ }
37
+ if (item.replies?.length) {
38
+ return matchTargetedPost(item.replies);
39
+ }
40
+ return false;
41
+ });
42
+ };
43
+ const stableHeight = () => {
44
+ if (lastHeightRef.current === containerHeight) {
45
+ return true;
46
+ }
47
+ lastHeightRef.current = containerHeight;
48
+ return false;
49
+ };
50
+ useEffect(() => {
51
+ let interval;
52
+ if (containerHeight && !autoScrolledRef.current) {
53
+ interval = window.setInterval(() => {
54
+ if (stableHeight()) {
55
+ clearInterval(interval);
56
+ if (highlightedRef.current && matchTargetedPost(data)) {
57
+ const element = document.getElementById(highlightedRef.current);
58
+ element?.scrollIntoView({ behavior: "smooth", block: "center" });
59
+ autoScrolledRef.current = true;
60
+ }
61
+ }
62
+ }, 200);
63
+ }
64
+ return () => {
65
+ if (interval) {
66
+ clearInterval(interval);
67
+ }
68
+ };
69
+ }, [containerHeight]);
70
+ };
71
+ export const CommentsContext = createContext({});
72
+ export const useCommentsContext = () => useContext(CommentsContext);
73
+ export function CommentsProvider({
74
+ target,
75
+ api,
76
+ flatView,
77
+ children,
78
+ order,
79
+ autoLoadComments = true,
80
+ autoCollapse,
81
+ allowCopyLink,
82
+ showProfileCard,
83
+ interactive,
84
+ containerRef,
85
+ renderComments,
86
+ renderDonation,
87
+ renderActions,
88
+ renderEditorPlugins,
89
+ enableAutoTranslate,
90
+ renderInnerFooter
91
+ }) {
92
+ const [state, setState] = useSetState(getInitialState(order ? { order } : {}));
93
+ const comments = useMemo(
94
+ () => uniqAndSort(
95
+ state.comments.filter((item) => !item.pinnedAt),
96
+ state.order
97
+ ),
98
+ [state.comments, state.order]
99
+ );
100
+ const pinned = useMemo(() => state.comments.filter((item) => item.pinnedAt), [state.comments]);
101
+ const containerHeight = useSize(containerRef)?.height;
102
+ const commentsKeyById = useMemo(() => {
103
+ return state.comments.reduce((acc, cur) => {
104
+ acc[cur.id] = cur;
105
+ if (cur.replies?.length) {
106
+ cur.replies.forEach((item) => {
107
+ acc[item.id] = item;
108
+ });
109
+ }
110
+ return acc;
111
+ }, {});
112
+ }, [state]);
113
+ const highlightedRef = useRef(getHighlightedId());
114
+ const limit = 15;
115
+ const initialRepliesLimit = -1;
116
+ useEffect(() => {
117
+ const init = async () => {
118
+ if (!autoLoadComments) {
119
+ setState({ initialized: true });
120
+ return;
121
+ }
122
+ setState({ ...getInitialState(), order: state.order, initialized: false });
123
+ const fetchCommentPosition = () => api.fetchCommentPosition({
124
+ id: highlightedRef.current,
125
+ objectId: target.id,
126
+ order: state.order,
127
+ includeReplies: flatView ? 1 : null
128
+ });
129
+ const [{ data, total, nextCursor }, positionRes, pinnedComments] = await Promise.all([
130
+ api.fetchComments({
131
+ objectId: target.id,
132
+ limit,
133
+ order: state.order,
134
+ includeReplies: flatView ? 1 : null,
135
+ initialRepliesLimit
136
+ }),
137
+ highlightedRef.current ? fetchCommentPosition() : -1,
138
+ api.fetchPinnedComments(target.id)
139
+ ]);
140
+ let position = -1;
141
+ let cursor = highlightedRef.current;
142
+ if (typeof positionRes !== "number") {
143
+ position = positionRes.position;
144
+ cursor = positionRes.rootId;
145
+ } else {
146
+ position = positionRes;
147
+ }
148
+ if (position > data.length - 1) {
149
+ const result = await api.fetchComments({
150
+ objectId: target.id,
151
+ limit,
152
+ order: state.order,
153
+ cursor,
154
+ skip: false,
155
+ includeReplies: flatView ? 1 : null,
156
+ initialRepliesLimit
157
+ });
158
+ setState({
159
+ comments: [...data, ...result.data, ...pinnedComments],
160
+ nextCursor,
161
+ total,
162
+ initialized: true,
163
+ // 记录 head (初始数据首元素 id)
164
+ highlighted: {
165
+ id: cursor,
166
+ position,
167
+ nextCursor: result.nextCursor,
168
+ head: data[0].id
169
+ }
170
+ });
171
+ } else {
172
+ setState({
173
+ comments: [...data, ...pinnedComments],
174
+ nextCursor,
175
+ total,
176
+ initialized: true
177
+ });
178
+ }
179
+ };
180
+ init();
181
+ }, [target.id, state.order, setState]);
182
+ useAutoScroll(state.comments, containerHeight);
183
+ const updateCommentState = (id, mapper) => {
184
+ const comment = commentsKeyById[id];
185
+ const root = commentsKeyById[comment.rootId];
186
+ if (root?.replies?.length) {
187
+ root.replies = root.replies.map((item) => item.id === id ? mapper(item) : item);
188
+ updateCommentState(root.id, (current) => ({ ...current }));
189
+ } else {
190
+ const mapped = state.comments.map((item) => item.id === id ? mapper(item) : item);
191
+ setState({ comments: mapped });
192
+ }
193
+ };
194
+ const loadMore = async (cursor) => {
195
+ const { data, total, nextCursor } = await api.fetchComments({
196
+ objectId: target.id,
197
+ limit,
198
+ order: state.order,
199
+ cursor,
200
+ includeReplies: flatView ? 1 : null,
201
+ initialRepliesLimit
202
+ });
203
+ const sorted = uniqAndSort([...state.comments, ...data], state.order);
204
+ setState({ comments: sorted, total, nextCursor });
205
+ };
206
+ const loadMoreForTailSection = async () => {
207
+ if (!state.highlighted) {
208
+ return;
209
+ }
210
+ const { data, total, nextCursor } = await api.fetchComments({
211
+ objectId: target.id,
212
+ limit,
213
+ order: state.order,
214
+ cursor: state.highlighted.nextCursor,
215
+ includeReplies: flatView ? 1 : null,
216
+ initialRepliesLimit
217
+ });
218
+ const sorted = uniqAndSort([...state.comments, ...data], state.order);
219
+ setState({ comments: sorted, total, highlighted: { ...state.highlighted, nextCursor } });
220
+ };
221
+ const loadMoreReplies = async (rootId) => {
222
+ const root = commentsKeyById[rootId];
223
+ if (root.replies) {
224
+ const cursor = state.replyCursors[root.id] || getLastItem(root.replies)?.id;
225
+ const { data, total, nextCursor } = await api.fetchComments({
226
+ objectId: target.id,
227
+ rootId,
228
+ limit,
229
+ order: "asc",
230
+ cursor
231
+ });
232
+ const sorted = uniqAndSort([...root.replies, ...data], "asc");
233
+ updateCommentState(rootId, (current) => ({
234
+ ...current,
235
+ replies: sorted,
236
+ totalReplies: total
237
+ }));
238
+ state.replyCursors[root.id] = nextCursor;
239
+ }
240
+ };
241
+ const sort = (order2) => {
242
+ setState({ order: order2 });
243
+ localStorage.setItem(KEY_LS_ORDER, order2);
244
+ };
245
+ const add = (post) => {
246
+ if (flatView || !post.rootId) {
247
+ setState({ comments: uniqAndSort([...state.comments, post], state.order) });
248
+ } else {
249
+ const root = commentsKeyById[post.rootId];
250
+ if (root) {
251
+ if (!state.replyCursors[root.id] && root.replies) {
252
+ state.replyCursors[root.id] = getLastItem(root.replies)?.id;
253
+ }
254
+ const sorted = uniqAndSort([...root.replies || [], post], "asc");
255
+ updateCommentState(post.rootId, (current) => ({
256
+ ...current,
257
+ replies: sorted,
258
+ totalReplies: (current.totalReplies || 0) + 1
259
+ }));
260
+ }
261
+ }
262
+ if (!commentsKeyById[post.id]) {
263
+ setState({ total: state.total + 1 });
264
+ }
265
+ };
266
+ const updateComment = async (post, content) => {
267
+ updateCommentState(post.id, (current) => ({ ...current, content }));
268
+ const { updatedAt } = await api.updateComment(post, content);
269
+ updateCommentState(post.id, (current) => ({ ...current, content, updatedAt }));
270
+ };
271
+ const deleteComment = async (post) => {
272
+ await api.deleteComment(post);
273
+ updateCommentState(post.id, (current) => ({ ...current, deletedAt: /* @__PURE__ */ new Date() }));
274
+ };
275
+ const reply = async (post, content) => {
276
+ const result = await api.reply(post, content);
277
+ setTimeout(() => add(result), 800);
278
+ };
279
+ const togglePin = async ({ post, value: value2 }) => {
280
+ await api.togglePin({ post, value: value2 });
281
+ updateCommentState(post.id, (current) => ({ ...current, pinnedAt: value2 ? /* @__PURE__ */ new Date() : null }));
282
+ };
283
+ const findById = (id) => commentsKeyById[id];
284
+ const value = useMemo(
285
+ () => ({
286
+ target,
287
+ state,
288
+ pinned,
289
+ comments,
290
+ flatView,
291
+ autoCollapse,
292
+ allowCopyLink,
293
+ showProfileCard,
294
+ interactive,
295
+ updateCommentState,
296
+ loadMore,
297
+ loadMoreForTailSection,
298
+ sort,
299
+ add,
300
+ api,
301
+ updateComment,
302
+ deleteComment,
303
+ reply,
304
+ togglePin,
305
+ loadMoreReplies,
306
+ findById,
307
+ renderComments,
308
+ renderDonation,
309
+ renderActions,
310
+ renderEditorPlugins,
311
+ enableAutoTranslate,
312
+ renderInnerFooter
313
+ }),
314
+ // eslint-disable-next-line react-hooks/exhaustive-deps
315
+ [target, state]
316
+ );
317
+ return /* @__PURE__ */ jsx(CommentsContext.Provider, { value, children });
318
+ }
@@ -0,0 +1,16 @@
1
+ /// <reference types="react" />
2
+ import type { CommentAPI, Post } from '../../types';
3
+ import { PostProps } from './post';
4
+ export interface CommentProps {
5
+ onDelete: CommentAPI['deleteComment'];
6
+ onRate: CommentAPI['rate'];
7
+ onUnrate: CommentAPI['unrate'];
8
+ onReply: CommentAPI['reply'];
9
+ onTogglePin: CommentAPI['togglePin'];
10
+ onLoadMoreReplies: (rootId: string) => void;
11
+ renderDonation?: (post: Post) => React.ReactNode;
12
+ renderActions?: (post: Post) => React.ReactNode;
13
+ renderEditorPlugins?: (post: Post) => React.ReactNode;
14
+ enableAutoTranslate?: boolean;
15
+ }
16
+ export default function Comment({ onDelete, onRate, onUnrate, onReply, onTogglePin, onContentUpdate, onLoadMoreReplies, renderDonation, renderActions, renderEditorPlugins, enableAutoTranslate, ...rest }: PostProps & CommentProps): import("react").JSX.Element;