Skip to content

Commit

Permalink
add comments to watch page.
Browse files Browse the repository at this point in the history
  • Loading branch information
sphinxrave committed Dec 30, 2024
1 parent 320d766 commit 1155f49
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 10 deletions.
2 changes: 1 addition & 1 deletion packages/react/src/components/player/PlayerStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { localeAtom } from "@/store/i18n";
import { useAtomValue } from "jotai";
import { useTranslation } from "react-i18next";

export function PlayerStats({
export function VideoStats({
type,
status,
topic_id,
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/components/video/VideoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export function VideoCard({
{(size == "lg" || size == "md") && video.channel && (
<Link
to={`/channel/${video.channel.id}`}
dataBehavior="channelLink"
databehavior="channelLink"
className="shrink-0"
onClick={(e) =>
onClick ? onClick("channel", video, e) : goToVideoClickHandler(e)
Expand Down Expand Up @@ -258,7 +258,7 @@ export function VideoCard({
{video.channel && (
<Link
className={videoCardClasses.channelLink}
dataBehavior="channelLink"
databehavior="channelLink"
to={`/channel/${video.channel.id}`}
onClick={(e) => onClick && onClick("channel", video, e)}
>
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/video/VideoCard.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function useDefaultVideoCardClickHandler(
// thumbnail, title, and channel image / channel name are all React Router links
// in contrast, the rest of the video card is not a link
const isChannelClick =
isLinkClick?.getAttribute("dataBehavior") === "channelLink";
isLinkClick?.getAttribute("databehavior") === "channelLink";

if (isChannelClick) {
return; // do not select, skip the entire handler
Expand Down
124 changes: 124 additions & 0 deletions packages/react/src/components/watch/Comments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { useState, useMemo, useRef, useEffect } from "react";
import { ExternalLink } from "lucide-react";
import { videoPlayerRefAtomFamily } from "@/store/player";
import { useAtomValue } from "jotai";

interface CommentData {
message: string;
comment_key: string;
}

interface TruncatedTextProps {
text: string;
}

const TruncatedText = ({ text }: TruncatedTextProps) => {
const contentRef = useRef<HTMLDivElement>(null);
const [isClamped, setClamped] = useState(false);

useEffect(() => {
if (contentRef && contentRef.current) {
setClamped(
contentRef.current.scrollHeight > contentRef.current.clientHeight + 2, // so i'm not too sure why but there's a 2px offset on my computer between the client height and the scrollheight.
);
}
}, []);

const [expanded, setExpanded] = useState(false);
return (
<div>
<div
ref={contentRef}
className={`whitespace-pre-wrap break-words ${!expanded ? `line-clamp-5` : ""}`}
>
<span dangerouslySetInnerHTML={{ __html: text }} />
</div>
{isClamped && (
<button
onClick={() => setExpanded(!expanded)}
className="mt-1 text-xs text-base-11 hover:text-base-12"
>
{expanded ? "Show less" : "Read more"}
</button>
)}
</div>
);
};

const parseTimestamps = (message: string, videoId: string) => {
const decoder = document.createElement("div");
decoder.innerHTML = message;
const sanitizedText = decoder.textContent || "";

return sanitizedText.replace(
/(?:([0-5]?[0-9]):)?([0-5]?[0-9]):([0-5][0-9])/gm,
(match, hr, min, sec) => {
const seconds = Number(hr ?? 0) * 3600 + Number(min) * 60 + Number(sec);
return `<a href="/watch/${videoId}?t=${seconds}" data-time="${seconds}" class="inline-block rounded px-1 text-primary-11 hover:bg-primary hover:text-primary-12">${match}</a>`;
},
);
};

const Comment = ({
comment,
videoId,
}: {
comment: CommentData;
videoId: string;
}) => {
const parsedMessage = useMemo(
() => parseTimestamps(comment.message, videoId),
[comment.message, videoId],
);

return (
<div className="group relative my-3 min-h-0 border-l-2 border-base-6 px-4 py-1">
<TruncatedText text={parsedMessage} />
<a
href={`https://www.youtube.com/watch?v=${videoId}&lc=${comment.comment_key}`}
target="_blank"
rel="noopener noreferrer"
className="absolute right-0 top-0 hidden text-base-11 hover:text-base-12 group-hover:block"
>
<ExternalLink className="h-4 w-4" />
</a>
</div>
);
};

export const Comments = ({ video }: { video?: PlaceholderVideo }) => {
const playerRefAtom = videoPlayerRefAtomFamily(
video?.id || "__nonexistent__",
);
const player = useAtomValue(playerRefAtom);
const goToTimestampHandler = (e: React.MouseEvent<HTMLAnchorElement>) => {
if (e.target instanceof HTMLAnchorElement) {
if (e.target.dataset.time) {
console.log(e.target.dataset.time, player);
player?.seekTo(+e.target.dataset.time, "seconds");
player?.getInternalPlayer().playVideo?.();
e.preventDefault();
}
}
};

if (!video?.comments?.length) return null;
return (
<div
className="space-y-2"
onClick={
goToTimestampHandler as unknown as React.MouseEventHandler<HTMLDivElement>
}
>
{video.comments.map((comment) => (
<Comment
key={comment.comment_key}
comment={comment}
videoId={video.id}
/>
))}
</div>
);
};

export default Comments;
2 changes: 1 addition & 1 deletion packages/react/src/react-router-dom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import "react-router-dom";

declare module "react-router-dom" {
export interface LinkProps {
dataBehavior?: string; // Add your custom attribute here
databehavior?: string; // Add your custom attribute here
}
}
17 changes: 17 additions & 0 deletions packages/react/src/routes/home/ClipsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useParams } from "react-router-dom";
import { useVideoCardSizes } from "@/store/video";
import PullToRefresh from "@/components/layout/PullToRefresh";
import { useVideoFilter } from "@/hooks/useVideoFilter";
import { ClipLanguageSelector } from "@/components/language/ClipLanguageSelector";

export function ClipsTab() {
const { org } = useParams();
Expand Down Expand Up @@ -40,6 +41,22 @@ export function ClipsTab() {
"org",
);

if (clipLangs.length === 0)
return (
<div className="gap-4 px-4 py-2 @container md:px-8">
<div>No language selected</div>

<ClipLanguageSelector />
</div>
);

if (!filteredClips.length)
return (
<div className="gap-4 px-4 py-2 @container md:px-8">
<div>No clips for languages: {clipLangs.join(", ")}</div>
</div>
);

return (
<PullToRefresh onRefresh={refetch}>
<MainVideoListing
Expand Down
5 changes: 2 additions & 3 deletions packages/react/src/routes/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,8 @@ function StickyTabsList({
{/* Optional Control Buttons */}
{tab === "clips" && <ClipLanguageSelector />}
{tab !== "members" && <CardSizeToggle />}
{(user?.role === "admin" || user?.role === "editor") && (
<EditingStateToggle />
)}
{(user?.role === "admin" || user?.role === "editor") &&
tab != "members" && <EditingStateToggle />}
</TabsList>
);
}
Expand Down
6 changes: 4 additions & 2 deletions packages/react/src/routes/watch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { Controlbar } from "@/components/player/Controlbar";
import { Mentions } from "@/components/player/MentionsCard";
import { PlayerDescription as Description } from "@/components/player/PlayerDescription";
import { PlayerRecommendations as Recommendations } from "@/components/player/PlayerRecommendations";
import { PlayerStats } from "@/components/player/PlayerStats";
import { VideoStats } from "@/components/player/PlayerStats";
import { QueueList } from "@/components/player/QueueList";
import Comments from "@/components/watch/Comments";
import { useIsLgAndUp } from "@/hooks/useBreakpoint";
import { headerHiddenAtom } from "@/hooks/useFrame";
import { cn, idToVideoURL } from "@/lib/utils";
Expand Down Expand Up @@ -96,6 +97,7 @@ const UnderVideoInfo = ({
<div className="flex @screen-lg:hidden">
<Recommendations {...currentVideo} />
</div>
<Comments video={currentVideo} />
</div>
);
};
Expand Down Expand Up @@ -211,7 +213,7 @@ export function Watch() {

<div className={titleSectionClasses}>
<h2 className="text-xl font-bold">{currentVideo?.title}</h2>
{currentVideo && <PlayerStats {...currentVideo} />}
{currentVideo && <VideoStats {...currentVideo} />}
</div>

<UnderVideoInfo currentVideo={currentVideo} channel={channel} />
Expand Down

0 comments on commit 1155f49

Please sign in to comment.