elseware-ui 2.31.10 → 2.31.12

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/index.js CHANGED
@@ -17606,9 +17606,12 @@ function CommentContent({ comment }) {
17606
17606
  var CommentContent_default = CommentContent;
17607
17607
 
17608
17608
  // src/compositions/comment-thread/utils/formatCommentDate.ts
17609
- function formatCommentDate(date) {
17610
- const now = /* @__PURE__ */ new Date();
17609
+ function formatCommentDate(date, format = "relative") {
17611
17610
  const target = new Date(date);
17611
+ if (format === "absolute") {
17612
+ return target.toLocaleString();
17613
+ }
17614
+ const now = /* @__PURE__ */ new Date();
17612
17615
  const seconds = Math.floor((now.getTime() - target.getTime()) / 1e3);
17613
17616
  const intervals = [
17614
17617
  {
@@ -17640,13 +17643,13 @@ function formatCommentDate(date) {
17640
17643
  }
17641
17644
  return "Just now";
17642
17645
  }
17643
- function CommentHeader({ comment }) {
17646
+ function CommentHeader({ comment, config }) {
17644
17647
  const author = comment.author;
17645
17648
  const displayName = author?.firstName || author?.lastName ? `${author?.firstName ?? ""} ${author?.lastName ?? ""}`.trim() : author?.username ?? "Unknown";
17646
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
17649
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [
17647
17650
  /* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: author?.avatar, size: "sm" }),
17648
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-wrap min-w-0", children: [
17649
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-sm text-gray-900 dark:text-gray-100", children: displayName }),
17651
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
17652
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: displayName }),
17650
17653
  author?.username && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17651
17654
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
17652
17655
  "@",
@@ -17654,7 +17657,7 @@ function CommentHeader({ comment }) {
17654
17657
  ] }),
17655
17658
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "\xB7" })
17656
17659
  ] }),
17657
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: formatCommentDate(comment.createdAt) }),
17660
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: formatCommentDate(comment.createdAt, config.dateFormat) }),
17658
17661
  comment.edited && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500", children: "(edited)" })
17659
17662
  ] })
17660
17663
  ] });
@@ -17747,20 +17750,29 @@ var CommentMenu_default = CommentMenu;
17747
17750
  function CommentRepliesToggle({
17748
17751
  repliesCount,
17749
17752
  collapsed,
17753
+ loading = false,
17750
17754
  onToggle
17751
17755
  }) {
17752
17756
  if (repliesCount <= 0) {
17753
17757
  return null;
17754
17758
  }
17755
- const label = collapsed ? `View ${repliesCount} ${repliesCount === 1 ? "reply" : "replies"}` : "Hide replies";
17759
+ let label = "";
17760
+ if (loading) {
17761
+ label = "Loading replies...";
17762
+ } else if (collapsed) {
17763
+ label = `View ${repliesCount} ${repliesCount === 1 ? "reply" : "replies"}`;
17764
+ } else {
17765
+ label = "Hide replies";
17766
+ }
17756
17767
  return /* @__PURE__ */ jsxRuntime.jsxs(
17757
17768
  "button",
17758
17769
  {
17759
17770
  type: "button",
17771
+ disabled: loading,
17760
17772
  onClick: onToggle,
17761
- className: "inline-flex w-fit items-center gap-1 text-sm font-medium text-blue-500 transition-colors hover:text-blue-400",
17773
+ className: "inline-flex w-fit items-center gap-1 text-sm font-medium text-blue-500 transition-colors hover:text-blue-400 disabled:cursor-not-allowed disabled:opacity-60",
17762
17774
  children: [
17763
- collapsed ? /* @__PURE__ */ jsxRuntime.jsx(bi.BiChevronDown, { size: 18 }) : /* @__PURE__ */ jsxRuntime.jsx(bi.BiChevronUp, { size: 18 }),
17775
+ !loading && (collapsed ? /* @__PURE__ */ jsxRuntime.jsx(bi.BiChevronDown, { size: 18 }) : /* @__PURE__ */ jsxRuntime.jsx(bi.BiChevronUp, { size: 18 })),
17764
17776
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
17765
17777
  ]
17766
17778
  }
@@ -17791,18 +17803,24 @@ function ReplyComposer({
17791
17803
  var ReplyComposer_default = ReplyComposer;
17792
17804
  function CommentItem({
17793
17805
  comment,
17806
+ replies = [],
17807
+ allReplies = {},
17794
17808
  depth = 0,
17795
17809
  config,
17796
17810
  components,
17797
17811
  onLike,
17798
17812
  onDislike,
17799
17813
  onReply,
17814
+ onLoadReplies,
17800
17815
  onEdit,
17801
17816
  onDelete,
17802
17817
  onReport
17803
17818
  }) {
17804
- const [collapsed, setCollapsed] = React4.useState(false);
17819
+ const [collapsed, setCollapsed] = React4.useState(true);
17805
17820
  const [isReplying, setIsReplying] = React4.useState(false);
17821
+ const [menuOpen, setMenuOpen] = React4.useState(false);
17822
+ const [loadingReplies, setLoadingReplies] = React4.useState(false);
17823
+ const [repliesLoaded, setRepliesLoaded] = React4.useState(false);
17806
17824
  const resolvedDepth = Math.min(depth, config.maxDepth);
17807
17825
  const {
17808
17826
  Header: HeaderComponent = CommentHeader_default,
@@ -17815,100 +17833,108 @@ function CommentItem({
17815
17833
  async function handleReply(content) {
17816
17834
  await onReply?.(comment, content);
17817
17835
  setIsReplying(false);
17836
+ setCollapsed(false);
17837
+ setRepliesLoaded(true);
17818
17838
  }
17819
- const [menuOpen, setMenuOpen] = React4.useState(false);
17820
- return /* @__PURE__ */ jsxRuntime.jsxs(
17839
+ async function handleToggleReplies() {
17840
+ const nextCollapsed = !collapsed;
17841
+ if (!nextCollapsed && !repliesLoaded && onLoadReplies) {
17842
+ try {
17843
+ setLoadingReplies(true);
17844
+ await onLoadReplies(comment);
17845
+ setRepliesLoaded(true);
17846
+ } finally {
17847
+ setLoadingReplies(false);
17848
+ }
17849
+ }
17850
+ setCollapsed(nextCollapsed);
17851
+ }
17852
+ return /* @__PURE__ */ jsxRuntime.jsx(
17821
17853
  "div",
17822
17854
  {
17823
17855
  className: "flex flex-col gap-4",
17824
17856
  style: {
17825
17857
  paddingLeft: resolvedDepth * config.indentation
17826
17858
  },
17827
- children: [
17828
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-3 min-w-0", children: [
17829
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
17830
- /* @__PURE__ */ jsxRuntime.jsx(HeaderComponent, { comment }),
17831
- /* @__PURE__ */ jsxRuntime.jsx(
17832
- MenuComponent,
17833
- {
17834
- comment,
17835
- open: menuOpen,
17836
- setOpen: setMenuOpen,
17837
- canEdit: comment.permissions?.canEdit,
17838
- canDelete: comment.permissions?.canDelete,
17839
- canReport: comment.permissions?.canReport,
17840
- onEdit: () => onEdit?.(comment),
17841
- onDelete: () => onDelete?.(comment),
17842
- onReport: () => onReport?.(comment)
17843
- }
17844
- )
17845
- ] }),
17846
- /* @__PURE__ */ jsxRuntime.jsx(ContentComponent, { comment }),
17859
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-3", children: [
17860
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
17861
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderComponent, { comment, config }),
17847
17862
  /* @__PURE__ */ jsxRuntime.jsx(
17848
- ActionsComponent,
17863
+ MenuComponent,
17849
17864
  {
17850
17865
  comment,
17851
- onLike: () => onLike?.(comment),
17852
- onDislike: () => onDislike?.(comment),
17853
- onReply: () => setIsReplying((prev) => !prev)
17854
- }
17855
- ),
17856
- isReplying && /* @__PURE__ */ jsxRuntime.jsx(
17857
- ReplyComposerComponent,
17858
- {
17859
- avatar: comment.author?.avatar,
17860
- username: comment.author?.username,
17861
- placeholder: `Reply to @${comment.author?.username}`,
17862
- submitLabel: "Reply",
17863
- onSubmit: handleReply
17864
- }
17865
- ),
17866
- !!comment.replies?.length && /* @__PURE__ */ jsxRuntime.jsx(
17867
- RepliesToggleComponent,
17868
- {
17869
- collapsed,
17870
- repliesCount: comment.replies.length,
17871
- onToggle: () => setCollapsed((prev) => !prev)
17866
+ open: menuOpen,
17867
+ setOpen: setMenuOpen,
17868
+ canEdit: comment.permissions?.canEdit,
17869
+ canDelete: comment.permissions?.canDelete,
17870
+ canReport: comment.permissions?.canReport,
17871
+ onEdit: () => onEdit?.(comment),
17872
+ onDelete: () => onDelete?.(comment),
17873
+ onReport: () => onReport?.(comment)
17872
17874
  }
17873
17875
  )
17874
- ] }) }),
17875
- !collapsed && !!comment.replies?.length && /* @__PURE__ */ jsxRuntime.jsx(
17876
- "div",
17876
+ ] }),
17877
+ /* @__PURE__ */ jsxRuntime.jsx(ContentComponent, { comment }),
17878
+ /* @__PURE__ */ jsxRuntime.jsx(
17879
+ ActionsComponent,
17877
17880
  {
17878
- className: cn(
17879
- "border-l border-gray-200 dark:border-neutral-800",
17880
- "pl-4 ml-5"
17881
- ),
17882
- children: /* @__PURE__ */ jsxRuntime.jsx(
17883
- CommentList_default,
17884
- {
17885
- comments: comment.replies,
17886
- depth: depth + 1,
17887
- config,
17888
- components,
17889
- onLike,
17890
- onDislike,
17891
- onReply,
17892
- onEdit,
17893
- onDelete,
17894
- onReport
17895
- }
17896
- )
17881
+ comment,
17882
+ onLike: () => onLike?.(comment),
17883
+ onDislike: () => onDislike?.(comment),
17884
+ onReply: () => setIsReplying((prev) => !prev)
17897
17885
  }
17898
- )
17899
- ]
17886
+ ),
17887
+ isReplying && /* @__PURE__ */ jsxRuntime.jsx(
17888
+ ReplyComposerComponent,
17889
+ {
17890
+ avatar: comment.author?.avatar,
17891
+ username: comment.author?.username,
17892
+ placeholder: `Reply to @${comment.author?.username}`,
17893
+ submitLabel: "Reply",
17894
+ onSubmit: handleReply
17895
+ }
17896
+ ),
17897
+ comment.replyCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
17898
+ RepliesToggleComponent,
17899
+ {
17900
+ collapsed,
17901
+ loading: loadingReplies,
17902
+ repliesCount: comment.replyCount,
17903
+ onToggle: handleToggleReplies
17904
+ }
17905
+ ),
17906
+ !collapsed && replies.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-5 border-l border-gray-200 pl-4 dark:border-neutral-800", children: /* @__PURE__ */ jsxRuntime.jsx(
17907
+ CommentList_default,
17908
+ {
17909
+ comments: replies,
17910
+ replies: allReplies,
17911
+ depth: depth + 1,
17912
+ config,
17913
+ components,
17914
+ onLike,
17915
+ onDislike,
17916
+ onReply,
17917
+ onLoadReplies,
17918
+ onEdit,
17919
+ onDelete,
17920
+ onReport
17921
+ }
17922
+ ) })
17923
+ ] }) })
17900
17924
  }
