diff --git a/src/v4/social/components/PostComment/PostComment.module.css b/src/v4/social/components/PostComment/PostComment.module.css deleted file mode 100644 index 17d6b628..00000000 --- a/src/v4/social/components/PostComment/PostComment.module.css +++ /dev/null @@ -1,175 +0,0 @@ -.postComment { - width: 100%; - display: grid; - grid-template-columns: min-content minmax(0, 1fr); - gap: 0.5rem; -} - -.postComment__deleteComment_container { - display: flex; - padding: 0.75rem 1rem; - align-items: flex-start; - gap: 1rem; - border-top: 1px solid var(--asc-color-base-shade4); - border-bottom: 1px solid var(--asc-color-base-shade4); -} - -.postComment__deleteComment_icon { - width: 1.25rem; - height: 1.25rem; - color: var(--asc-color-base-shade2); -} - -.postComment__deleteComment_text { - color: var(--asc-color-base-shade2); -} - -.postComment__avatar { - margin-right: 0.75rem; - display: inline-block; -} - -.postComment__details { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.postComment__content { - display: flex; - flex-direction: column; - gap: 0.25rem; - justify-content: center; - align-items: start; - background-color: var(--asc-color-base-shade4); - border-radius: 0 0.75rem 0.75rem; - padding: 0.75rem; - max-width: max-content; -} - -.postComment__content__username { - color: var(--asc-color-base-default); -} - -.postComment__content__text { - color: var(--asc-color-base-default); -} - -.postComment__secondRow { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; -} - -.postComment__secondRow__leftPane { - display: flex; - gap: 0.75rem; - align-items: center; -} - -.postComment__secondRow__timestamp { - display: flex; - color: var(--asc-color-base-shade2); - white-space: pre; -} - -.postComment__secondRow__rightPane { - display: flex; - gap: 0.25rem; - align-items: center; -} - -.postComment__secondRow__rightPane__like { - width: 1.25rem; - height: 1.25rem; -} - -.postComment__secondRow__like { - cursor: pointer; - color: var(--asc-color-base-shade2); -} - -.postComment__secondRow__like[data-is-liked='true'] { - color: var(--asc-color-primary-default); -} - -.postComment__secondRow__reply { - cursor: pointer; - color: var(--asc-color-base-shade2); -} - -.postComment__secondRow__actionButton { - cursor: pointer; - height: 1.25rem; - fill: var(--asc-color-secondary-shade2); -} - -.postComment__viewReply_button { - display: flex; - padding: 0.3125rem 0.75rem 0.3125rem 0.5rem; - cursor: pointer; - align-items: flex-start; - gap: 0.25rem; - background: var(--asc-color-background-default); - border: 1px solid var(--asc-color-base-shade4); - border-radius: 0.25rem; - width: max-content; -} - -.postComment__viewReply_icon { - color: var(--asc-color-secondary-shade1); - width: 1rem; - height: 1rem; -} - -.postComment__viewReply_text { - color: var(--asc-color-secondary-shade1); -} - -.postComment__edit { - display: flex; - gap: 0.5rem; -} - -.postComment__edit__inputWrap { - width: 100%; -} - -.postComment__edit__input { - display: flex; - height: 7.5rem; - padding: 0.75rem; - flex-direction: column; - align-items: flex-start; - gap: 0.25rem; - align-self: stretch; - background-color: var(--asc-color-base-shade4); - width: 100%; - border-radius: 0 0.75rem 0.75rem; -} - -.postComment__edit__buttonWrap { - display: flex; - gap: var(--asc-spacing-s1); - justify-content: flex-end; - margin-top: var(--asc-spacing-s1); -} - -.postComment__edit__button { - padding: 0.375rem 0.75rem; - cursor: pointer; -} - -.postComment__edit__cancelButton { - color: var(--asc-color-secondary-shade1); - border-radius: var(--asc-border-radius-sm); - border: 1px solid var(--asc-color-secondary-shade1); - background: var(--asc-color-background-default); -} - -.postComment__edit__saveButton { - color: var(--asc-color-white); - border-radius: var(--asc-border-radius-sm); - background: var(--asc-color-primary-default); -} diff --git a/src/v4/social/components/PostComment/PostComment.stories.tsx b/src/v4/social/components/PostComment/PostComment.stories.tsx deleted file mode 100644 index a003f065..00000000 --- a/src/v4/social/components/PostComment/PostComment.stories.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import useOneComment from '~/mock/useOneComment'; - -import { PostComment } from './PostComment'; - -export default { - title: 'v4-social/components/PostComment', -}; - -export const PostCommentStory = { - render: () => { - const [comment] = useOneComment(); - - if (comment == null) return null; - - return ; - }, - - name: 'PostComment', -}; diff --git a/src/v4/social/components/PostComment/PostComment.tsx b/src/v4/social/components/PostComment/PostComment.tsx deleted file mode 100644 index 77acbdb2..00000000 --- a/src/v4/social/components/PostComment/PostComment.tsx +++ /dev/null @@ -1,297 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { Typography, BottomSheet } from '~/v4/core/components'; -import { ModeratorBadge } from '~/v4/social/elements/ModeratorBadge'; -import { Timestamp } from '~/v4/social/elements/Timestamp'; -import { UserAvatar } from '~/v4/social/internal-components/UserAvatar'; -import styles from './PostComment.module.css'; -import { useAmityComponent } from '~/v4/core/hooks/uikit'; -import ReplyComment from '~/v4/icons/ReplyComment'; -import { PostReplyCommentList } from '../PostReplyCommentList/PostReplyCommentList'; -import { MinusCircleIcon } from '../../icons/index'; -import { Mentionees } from '~/v4/helpers/utils'; -import { CommentRepository, ReactionRepository } from '@amityco/ts-sdk'; -import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { LIKE_REACTION_KEY } from '../../constants/reactions'; -import { EditCancelButton } from '../../elements/EditCancelButton/EditCancelButton'; -import { SaveButton } from '../../elements/SaveButton/SaveButton'; -import clsx from 'clsx'; -import { PostCommentInput } from '../PostCommentComposer/PostCommentInput'; -import { CommentOptions } from '../CommentOptions/CommentOptions'; -import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; -import useCommentSubscription from '~/v4/core/hooks/subscriptions/useCommentSubscription'; -import { TextWithMention } from '../../internal-components/TextWithMention/TextWithMention'; -import millify from 'millify'; - -const EllipsisH = ({ ...props }: React.SVGProps) => ( - - - -); - -const Like = ({ ...props }: React.SVGProps) => ( - - - - - - - - - - -); - -interface PostCommentProps { - pageId?: string; - componentId?: string; - comment: Amity.Comment; - postTargetType: Amity.PostTargetType; - postTargetId: string; - onClickReply: (comment: Amity.Comment) => void; -} - -export const PostComment = ({ - pageId = '*', - componentId = 'comment_bubble', - comment, - postTargetType, - postTargetId, - onClickReply, -}: PostCommentProps) => { - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityComponent({ - pageId, - componentId, - }); - - const { confirm } = useConfirmContext(); - - const [bottomSheetOpen, setBottomSheetOpen] = useState(false); - const [hasClickLoadMore, setHasClickLoadMore] = useState(false); - const [isEditing, setIsEditing] = useState(false); - const [commentData, setCommentData] = useState(); - - const toggleBottomSheet = () => setBottomSheetOpen((prev) => !prev); - - const isLiked = (comment.myReactions || []).some((reaction) => reaction === 'like'); - - const replyAmount = comment.childrenNumber; - - if (isExcluded) return null; - - const deleteComment = async () => - comment.commentId && CommentRepository.deleteComment(comment.commentId); - - const handleEditComment = () => { - setIsEditing(true); - }; - - const handleDeleteComment = () => { - confirm({ - pageId, - componentId, - title: 'Delete comment', - content: 'This comment will be permanently removed.', - cancelText: 'Cancel', - okText: 'Delete', - onOk: deleteComment, - }); - }; - - const handleLike = async () => { - if (!comment) return; - - if (!isLiked) { - await ReactionRepository.addReaction('comment', comment?.commentId, LIKE_REACTION_KEY); - } else { - await ReactionRepository.removeReaction('comment', comment?.commentId, LIKE_REACTION_KEY); - } - }; - - const handleSaveComment = useCallback(async () => { - if (!commentData || !comment.commentId) return; - - await CommentRepository.updateComment(comment.commentId, { - data: commentData.data, - mentionees: commentData.mentionees as Amity.UserMention[], - metadata: commentData.metadata, - }); - - setIsEditing(false); - }, [commentData]); - - useCommentSubscription({ - commentId: comment.commentId, - }); - - return ( -
- {comment.isDeleted ? ( -
- - - This comment has been deleted - -
- ) : isEditing ? ( -
- -
-
- { - setCommentData(value); - }} - maxLines={5} - mentionOffsetBottom={215} - /> -
-
- { - setIsEditing(false); - }} - /> - -
-
-
- ) : ( -
- -
-
- - {comment.creator?.displayName} - - - - - -
-
-
- - - {comment.createdAt !== comment.editedAt && ' (edited)'} - - -
- - {isLiked ? 'Liked' : 'Like'} - -
-
onClickReply(comment)}> - - Reply - -
- - setBottomSheetOpen(true)} - /> -
- {comment.reactionsCount > 0 && ( -
- {millify(comment.reactionsCount)} - -
- )} -
- - {replyAmount > 0 && !hasClickLoadMore && ( -
setHasClickLoadMore(true)} - > - - - View {replyAmount} {replyAmount > 1 ? 'replies' : 'reply'} - -
- )} - - {hasClickLoadMore && ( - - )} -
-
- )} - - - -
- ); -}; diff --git a/src/v4/social/components/PostComment/PostCommentSkeleton.module.css b/src/v4/social/components/PostComment/PostCommentSkeleton.module.css deleted file mode 100644 index 9539c2e3..00000000 --- a/src/v4/social/components/PostComment/PostCommentSkeleton.module.css +++ /dev/null @@ -1,51 +0,0 @@ -.postCommentSkeleton { - display: flex; - gap: 0.5rem; -} - -.postCommentSkeleton__avatar { - width: 2rem; - height: 2rem; - border-radius: 50%; - background: var(--asc-color-base-shade4); -} - -.postCommentSkeleton__details { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.postCommentSkeleton__content { - background-color: var(--asc-color-base-shade4); - border-radius: 0 0.75rem 0.75rem; - width: 13.875rem; - height: 4.25rem; -} - -.postCommentSkeleton__content__bar { - width: 10.3269rem; - height: 0.5rem; - border-radius: 0.75rem; - background: var(--asc-color-base-shade4); - padding-top: 0.19rem; - padding-bottom: 0.19rem; -} - -.postCommentSkeleton__animation { - animation: skeleton-pulse 1.5s ease-in-out infinite; -} - -@keyframes skeleton-pulse { - 0% { - opacity: 0.6; - } - - 50% { - opacity: 1; - } - - 100% { - opacity: 0.6; - } -} diff --git a/src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx b/src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx deleted file mode 100644 index 869f47b8..00000000 --- a/src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import useOnePost from '~/mock/useOnePost'; - -import { PostCommentSkeleton } from './PostCommentSkeleton'; - -export default { - title: 'v4-social/components/PostCommentSkeleton', -}; - -export const PostCommentSkeletonStory = { - render: () => { - const [post] = useOnePost(); - - if (post == null) return null; - - return ; - }, - - name: 'PostCommentSkeleton', -}; diff --git a/src/v4/social/components/PostComment/PostCommentSkeleton.tsx b/src/v4/social/components/PostComment/PostCommentSkeleton.tsx deleted file mode 100644 index 7ad2cbb0..00000000 --- a/src/v4/social/components/PostComment/PostCommentSkeleton.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import clsx from 'clsx'; - -import styles from './PostCommentSkeleton.module.css'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; -import { useAmityComponent } from '~/v4/core/hooks/uikit'; - -interface PostCommentSkeletonProps { - pageId?: string; -} - -export const PostCommentSkeleton = ({ pageId = '*' }: PostCommentSkeletonProps) => { - const componentId = 'post_comment'; - const { accessibilityId, isExcluded, themeStyles } = useAmityComponent({ - pageId, - componentId, - }); - - if (isExcluded) return null; - - return ( -
-
-
-
-
-
-
- ); -}; diff --git a/src/v4/social/components/PostComment/index.tsx b/src/v4/social/components/PostComment/index.tsx deleted file mode 100644 index 55aa2252..00000000 --- a/src/v4/social/components/PostComment/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { PostComment } from './PostComment'; -export { PostCommentSkeleton } from './PostCommentSkeleton'; diff --git a/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css b/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css deleted file mode 100644 index 554f64b3..00000000 --- a/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css +++ /dev/null @@ -1,67 +0,0 @@ -.postCommentComposer__container { - position: relative; - display: flex; - flex-direction: row; - background-color: var(--asc-color-background-default); - align-items: flex-end; - padding: var(--asc-spacing-s1) 0 var(--asc-spacing-s1) var(--asc-spacing-m1); - border-top: 1px solid var(--asc-color-base-shade4); - width: 100%; -} - -.postCommentComposer__avatar { - width: 2rem; - height: 2rem; - margin-bottom: var(--asc-spacing-xxs2); - border-radius: var(--asc-border-radius-full); - margin-right: var(--asc-spacing-s1); -} - -.postCommentComposer__button { - border: none; - margin-bottom: var(--asc-spacing-xxs2); -} - -.postCommentComposer__button:hover { - background-color: transparent !important; -} - -.postCommentComposer__button:disabled { - color: var(--asc-color-primary-shade2); -} - -.postCommentComposer__input { - flex-grow: 1; - background-color: var(--asc-color-base-shade4); - border-radius: 1.25rem; - padding: 0.625rem 0.75rem; -} - -.postCommentComposer__replyContainer { - position: absolute; - left: 0; - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.625rem 1rem; - background-color: var(--asc-color-base-shade4); -} - -.postCommentComposer__replyContainer__text { - font-size: var(--asc-text-font-size-sm); - font-weight: var(--asc-text-font-weight-normal); - line-height: var(--asc-line-height-sm); - color: var(--asc-color-base-shade1); -} - -.postCommentComposer__replyContainer__username { - font-weight: var(--asc-text-font-weight-bold); -} - -.postCommentComposer__replyContainer__closeButton { - fill: var(--asc-color-base-shade2); - width: 1.25rem; - height: 1.25rem; - cursor: pointer; -} diff --git a/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx b/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx deleted file mode 100644 index cdf76dd7..00000000 --- a/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Avatar, Button } from '~/v4/core/components/index'; -import { useUser } from '~/v4/core/hooks/objects/useUser'; -import { useImage } from '~/v4/core/hooks/useImage'; -import useSDK from '~/v4/core/hooks/useSDK'; -import User from '~/v4/icons/User'; -import { PostCommentInput, PostCommentInputRef } from './PostCommentInput'; -import styles from './PostCommentComposer.module.css'; -import { useMutation } from '@tanstack/react-query'; -import { CommentRepository } from '@amityco/ts-sdk'; -import Close from '~/v4/icons/Close'; -import { Mentionees, Metadata } from '~/v4/helpers/utils'; - -export type CreateCommentParams = { - data: { - text: string; - }; - mentionees: Mentionees; - metadata: Metadata; -}; - -export const PostCommentComposer = ({ - post, - replyTo, - onCancelReply, -}: { - post: Amity.Post; - replyTo?: Amity.Comment; - onCancelReply: () => void; -}) => { - const userId = useSDK().currentUserId; - const { user } = useUser(userId); - const avatarUrl = useImage({ fileId: user?.avatar?.fileId, imageSize: 'small' }); - const editorRef = useRef(null); - const composerRef = useRef(null); - - const [composerHeight, setComposerHeight] = useState(0); - - const [textValue, setTextValue] = useState({ - data: { - text: '', - }, - mentionees: [ - { - type: 'user', - userIds: [''], - }, - ], - metadata: {}, - }); - - const onChange = (val: any) => { - setTextValue(val); - }; - - const { mutateAsync } = useMutation({ - mutationFn: async ({ params }: { params: CreateCommentParams }) => { - const referenceId = replyTo ? replyTo.referenceId : post.postId; - const referenceType = replyTo ? replyTo.referenceType : 'post'; - const parentId = replyTo ? replyTo.commentId : undefined; - - await CommentRepository.createComment({ - referenceId, - referenceType, - parentId, - ...params, - mentionees: params.mentionees as Amity.UserMention[], - }); - }, - onError: (error) => {}, - onSuccess: () => { - setTextValue({ - data: { text: '' }, - mentionees: [], - metadata: {}, - }); - editorRef.current?.clearEditorState(); - onCancelReply(); - }, - }); - - useEffect(() => { - if (composerRef.current) { - setComposerHeight(composerRef.current.offsetHeight); - } - }, []); - - return ( -
-
- } /> -
-
- -
- - {replyTo && ( -
-
- Replying to - - {replyTo?.userId} - -
- -
- )} -
- ); -}; diff --git a/src/v4/social/components/PostCommentComposer/PostCommentInput.module.css b/src/v4/social/components/PostCommentComposer/PostCommentInput.module.css deleted file mode 100644 index daae1b98..00000000 --- a/src/v4/social/components/PostCommentComposer/PostCommentInput.module.css +++ /dev/null @@ -1,45 +0,0 @@ -.editorPlaceholder { - color: #999; - overflow: hidden; - position: absolute; - top: 0; - user-select: none; - pointer-events: none; -} - -.editorParagraph { - height: 100%; - width: 100%; - color: var(--asc-color-base-default); -} - -.editorParagraph span { - color: var(--asc-color-base-default); -} - -.editorContainer { - width: 100%; - height: 100%; - color: var(--asc-color-base-default); - max-height: calc(var(--asc-line-height-md) * var(--var-max-lines) + (0.62rem * 2)); - - /* padding: 0.62rem 1rem; */ - position: relative; - - p { - line-height: var(--asc-line-height-md); - font-size: var(--asc-text-font-size-md); - margin: 0; - } -} - -.editorEditableContent { - height: max-content; - max-height: calc(var(--asc-line-height-md) * var(--var-max-lines)); - overflow-y: scroll; -} - -.editorContainer :focus { - border: none; - outline: none; -} diff --git a/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx b/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx deleted file mode 100644 index 81a8f8ae..00000000 --- a/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx +++ /dev/null @@ -1,298 +0,0 @@ -import React, { forwardRef, MutableRefObject, useImperativeHandle } from 'react'; -import { LexicalComposer } from '@lexical/react/LexicalComposer'; -import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; -import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; -import { ContentEditable } from '@lexical/react/LexicalContentEditable'; -import { EditorRefPlugin } from '@lexical/react/LexicalEditorRefPlugin'; -import { - $getRoot, - LexicalEditor, - SerializedLexicalNode, - SerializedTextNode, - SerializedRootNode, - SerializedParagraphNode, -} from 'lexical'; -import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; -import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; -import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; -import { - MentionNode, - SerializedMentionNode, -} from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; -import styles from './PostCommentInput.module.css'; -import { PostCommentMentionInput } from '../PostCommentMentionInput'; -import { useMentionUsers } from '../../hooks/useMentionUser'; -import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; -import { Mentioned, Mentionees } from '~/v4/helpers/utils'; - -const theme = { - ltr: 'ltr', - rtl: 'rtl', - placeholder: styles.editorPlaceholder, - paragraph: styles.editorParagraph, -}; - -const editorConfig = { - namespace: 'PostCommentInput', - theme: theme, - onError(error: Error) { - throw error; - }, - nodes: [MentionNode], -}; - -interface EditorStateJson extends SerializedLexicalNode { - children: []; -} - -interface PostCommentInputProps { - postTargetType: Amity.PostTargetType; - postTargetId: string; - value?: CreateCommentParams; - mentionOffsetBottom?: number; - maxLines?: number; - placehoder?: string; - ref: MutableRefObject; - onChange: (data: CreateCommentParams) => void; -} - -export interface PostCommentInputRef { - clearEditorState: () => void; -} - -function editorStateToText(editor: LexicalEditor): CreateCommentParams { - const editorStateTextString: string[] = []; - const paragraphs = editor.getEditorState().toJSON().root.children as EditorStateJson[]; - - const mentioned: Mentioned[] = []; - const mentionees: { - type: Amity.Mention['type']; - userIds: string[]; - }[] = []; - let runningIndex = 0; - - paragraphs.forEach((paragraph) => { - const children = paragraph.children; - const paragraphText: string[] = []; - - children.forEach((child: { type: string; text: string; userId: string }) => { - if (child.text) { - paragraphText.push(child.text); - } - if (child.type === 'mention') { - mentioned.push({ - index: runningIndex, - length: child.text.length, - type: 'user', - userId: child.userId, - }); - - mentionees.push({ type: 'user', userIds: [child.userId] }); - } - runningIndex += child.text.length; - }); - runningIndex += 1; - editorStateTextString.push(paragraphText.join('')); - }); - - return { - data: { text: editorStateTextString.join('\n') }, - mentionees, - metadata: { - mentioned, - }, - }; -} - -function createRootNode(): SerializedRootNode { - return { - children: [], - direction: 'ltr', - format: '', - indent: 0, - type: 'root', - version: 1, - }; -} - -function createParagraphNode(): SerializedParagraphNode { - return { - children: [], - direction: 'ltr', - format: '', - indent: 0, - type: 'paragraph', - version: 1, - textFormat: 0, - }; -} - -function createSerializeTextNode(text: string): SerializedTextNode { - return { - detail: 0, - format: 0, - mode: 'normal', - style: '', - text, - type: 'text', - version: 1, - }; -} - -function createSerializeMentionNode(mention: Mentioned): SerializedMentionNode { - return { - detail: 0, - format: 0, - mode: 'normal', - style: '', - text: ('@' + mention.userId) as string, - type: 'mention', - version: 1, - mentionName: mention.userId as string, - displayName: mention.userId as string, - userId: mention.userId as string, - userInternalId: mention.userId as string, - userPublicId: mention.userId as string, - }; -} - -export function TextToEditorState(value: { - data: { text: string }; - metadata?: { - mentioned?: Mentioned[]; - }; - mentionees?: Mentionees; -}) { - const rootNode = createRootNode(); - - const textArray = value.data.text.split('\n'); - - const mentions = value.metadata?.mentioned; - - let start = 0; - let stop = -1; - let mentionRunningIndex = 0; - - for (let i = 0; i < textArray.length; i++) { - start = stop + 1; - stop = start + textArray[i].length; - - const paragraph = createParagraphNode(); - - if (Array.isArray(mentions) && mentions?.length > 0) { - let runningIndex = 0; - - while (runningIndex < textArray[i].length) { - if (mentionRunningIndex >= mentions.length) { - paragraph.children.push(createSerializeTextNode(textArray[i].slice(runningIndex))); - runningIndex = textArray[i].length; - break; - } - - if (mentions[mentionRunningIndex].index >= stop) { - paragraph.children.push(createSerializeTextNode(textArray[i])); - runningIndex = textArray[i].length; - } else { - const text = textArray[i].slice( - runningIndex, - runningIndex + mentions[mentionRunningIndex]?.index - start, - ); - - if (text) { - paragraph.children.push(createSerializeTextNode(text)); - } - - paragraph.children.push(createSerializeMentionNode(mentions[mentionRunningIndex])); - - runningIndex += - mentions[mentionRunningIndex].index + mentions[mentionRunningIndex].length - start; - - mentionRunningIndex++; - } - } - } - - if (!mentions || mentions?.length === 0) { - const textNode = createSerializeTextNode(textArray[i]); - paragraph.children.push(textNode); - } - - rootNode.children.push(paragraph); - } - - return { root: rootNode }; -} - -export const PostCommentInput = forwardRef( - ( - { - postTargetType, - postTargetId, - mentionOffsetBottom = 0, - value, - onChange, - maxLines = 10, - placehoder, - }, - ref, - ) => { - const editorRef = React.useRef(null); - const [queryMentionUser, setQueryMentionUser] = React.useState(null); - - const { mentionUsers } = useMentionUsers({ - displayName: queryMentionUser || '', - postTargetType: postTargetType, - postTargetId: postTargetId, - }); - - const clearEditorState = () => { - editorRef.current?.update(() => { - const root = $getRoot(); - root.clear(); - }); - }; - - useImperativeHandle(ref, () => ({ - clearEditorState, - })); - - return ( - -
- } - placeholder={ - placehoder ?
{placehoder}
: null - } - ErrorBoundary={LexicalErrorBoundary} - /> - { - onChange(editorStateToText(editor)); - }} - /> - - - - setQueryMentionUser(query)} - offsetBottom={mentionOffsetBottom} - /> -
-
- ); - }, -); diff --git a/src/v4/social/components/PostCommentComposer/index.tsx b/src/v4/social/components/PostCommentComposer/index.tsx deleted file mode 100644 index 9226b06e..00000000 --- a/src/v4/social/components/PostCommentComposer/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { PostCommentComposer } from './PostCommentComposer'; diff --git a/src/v4/social/components/PostCommentList/PostCommentList.module.css b/src/v4/social/components/PostCommentList/PostCommentList.module.css deleted file mode 100644 index fa8cceca..00000000 --- a/src/v4/social/components/PostCommentList/PostCommentList.module.css +++ /dev/null @@ -1,18 +0,0 @@ -.postCommentList__container { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.postCommentList__viewAllComments__button { - width: 100%; - padding: var(--asc-spacing-s2) 0; - margin: auto; - border-top: 0.0625rem solid var(--asc-color-base-shade4); - cursor: pointer; - text-align: center; -} - -.postCommentList__container_intersection { - height: 1px; -} diff --git a/src/v4/social/components/PostCommentList/PostCommentList.tsx b/src/v4/social/components/PostCommentList/PostCommentList.tsx deleted file mode 100644 index 2cf0ef9f..00000000 --- a/src/v4/social/components/PostCommentList/PostCommentList.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useRef } from 'react'; -import styles from './PostCommentList.module.css'; -import { PostComment } from '../PostComment/PostComment'; -import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; -import useCommentsCollection from '../../hooks/collections/useCommentsCollection'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; -import useUserSubscription from '~/v4/core/hooks/subscriptions/useUserSubscription'; -import { CommentRepository, SubscriptionLevels } from '@amityco/ts-sdk'; -import useCommunitySubscription from '~/v4/core/hooks/subscriptions/useCommunitySubscription'; -import { usePaginator } from '~/v4/core/hooks/usePaginator'; -import { CommentAd } from '../../internal-components/CommentAd/CommentAd'; - -type PostCommentListProps = { - post: Amity.Post; - pageId?: string; - onClickReply: (comment: Amity.Comment) => void; -}; - -const isAmityAd = (item: Amity.Comment | Amity.InternalComment | Amity.Ad): item is Amity.Ad => { - return 'adId' in item; -}; - -export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommentListProps) => { - const componentId = 'comment_tray_component'; - - const { themeStyles, accessibilityId } = useAmityComponent({ - componentId, - pageId, - }); - - const containerRef = useRef(null); - const intersectionRef = useRef(null); - - const { items, loadMore, hasMore, isLoading } = usePaginator({ - fetcher: CommentRepository.getComments, - params: { - referenceId: post.postId, - referenceType: 'post', - limit: 5, - includeDeleted: true, - }, - placement: 'comment' as Amity.AdPlacement, - pageSize: 5, - getItemId: (item) => item.commentId, - }); - - useIntersectionObserver({ - ref: intersectionRef, - onIntersect: () => { - if (hasMore && isLoading === false) { - loadMore(); - } - }, - }); - - useUserSubscription({ - userId: post.targetId, - level: SubscriptionLevels.COMMENT, - shouldSubscribe: post.targetType === 'user', - }); - - useCommunitySubscription({ - communityId: post.targetId, - level: SubscriptionLevels.COMMENT, - shouldSubscribe: post.targetType === 'community', - }); - - return ( -
- {items.map((item) => { - return isAmityAd(item) ? ( - - ) : ( - onClickReply?.(comment)} - componentId={componentId} - postTargetId={post.targetId} - postTargetType={post.targetType} - /> - ); - })} - {/* TODO: add this button when implement desktop view */} - {/*
- View all comments... -
*/} - {!isLoading && ( -
- )} -
- ); -}; diff --git a/src/v4/social/components/PostCommentList/index.ts b/src/v4/social/components/PostCommentList/index.ts deleted file mode 100644 index 646fb933..00000000 --- a/src/v4/social/components/PostCommentList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PostCommentList } from './PostCommentList'; diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.module.css b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.module.css deleted file mode 100644 index 2b4a017b..00000000 --- a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.mentionTextInput_item { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 12.5rem; - overflow-y: scroll; -} - -.mentionTextInput_item::-webkit-scrollbar { - display: none; -} diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx deleted file mode 100644 index b747ca97..00000000 --- a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import styles from './PostCommentMentionInput.module.css'; -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; -import { - LexicalTypeaheadMenuPlugin, - MenuOption, - MenuTextMatch, -} from '@lexical/react/LexicalTypeaheadMenuPlugin'; -import { TextNode } from 'lexical'; -import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import ReactDOM from 'react-dom'; -import { $createMentionNode } from '../../internal-components/MentionTextInput/MentionNodes'; -import { CommunityMember } from '../../internal-components/CommunityMember/CommunityMember'; - -const MAX_LENGTH = 5000; - -const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'; -const NAME = '\\b[A-Z][^\\s' + PUNCTUATION + ']'; - -const DocumentMentionsRegex = { - NAME, - PUNCTUATION, -}; - -const PUNC = DocumentMentionsRegex.PUNCTUATION; - -const TRIGGERS = ['@'].join(''); - -// Chars we expect to see in a mention (non-space, non-punctuation). -const VALID_CHARS = '[^' + TRIGGERS + PUNC + '\\s]'; - -// Non-standard series of chars. Each series must be preceded and followed by -// a valid char. -const VALID_JOINS = - '(?:' + - '\\.[ |$]|' + // E.g. "r. " in "Mr. Smith" - ' |' + // E.g. " " in "Josh Duck" - '[' + - PUNC + - ']|' + // E.g. "-' in "Salier-Hellendag" - ')'; - -const LENGTH_LIMIT = 75; - -const AtSignMentionsRegex = new RegExp( - '(^|\\s|\\()(' + - '[' + - TRIGGERS + - ']' + - '((?:' + - VALID_CHARS + - VALID_JOINS + - '){0,' + - LENGTH_LIMIT + - '})' + - ')$', -); - -// 50 is the longest alias length limit. -const ALIAS_LENGTH_LIMIT = 50; - -// Regex used to match alias. -const AtSignMentionsRegexAliasRegex = new RegExp( - '(^|\\s|\\()(' + - '[' + - TRIGGERS + - ']' + - '((?:' + - VALID_CHARS + - '){0,' + - ALIAS_LENGTH_LIMIT + - '})' + - ')$', -); - -function checkForAtSignMentions(text: string, minMatchLength: number): MenuTextMatch | null { - let match = AtSignMentionsRegex.exec(text); - - if (match === null) { - match = AtSignMentionsRegexAliasRegex.exec(text); - } - if (match !== null) { - // The strategy ignores leading whitespace but we need to know it's - // length to add it to the leadOffset - const maybeLeadingWhitespace = match[1]; - - const matchingString = match[3]; - if (matchingString.length >= minMatchLength) { - return { - leadOffset: match.index + maybeLeadingWhitespace.length, - matchingString, - replaceableString: match[2], - }; - } - } - return null; -} - -function getPossibleQueryMatch(text: string): MenuTextMatch | null { - return checkForAtSignMentions(text, 1); -} - -export class MentionTypeaheadOption extends MenuOption { - user: Amity.User; - - constructor(user: Amity.User) { - super(user.userId); - this.user = user; - } -} - -export const PostCommentMentionInput = ({ - mentionUsers, - offsetBottom = 0, - onQueryChange, -}: { - mentionUsers: Amity.User[]; - offsetBottom?: number; - onQueryChange: (query: string) => void; -}) => { - const mentionTextInputItemRef = useRef(null); - return ( -
-
- -
- ); -}; - -type MentionProps = { - anchorRef: RefObject; - mentionUsers: Amity.User[]; - offsetBottom?: number; - onQueryChange: (queryString: string) => void; -}; - -function Mention({ anchorRef, mentionUsers, offsetBottom = 0, onQueryChange }: MentionProps) { - const [editor] = useLexicalComposerContext(); - - const [queryString, setQueryString] = useState(null); - - useEffect(() => { - queryString && onQueryChange(queryString); - }, [queryString]); - - const options = useMemo( - () => mentionUsers.map((user) => new MentionTypeaheadOption(user)), - [mentionUsers], - ); - - const onSelectOption = useCallback( - ( - selectedOption: MentionTypeaheadOption, - nodeToReplace: TextNode | null, - closeMenu: () => void, - ) => { - editor.update(() => { - const mentionNode = $createMentionNode({ - mentionName: selectedOption.key, - displayName: selectedOption.user.displayName, - userId: selectedOption.user.userId, - }); - if (nodeToReplace) { - nodeToReplace.replace(mentionNode); - } - mentionNode.select(); - closeMenu(); - }); - }, - [editor], - ); - - const checkForMentionMatch = useCallback( - (text: string) => { - return getPossibleQueryMatch(text); - }, - [editor], - ); - - return ( - - anchorRef.current && options.length - ? ReactDOM.createPortal( -
- {options.map((option, i: number) => ( - { - setHighlightedIndex(i); - selectOptionAndCleanUp(option); - }} - onMouseEnter={() => { - setHighlightedIndex(i); - }} - key={option.key} - option={option} - /> - ))} -
, - anchorRef.current, - ) - : null - } - /> - ); -} diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionNode.tsx b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionNode.tsx deleted file mode 100644 index 64a89d86..00000000 --- a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionNode.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import type { Spread } from 'lexical'; - -import { - DOMConversionMap, - DOMConversionOutput, - DOMExportOutput, - EditorConfig, - LexicalNode, - NodeKey, - SerializedTextNode, - TextNode, -} from 'lexical'; - -export type SerializedMentionNode = Spread< - { - mentionName: string; - displayName?: string; - userId?: string; - userInternalId?: string; - userPublicId?: string; - type: 'mention'; - version: 1; - }, - SerializedTextNode ->; - -function convertMentionElement(domNode: HTMLElement): DOMConversionOutput | null { - const textContent = domNode.textContent; - - if (textContent !== null) { - const node = $createMentionNode({ mentionName: textContent }); - return { - node, - }; - } - - return null; -} - -const mentionStyle = `color: var(--asc-color-primary-default)`; - -export class MentionNode extends TextNode { - __mention: string; - __displayName: string | undefined; - __userId: string | undefined; - __userInternalId: string | undefined; - __userPublicId: string | undefined; - - static getType(): string { - return 'mention'; - } - - static clone(node: MentionNode): MentionNode { - return new MentionNode({ - mentionName: node.__mention, - displayName: node.__displayName, - userId: node.__userId, - userInternalId: node.__userInternalId, - userPublicId: node.__userPublicId, - text: node.__text, - key: node.__key, - }); - } - static importJSON(serializedNode: SerializedMentionNode): MentionNode { - const node = $createMentionNode({ - mentionName: serializedNode.mentionName, - displayName: serializedNode.displayName, - userId: serializedNode.userId, - userInternalId: serializedNode.userInternalId, - userPublicId: serializedNode.userPublicId, - }); - node.setTextContent(serializedNode.text); - node.setFormat(serializedNode.format); - node.setDetail(serializedNode.detail); - node.setMode(serializedNode.mode); - node.setStyle(serializedNode.style); - return node; - } - - constructor({ - mentionName, - displayName, - userId, - userInternalId, - userPublicId, - text, - key, - }: { - mentionName: string; - text?: string; - key?: NodeKey; - displayName?: string; - userId?: string; - userInternalId?: string; - userPublicId?: string; - }) { - super(text ?? mentionName, key); - this.__mention = mentionName; - this.__displayName = displayName; - this.__userId = userId; - this.__userInternalId = userInternalId; - this.__userPublicId = userPublicId; - } - - exportJSON(): SerializedMentionNode { - return { - ...super.exportJSON(), - mentionName: this.__mention, - displayName: this.__displayName, - userId: this.__userId, - userInternalId: this.__userInternalId, - userPublicId: this.__userPublicId, - type: 'mention', - version: 1, - }; - } - - createDOM(config: EditorConfig): HTMLElement { - const dom = super.createDOM(config); - dom.style.cssText = mentionStyle; //class name css - dom.className = 'mention'; //create css - return dom; - } - - exportDOM(): DOMExportOutput { - const element = document.createElement('span'); - element.setAttribute('data-lexical-mention', 'true'); - element.textContent = this.__text; - return { element }; - } - - isSegmented(): false { - return false; - } - - static importDOM(): DOMConversionMap | null { - return { - span: (domNode: HTMLElement) => { - if (!domNode.hasAttribute('data-lexical-mention')) { - return null; - } - return { - conversion: convertMentionElement, - priority: 1, - }; - }, - }; - } - - isTextEntity(): true { - return true; - } - - isToken(): true { - return true; - } - - canInsertTextBefore(): boolean { - return false; - } - - canInsertTextAfter(): boolean { - return false; - } -} - -export function $createMentionNode({ - mentionName, - displayName, - userId, - userInternalId, - userPublicId, -}: { - mentionName: string; - displayName?: string; - userId?: string; - userInternalId?: string; - userPublicId?: string; -}): MentionNode { - const mentionNode = new MentionNode({ - mentionName: `@${mentionName}`, - displayName: displayName, - userId: userId, - userInternalId: userInternalId, - userPublicId: userPublicId, - }) - .setMode('segmented') - .toggleDirectionless(); - return mentionNode; -} - -export function $isMentionNode(node: LexicalNode | null | undefined): node is MentionNode { - return node instanceof MentionNode; -} diff --git a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css b/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css deleted file mode 100644 index 2167f73f..00000000 --- a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.communityMember__item { - width: 100%; - display: flex; - align-items: center; - padding: 0.5rem 1rem; - background-color: var(--asc-color-background-default); - color: var(--asc-color-base-default); -} - -.communityMember__item:focus { - background-color: var(--asc-color-base-shade4); -} - -.communityMember__avatar { - width: 2.5rem; - height: 2.5rem; -} - -.communityMember__displayName { - margin-left: 0.5rem; - font-size: 1rem; - display: block; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - color: var(--asc-color-base-default); -} diff --git a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx b/src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx deleted file mode 100644 index 2925cf29..00000000 --- a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import styles from './PostMentionUser.module.css'; -import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; -import { MentionTypeaheadOption } from './PostCommentMentionInput'; - -interface PostMentionUserProps { - isSelected: boolean; - onClick: () => void; - onMouseEnter: () => void; - option: MentionTypeaheadOption; -} - -export function PostMentionUser({ - isSelected, - onClick, - onMouseEnter, - option, -}: PostMentionUserProps) { - let className = 'item'; - if (isSelected) { - className += ' selected'; - } - - return ( -
-
-
- -
-

{option.user.displayName}

-
-
- ); -} diff --git a/src/v4/social/components/PostCommentMentionInput/index.ts b/src/v4/social/components/PostCommentMentionInput/index.ts deleted file mode 100644 index 485bb672..00000000 --- a/src/v4/social/components/PostCommentMentionInput/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PostCommentMentionInput } from './PostCommentMentionInput';