diff --git a/src/app/watch/Channel.tsx b/src/app/watch/Channel.tsx deleted file mode 100644 index 7b278fd..0000000 --- a/src/app/watch/Channel.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import NextLink from "next/link"; - -import { Avatar } from "@nextui-org/avatar"; -import { Link } from "@nextui-org/link"; - -import { Author } from "@/client/typings/author"; -import formatBigNumber from "@/utils/formatBigNumber"; -import { channelUrl } from "@/utils/urls"; - -import { Component } from "@/typings/component"; - -export const Channel: Component<{ - data: Author; -}> = ({ data }) => { - const url = data?.id ? channelUrl(data.id) : undefined; - - return ( - -
- -
-

{data.name}

- {data.subscribers && ( -

- {formatBigNumber(data.subscribers)} subscribers -

- )} -
-
- - ); -}; diff --git a/src/app/watch/Comments.tsx b/src/app/watch/Comments.tsx new file mode 100644 index 0000000..0cd12e1 --- /dev/null +++ b/src/app/watch/Comments.tsx @@ -0,0 +1,227 @@ +import { useQuery } from "@tanstack/react-query"; +import NextLink from "next/link"; +import { FC, useMemo, useState } from "react"; +import { + FiHeart as HeartIcon, + FiThumbsUp as LikeIcon, + FiLock as PinnedIcon, + FiCornerDownRight as ShowRepliesIcon, + FiSlash as SlashIcon, + FiCheck as UploaderIcon +} from "react-icons/fi"; + +import { Avatar } from "@nextui-org/avatar"; +import { Button } from "@nextui-org/button"; +import { Chip } from "@nextui-org/chip"; +import { Divider } from "@nextui-org/divider"; +import { Link } from "@nextui-org/link"; +import { CircularProgress } from "@nextui-org/progress"; +import { Tooltip } from "@nextui-org/tooltip"; + +import { useClient } from "@/hooks/useClient"; + +import { Author } from "@/client/typings/author"; +import { + Comment as CommentProps, + Comments as CommentsProps +} from "@/client/typings/comment"; +import formatBigNumber from "@/utils/formatBigNumber"; +import formatUploadedTime from "@/utils/formatUploadedTime"; +import { highlight } from "@/utils/highlight"; +import { channelUrl } from "@/utils/urls"; + +import { HighlightRenderer } from "./HighlightRenderer"; + +const Comment: FC<{ + data: CommentProps; + videoUploader: Author; + videoId: string; +}> = ({ data, videoUploader, videoId }) => { + const message = useMemo(() => highlight(data.message), [data.message]); + + const client = useClient(); + + const [showReplies, setShowReplies] = useState(false); + + const { + data: replies, + error: repliesError, + refetch: refetchReplies, + isLoading: isLoadingReplies + } = useQuery({ + queryKey: ["replies", videoId, data.repliesToken], + queryFn: () => { + return client.getComments(videoId, data.repliesToken); + }, + enabled: showReplies && !!data.repliesToken + }); + + const userUrl = data.author.id ? channelUrl(data.author.id) : "#"; + + return ( +
+
+ + + +
+ +
+
+ +

+ {data.author.name} +

+ + {data.author.id === videoUploader.id && ( + } + color="primary" + > + Uploader + + )} + {data.pinned && ( + } + color="primary" + > + Pinned + + )} +
+ +

+ +

+ +
+
+ +

{formatBigNumber(data.likes)} likes

+
+ +
+

{formatUploadedTime(data.written)}

+
+ + {data.videoUploaderLiked && ( +
+ +

+ +

+
+
+ )} + + {data.videoUploaderReplied && ( +
+ +
+ )} + + {data.edited && ( +

(edited)

+ )} + + {data.repliesToken && ( + + )} +
+ + {showReplies && ( +
+ +
+ )} +
+
+ ); +}; + +export const Comments: FC<{ + data?: CommentsProps; + isLoading: boolean; + error: Error | null; + refetch: () => void; + videoUploader: Author; + videoId: string; +}> = ({ data, isLoading, error, refetch, videoUploader, videoId }) => { + return ( + <> + {data && ( + <> +

+ {data.count && formatBigNumber(data.count)} Comments +

+ + + +
+ {data.enabled && ( + <> + {data.data.map((comment) => ( + + ))} + + )} + {!data.enabled && ( +
+ +

Comments on this video are disabled

+
+ )} +
+ + )} + {!data && isLoading && ( +
+ +
+ )} + {error && ( +
+

Failed to load comments:

+ {error.toString()} +
+ +
+
+ )} + + ); +}; diff --git a/src/app/watch/Description.tsx b/src/app/watch/Description.tsx index cd81a7b..b046948 100644 --- a/src/app/watch/Description.tsx +++ b/src/app/watch/Description.tsx @@ -1,16 +1,16 @@ import sanitizeHtml from "sanitize-html"; -import { Fragment, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { FiChevronUp as CollapseIcon, FiChevronDown as ExpandIcon } from "react-icons/fi"; import { Button } from "@nextui-org/button"; -import { Link } from "@nextui-org/link"; -import formatDuration from "@/utils/formatDuration"; -import { highlight, ItemType } from "@/utils/highlight"; +import { highlight } from "@/utils/highlight"; + +import { HighlightRenderer } from "./HighlightRenderer"; import { Component } from "@/typings/component"; @@ -46,29 +46,7 @@ export const Description: Component<{ data: string }> = ({ data }) => { return (

- {description.map((item) => { - switch (item.type) { - case ItemType.Tokens: - return {item.content}; - - case ItemType.Link: - return ( - - {item.text ?? item.href} - - ); - - case ItemType.Timestamp: - return ( - - {formatDuration(item.duration * 1000)} - - ); - - case ItemType.Linebreak: - return
; - } - })} +