17901
17925
  );
17902
17926
  }
17903
17927
  var CommentItem_default = CommentItem;
17904
17928
  function CommentList({
17905
17929
  comments,
17930
+ replies = {},
17906
17931
  depth = 0,
17907
17932
  config,
17908
17933
  components,
17909
17934
  onLike,
17910
17935
  onDislike,
17911
17936
  onReply,
17937
+ onLoadReplies,
17912
17938
  onEdit,
17913
17939
  onDelete,
17914
17940
  onReport
@@ -17920,12 +17946,15 @@ function CommentList({
17920
17946
  CommentItem_default,
17921
17947
  {
17922
17948
  comment,
17949
+ replies: replies[comment.id] ?? [],
17950
+ allReplies: replies,
17923
17951
  depth,
17924
17952
  config,
17925
17953
  components,
17926
17954
  onLike,
17927
17955
  onDislike,
17928
17956
  onReply,
17957
+ onLoadReplies,
17929
17958
  onEdit,
17930
17959
  onDelete,
17931
17960
  onReport
@@ -17935,64 +17964,20 @@ function CommentList({
17935
17964
  }
17936
17965
  var CommentList_default = CommentList;
17937
17966
 
17938
- // src/compositions/comment-thread/utils/buildCommentTree.ts
17939
- function buildCommentTree(comments) {
17940
- const map = /* @__PURE__ */ new Map();
17941
- const roots = [];
17942
- comments.forEach((comment) => {
17943
- map.set(comment.id, {
17944
- ...comment,
17945
- replies: []
17946
- });
17947
- });
17948
- comments.forEach((comment) => {
17949
- const current = map.get(comment.id);
17950
- if (!comment.parentCommentId) {
17951
- roots.push(current);
17952
- return;
17953
- }
17954
- const parent = map.get(comment.parentCommentId);
17955
- if (parent) {
17956
- parent.replies.push(current);
17957
- }
17958
- });
17959
- return roots;
17960
- }
17961
-
17962
17967
  // src/compositions/comment-thread/utils/updateComment.ts
17963
17968
  function updateComment(comments, commentId, updater) {
17964
- return comments.map((comment) => {
17965
- if (comment.id === commentId) {
17966
- return updater(comment);
17967
- }
17968
- return {
17969
- ...comment,
17970
- replies: comment.replies ? updateComment(comment.replies, commentId, updater) : []
17971
- };
17972
- });
17973
- }
17974
-
17975
- // src/compositions/comment-thread/utils/insertReply.ts
17976
- function insertReply(comments, parentId, reply) {
17977
- return comments.map((comment) => {
17978
- if (comment.id === parentId) {
17979
- return {
17980
- ...comment,
17981
- replies: [...comment.replies || [], reply]
17982
- };
17983
- }
17984
- return {
17985
- ...comment,
17986
- replies: comment.replies ? insertReply(comment.replies, parentId, reply) : []
17987
- };
17988
- });
17969
+ return comments.map(
17970
+ (comment) => comment.id === commentId ? updater(comment) : comment
17971
+ );
17989
17972
  }
17990
17973
 
17991
17974
  // src/compositions/comment-thread/constants/comment.constants.ts
17992
17975
  var DEFAULT_COMMENT_THREAD_CONFIG = {
17993
17976
  maxDepth: 5,
17994
17977
  indentation: 24,
17995
- dateFormat: "relative"
17978
+ dateFormat: "relative",
17979
+ commentsPerPage: 20,
17980
+ repliesPerPage: 10
17996
17981
  };
17997
17982
  function CommentThread({
17998
17983
  currentUser,
@@ -18006,6 +17991,9 @@ function CommentThread({
18006
17991
  }) {
18007
17992
  const [comments, setComments] = React4.useState([]);
18008
17993
  const [isLoadingComments, setIsLoadingComments] = React4.useState(true);
17994
+ const [replyCache, setReplyCache] = React4.useState(
17995
+ {}
17996
+ );
18009
17997
  const ComposerComponent = components?.Composer ?? CommentComposer_default;
18010
17998
  const resolvedConfig = {
18011
17999
  ...DEFAULT_COMMENT_THREAD_CONFIG,
@@ -18014,14 +18002,27 @@ function CommentThread({
18014
18002
  React4.useEffect(() => {
18015
18003
  async function loadComments() {
18016
18004
  try {
18017
- const result = await dataSource.getComments();
18018
- setComments(buildCommentTree(result));
18005
+ const result = await dataSource.getComments({
18006
+ parentCommentId: null,
18007
+ limit: resolvedConfig.commentsPerPage
18008
+ });
18009
+ setComments(result.items);
18019
18010
  } finally {
18020
18011
  setIsLoadingComments(false);
18021
18012
  }
18022
18013
  }
18023
18014
  loadComments();
18024
- }, [dataSource]);
18015
+ }, [dataSource, resolvedConfig.commentsPerPage]);
18016
+ function updateReplyCacheComment(cache, commentId, updater) {
18017
+ return Object.fromEntries(
18018
+ Object.entries(cache).map(([parentId, replies]) => [
18019
+ parentId,
18020
+ replies.map(
18021
+ (reply) => reply.id === commentId ? updater(reply) : reply
18022
+ )
18023
+ ])
18024
+ );
18025
+ }
18025
18026
  async function handleCreateComment(content) {
18026
18027
  const comment = await dataSource.createComment({
18027
18028
  content,
@@ -18034,7 +18035,35 @@ function CommentThread({
18034
18035
  content,
18035
18036
  parentCommentId: parentComment.id
18036
18037
  });
18037
- setComments((prev) => insertReply(prev, parentComment.id, reply));
18038
+ setReplyCache((prev) => ({
18039
+ ...prev,
18040
+ [parentComment.id]: [...prev[parentComment.id] ?? [], reply]
18041
+ }));
18042
+ const incrementReplyCount = (items) => items.map(
18043
+ (item) => item.id === parentComment.id ? {
18044
+ ...item,
18045
+ replyCount: item.replyCount + 1
18046
+ } : item
18047
+ );
18048
+ setComments((prev) => incrementReplyCount(prev));
18049
+ setReplyCache(
18050
+ (prev) => updateReplyCacheComment(prev, parentComment.id, (item) => ({
18051
+ ...item,
18052
+ replyCount: item.replyCount + 1
18053
+ }))
18054
+ );
18055
+ }
18056
+ async function handleLoadReplies(comment) {
18057
+ if (replyCache[comment.id]) {
18058
+ return;
18059
+ }
18060
+ const result = await dataSource.getReplies(comment.id, {
18061
+ limit: resolvedConfig.repliesPerPage
18062
+ });
18063
+ setReplyCache((prev) => ({
18064
+ ...prev,
18065
+ [comment.id]: result.items
18066
+ }));
18038
18067
  }
18039
18068
  async function handleLike(comment) {
18040
18069
  const optimistic = {
@@ -18043,19 +18072,22 @@ function CommentThread({
18043
18072
  likes: comment.liked ? Math.max(comment.likes - 1, 0) : comment.likes + 1,
18044
18073
  dislikes: comment.disliked ? Math.max(comment.dislikes - 1, 0) : comment.dislikes
18045
18074
  };
18046
- setComments(
18047
- (prev) => updateComment(prev, comment.id, (item) => ({
18048
- ...item,
18049
- likes: optimistic.likes,
18050
- dislikes: optimistic.dislikes,
18051
- liked: optimistic.liked,
18052
- disliked: optimistic.disliked
18053
- }))
18054
- );
18075
+ const updater = (item) => ({
18076
+ ...item,
18077
+ likes: optimistic.likes,
18078
+ dislikes: optimistic.dislikes,
18079
+ liked: optimistic.liked,
18080
+ disliked: optimistic.disliked
18081
+ });
18082
+ setComments((prev) => updateComment(prev, comment.id, updater));
18083
+ setReplyCache((prev) => updateReplyCacheComment(prev, comment.id, updater));
18055
18084
  try {
18056
18085
  await dataSource.toggleLike(comment.id);
18057
18086
  } catch {
18058
18087
  setComments((prev) => updateComment(prev, comment.id, () => comment));
18088
+ setReplyCache(
18089
+ (prev) => updateReplyCacheComment(prev, comment.id, () => comment)
18090
+ );
18059
18091
  }
18060
18092
  }
18061
18093
  async function handleDislike(comment) {
@@ -18065,19 +18097,22 @@ function CommentThread({
18065
18097
  likes: comment.liked ? Math.max(comment.likes - 1, 0) : comment.likes,
18066
18098
  dislikes: comment.disliked ? Math.max(comment.dislikes - 1, 0) : comment.dislikes + 1
18067
18099
  };
18068
- setComments(
18069
- (prev) => updateComment(prev, comment.id, (item) => ({
18070
- ...item,
18071
- likes: optimistic.likes,
18072
- dislikes: optimistic.dislikes,
18073
- liked: optimistic.liked,
18074
- disliked: optimistic.disliked
18075
- }))
18076
- );
18100
+ const updater = (item) => ({
18101
+ ...item,
18102
+ likes: optimistic.likes,
18103
+ dislikes: optimistic.dislikes,
18104
+ liked: optimistic.liked,
18105
+ disliked: optimistic.disliked
18106
+ });
18107
+ setComments((prev) => updateComment(prev, comment.id, updater));
18108
+ setReplyCache((prev) => updateReplyCacheComment(prev, comment.id, updater));
18077
18109
  try {
18078
18110
  await dataSource.toggleDislike(comment.id);
18079
18111
  } catch {
18080
18112
  setComments((prev) => updateComment(prev, comment.id, () => comment));
18113
+ setReplyCache(
18114
+ (prev) => updateReplyCacheComment(prev, comment.id, () => comment)
18115
+ );
18081
18116
  }
18082
18117
  }
18083
18118
  if (loading || isLoadingComments) {
@@ -18097,11 +18132,13 @@ function CommentThread({
18097
18132
  CommentList_default,
18098
18133
  {
18099
18134
  comments,
18100
- components,
18135
+ replies: replyCache,
18101
18136
  config: resolvedConfig,
18137
+ components,
18102
18138
  onLike: handleLike,
18103
18139
  onDislike: handleDislike,
18104
18140
  onReply: handleReply,
18141
+ onLoadReplies: handleLoadReplies,
18105
18142
  onEdit,
18106
18143
  onDelete,
18107
18144
  onReport