فهرست منبع

选视频弹窗支持翻页与滚动联动

刘立冬 4 ساعت پیش
والد
کامیت
aa6c38fddf

+ 98 - 13
src/views/publishContent/weCom/components/videoSelectModal/index.tsx

@@ -8,6 +8,7 @@ import {
 	Typography,
 	Typography,
 	Space,
 	Space,
 	Spin,
 	Spin,
+	Pagination,
 	message,
 	message,
 	Modal,
 	Modal,
 } from 'antd';
 } from 'antd';
@@ -20,6 +21,8 @@ import { WeComPlanType, WeVideoItem, VideoSearchPlanType } from '@src/views/publ
 
 
 const { Text, Paragraph } = Typography;
 const { Text, Paragraph } = Typography;
 
 
+type LoadMode = 'replace' | 'append' | 'jump';
+
 interface VideoSelectModalProps {
 interface VideoSelectModalProps {
 	planType: WeComPlanType;
 	planType: WeComPlanType;
 	visible: boolean;
 	visible: boolean;
@@ -50,20 +53,26 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 	const [videoListAll, setVideoListAll] = useState<WeVideoItem[]>([]);
 	const [videoListAll, setVideoListAll] = useState<WeVideoItem[]>([]);
 	const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
 	const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
 	const [playingVideo, setPlayingVideo] = useState<WeVideoItem | null>(null);
 	const [playingVideo, setPlayingVideo] = useState<WeVideoItem | null>(null);
+	const [viewingPage, setViewingPage] = useState(1);
+	const [pageAnchors, setPageAnchors] = useState<Map<number, number>>(new Map()); // videoId -> page
 	const observerRef = useRef<IntersectionObserver | null>(null);
 	const observerRef = useRef<IntersectionObserver | null>(null);
+	const pageObserverRef = useRef<IntersectionObserver | null>(null);
+	const passedPagesRef = useRef<Set<number>>(new Set());
+	const drawerBodyRef = useRef<HTMLElement | null>(null);
 	const loadingMoreRef = useRef(false);
 	const loadingMoreRef = useRef(false);
 	const hasMoreRef = useRef(true);
 	const hasMoreRef = useRef(true);
 	const loadingRef = useRef(false);
 	const loadingRef = useRef(false);
 	const currentPageRef = useRef(1);
 	const currentPageRef = useRef(1);
-	const getVideoListRef = useRef<(pageNum: number, append: boolean) => Promise<void>>();
+	const getVideoListRef = useRef<(pageNum: number, mode: LoadMode) => Promise<void>>();
 	const MAX_SELECTION = 3;
 	const MAX_SELECTION = 3;
 
 
 	useEffect(() => { hasMoreRef.current = hasMore; }, [hasMore]);
 	useEffect(() => { hasMoreRef.current = hasMore; }, [hasMore]);
 	useEffect(() => { loadingRef.current = loading; }, [loading]);
 	useEffect(() => { loadingRef.current = loading; }, [loading]);
 	useEffect(() => { currentPageRef.current = currentPage; }, [currentPage]);
 	useEffect(() => { currentPageRef.current = currentPage; }, [currentPage]);
 
 
-	const getVideoList = async (pageNum: number, append: boolean) => {
-		if (append) {
+	const getVideoList = async (pageNum: number, mode: LoadMode) => {
+		const isAppend = mode === 'append';
+		if (isAppend) {
 			if (loadingMoreRef.current) return;
 			if (loadingMoreRef.current) return;
 			loadingMoreRef.current = true;
 			loadingMoreRef.current = true;
 			setLoadingMore(true);
 			setLoadingMore(true);
@@ -84,7 +93,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 		const res = await http.post<VideoListResponse>(getVideoContentListApi, requestParams).catch(() => {
 		const res = await http.post<VideoListResponse>(getVideoContentListApi, requestParams).catch(() => {
 			message.error('获取视频列表失败');
 			message.error('获取视频列表失败');
 		}).finally(() => {
 		}).finally(() => {
-			if (append) {
+			if (isAppend) {
 				loadingMoreRef.current = false;
 				loadingMoreRef.current = false;
 				setLoadingMore(false);
 				setLoadingMore(false);
 			} else {
 			} else {
@@ -93,9 +102,15 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 		});
 		});
 		if (res && res.code === 0) {
 		if (res && res.code === 0) {
 			const mapped = (res.data.objs || []).map(video => ({ ...video, scene: 0 as 0 | 1 }));
 			const mapped = (res.data.objs || []).map(video => ({ ...video, scene: 0 as 0 | 1 }));
-			setVideoList(prev => append
-				? [...prev, ...mapped.filter(v => !prev.find(p => p.videoId === v.videoId))]
-				: mapped);
+			if (mode === 'append') {
+				setVideoList(prev => [...prev, ...mapped.filter(v => !prev.find(p => p.videoId === v.videoId))]);
+				if (mapped.length > 0) {
+					setPageAnchors(prev => new Map(prev).set(mapped[0].videoId, pageNum));
+				}
+			} else {
+				setVideoList(mapped);
+				setPageAnchors(mapped.length > 0 ? new Map([[mapped[0].videoId, pageNum]]) : new Map());
+			}
 			setVideoListAll(old => [...old, ...mapped.filter(v => !old.find(o => o.videoId === v.videoId))]);
 			setVideoListAll(old => [...old, ...mapped.filter(v => !old.find(o => o.videoId === v.videoId))]);
 			setTotal(res.data.totalSize);
 			setTotal(res.data.totalSize);
 			setHasMore(pageNum * PAGE_SIZE < res.data.totalSize);
 			setHasMore(pageNum * PAGE_SIZE < res.data.totalSize);
@@ -105,7 +120,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 	getVideoListRef.current = getVideoList;
 	getVideoListRef.current = getVideoList;
 
 
 	useEffect(() => {
 	useEffect(() => {
-		getVideoList(1, false);
+		getVideoList(1, 'replace');
 	}, []);
 	}, []);
 
 
 	useEffect(() => {
 	useEffect(() => {
@@ -120,15 +135,72 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 		if (!node) return;
 		if (!node) return;
 		observerRef.current = new IntersectionObserver((entries) => {
 		observerRef.current = new IntersectionObserver((entries) => {
 			if (entries[0].isIntersecting && hasMoreRef.current && !loadingMoreRef.current && !loadingRef.current) {
 			if (entries[0].isIntersecting && hasMoreRef.current && !loadingMoreRef.current && !loadingRef.current) {
-				getVideoListRef.current?.(currentPageRef.current + 1, true);
+				getVideoListRef.current?.(currentPageRef.current + 1, 'append');
 			}
 			}
 		}, { rootMargin: '2000px' });
 		}, { rootMargin: '2000px' });
 		observerRef.current.observe(node);
 		observerRef.current.observe(node);
 	}, []);
 	}, []);
 
 
+	const ensureObserver = useCallback((body: HTMLElement) => {
+		pageObserverRef.current?.disconnect();
+		pageObserverRef.current = new IntersectionObserver((entries) => {
+			entries.forEach(entry => {
+				const pageStr = (entry.target as HTMLElement).dataset.pageAnchor;
+				if (!pageStr) return;
+				const page = Number(pageStr);
+				const rootTop = entry.rootBounds?.top ?? 0;
+				if (entry.boundingClientRect.top < rootTop) {
+					passedPagesRef.current.add(page);
+				} else {
+					passedPagesRef.current.delete(page);
+				}
+			});
+			const passed = passedPagesRef.current;
+			const max = passed.size > 0 ? Math.max(...Array.from(passed)) : 1;
+			setViewingPage(prev => prev === max ? prev : max);
+		}, { root: body, threshold: [0, 1] });
+	}, []);
+
+	const captureDrawerBody = useCallback((node: HTMLDivElement | null) => {
+		if (!node) return;
+		const body = node.closest('.ant-drawer-body') as HTMLElement | null;
+		if (body) {
+			drawerBodyRef.current = body;
+			ensureObserver(body);
+		}
+	}, [ensureObserver]);
+
+	useEffect(() => {
+		const obs = pageObserverRef.current;
+		const body = drawerBodyRef.current;
+		if (!obs || !body) return;
+		obs.disconnect();
+		passedPagesRef.current.clear();
+		body.querySelectorAll<HTMLElement>('[data-page-anchor]').forEach(el => obs.observe(el));
+	}, [videoList, pageAnchors]);
+
+	useEffect(() => {
+		return () => {
+			pageObserverRef.current?.disconnect();
+		};
+	}, []);
+
+	const jumpToPage = (page: number) => {
+		setHasMore(true);
+		setVideoList([]);
+		setPageAnchors(new Map());
+		passedPagesRef.current.clear();
+		setViewingPage(page);
+		drawerBodyRef.current?.scrollTo({ top: 0 });
+		getVideoList(page, 'jump');
+	};
+
 	const handleSearch = () => {
 	const handleSearch = () => {
 		setHasMore(true);
 		setHasMore(true);
-		getVideoList(1, false);
+		setViewingPage(1);
+		setPageAnchors(new Map());
+		passedPagesRef.current.clear();
+		getVideoList(1, 'replace');
 	};
 	};
 
 
 	const handleSelectVideo = (videoId: number) => {
 	const handleSelectVideo = (videoId: number) => {
@@ -196,8 +268,19 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 				destroyOnClose
 				destroyOnClose
 				styles={{ footer: { textAlign: 'right', padding: '10px 24px' } }}
 				styles={{ footer: { textAlign: 'right', padding: '10px 24px' } }}
 				footer={
 				footer={
-					<div className="flex justify-between items-center">
-						<Text type="secondary">已加载 {videoList.length} / 共 {total} 条</Text>
+					<div className="flex justify-between items-center gap-2">
+						<div className="flex items-center gap-3">
+							<Text type="secondary">已加载 {videoList.length} / 共 {total} 条</Text>
+							{total > PAGE_SIZE && (
+								<Pagination
+									simple
+									current={viewingPage}
+									total={total}
+									pageSize={PAGE_SIZE}
+									onChange={jumpToPage}
+								/>
+							)}
+						</div>
 						<Space>
 						<Space>
 							<Text>已选 {selectedVideoIds.size} / {MAX_SELECTION} 条视频</Text>
 							<Text>已选 {selectedVideoIds.size} / {MAX_SELECTION} 条视频</Text>
 							<Button onClick={onClose}>取消</Button>
 							<Button onClick={onClose}>取消</Button>
@@ -233,13 +316,15 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 				</div>
 				</div>
 
 
 				<Spin spinning={loading}>
 				<Spin spinning={loading}>
-				<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
+				<div ref={captureDrawerBody} className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
 					{videoList.map((video) => {
 					{videoList.map((video) => {
 						const isSelected = selectedVideoIds.has(video.videoId);
 						const isSelected = selectedVideoIds.has(video.videoId);
 						const isDisabled = !isSelected && selectedVideoIds.size >= MAX_SELECTION;
 						const isDisabled = !isSelected && selectedVideoIds.size >= MAX_SELECTION;
+						const startsPage = pageAnchors.get(video.videoId);
 						return (
 						return (
 							<Card
 							<Card
 								key={video.videoId}
 								key={video.videoId}
+								{...(startsPage ? { 'data-page-anchor': startsPage } : {})}
 								className={`relative ${isDisabled ? 'opacity-50' : 'cursor-pointer'} ${isSelected ? 'border-blue-500 border-2 bg-blue-50 shadow-md' : 'hover:shadow-sm'}`}
 								className={`relative ${isDisabled ? 'opacity-50' : 'cursor-pointer'} ${isSelected ? 'border-blue-500 border-2 bg-blue-50 shadow-md' : 'hover:shadow-sm'}`}
 								styles={{ body: { padding: 0 } }}
 								styles={{ body: { padding: 0 } }}
 								onClick={() => !isDisabled && handleSelectVideo(video.videoId)}
 								onClick={() => !isDisabled && handleSelectVideo(video.videoId)}

+ 104 - 13
src/views/publishContent/weGZH/components/videoSelectModal/index.tsx

@@ -8,6 +8,7 @@ import {
 	Typography,
 	Typography,
 	Space,
 	Space,
 	Spin,
 	Spin,
+	Pagination,
 	message,
 	message,
 	Modal,
 	Modal,
 } from 'antd';
 } from 'antd';
@@ -22,6 +23,8 @@ import { VideoSearchPlanType } from '@src/views/publishContent/weCom/type';
 
 
 const { Paragraph, Text } = Typography;
 const { Paragraph, Text } = Typography;
 
 
+type LoadMode = 'replace' | 'append' | 'jump';
+
 interface VideoSelectModalProps {
 interface VideoSelectModalProps {
 	planType: GzhPlanType;
 	planType: GzhPlanType;
 	visible: boolean;
 	visible: boolean;
@@ -46,12 +49,17 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 	const [videoListAll, setVideoListAll] = useState<VideoItem[]>([]);
 	const [videoListAll, setVideoListAll] = useState<VideoItem[]>([]);
 	const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
 	const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
 	const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null);
 	const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null);
+	const [viewingPage, setViewingPage] = useState(1);
+	const [pageAnchors, setPageAnchors] = useState<Map<number, number>>(new Map()); // videoId -> page
 	const observerRef = useRef<IntersectionObserver | null>(null);
 	const observerRef = useRef<IntersectionObserver | null>(null);
+	const pageObserverRef = useRef<IntersectionObserver | null>(null);
+	const passedPagesRef = useRef<Set<number>>(new Set());
+	const drawerBodyRef = useRef<HTMLElement | null>(null);
 	const loadingMoreRef = useRef(false);
 	const loadingMoreRef = useRef(false);
 	const hasMoreRef = useRef(true);
 	const hasMoreRef = useRef(true);
 	const loadingRef = useRef(false);
 	const loadingRef = useRef(false);
 	const currentPageRef = useRef(1);
 	const currentPageRef = useRef(1);
-	const getVideoListRef = useRef<(pageNum: number, append: boolean) => Promise<void>>();
+	const getVideoListRef = useRef<(pageNum: number, mode: LoadMode) => Promise<void>>();
 	const MAX_SELECTION = 3;
 	const MAX_SELECTION = 3;
 
 
 	useEffect(() => { hasMoreRef.current = hasMore; }, [hasMore]);
 	useEffect(() => { hasMoreRef.current = hasMore; }, [hasMore]);
@@ -68,8 +76,9 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 		}
 		}
 	}
 	}
 
 
-	const getVideoList = async (pageNum: number, append: boolean) => {
-		if (append) {
+	const getVideoList = async (pageNum: number, mode: LoadMode) => {
+		const isAppend = mode === 'append';
+		if (isAppend) {
 			if (loadingMoreRef.current) return;
 			if (loadingMoreRef.current) return;
 			loadingMoreRef.current = true;
 			loadingMoreRef.current = true;
 			setLoadingMore(true);
 			setLoadingMore(true);
@@ -90,7 +99,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 		const res = await http.post<VideoListResponse>(getVideoContentListApi, requestParams).catch(() => {
 		const res = await http.post<VideoListResponse>(getVideoContentListApi, requestParams).catch(() => {
 			message.error('获取视频列表失败');
 			message.error('获取视频列表失败');
 		}).finally(() => {
 		}).finally(() => {
-			if (append) {
+			if (isAppend) {
 				loadingMoreRef.current = false;
 				loadingMoreRef.current = false;
 				setLoadingMore(false);
 				setLoadingMore(false);
 			} else {
 			} else {
@@ -99,9 +108,18 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 		});
 		});
 		if (res && res.code === 0) {
 		if (res && res.code === 0) {
 			const items = res.data.objs || [];
 			const items = res.data.objs || [];
-			setVideoList(prev => append
-				? [...prev, ...items.filter(v => !prev.find(p => p.videoId === v.videoId))]
-				: [...selectedVideos, ...items.filter(v => !selectedVideos.find(ov => ov.videoId === v.videoId))]);
+			if (mode === 'append') {
+				setVideoList(prev => [...prev, ...items.filter(v => !prev.find(p => p.videoId === v.videoId))]);
+				if (items.length > 0) {
+					setPageAnchors(prev => new Map(prev).set(items[0].videoId, pageNum));
+				}
+			} else if (mode === 'jump') {
+				setVideoList([...items]);
+				setPageAnchors(items.length > 0 ? new Map([[items[0].videoId, pageNum]]) : new Map());
+			} else {
+				setVideoList([...selectedVideos, ...items.filter(v => !selectedVideos.find(ov => ov.videoId === v.videoId))]);
+				setPageAnchors(items.length > 0 ? new Map([[items[0].videoId, pageNum]]) : new Map());
+			}
 			setVideoListAll(old => [...old, ...items.filter(v => !old.find(ov => ov.videoId === v.videoId))]);
 			setVideoListAll(old => [...old, ...items.filter(v => !old.find(ov => ov.videoId === v.videoId))]);
 			setTotal(res.data.totalSize);
 			setTotal(res.data.totalSize);
 			setHasMore(pageNum * PAGE_SIZE < res.data.totalSize);
 			setHasMore(pageNum * PAGE_SIZE < res.data.totalSize);
@@ -115,7 +133,10 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 			setVideoList(selectedVideos);
 			setVideoList(selectedVideos);
 			setVideoListAll(selectedVideos);
 			setVideoListAll(selectedVideos);
 			setHasMore(true);
 			setHasMore(true);
-			getVideoList(1, false);
+			setViewingPage(1);
+			setPageAnchors(new Map());
+			passedPagesRef.current.clear();
+			getVideoList(1, 'replace');
 		}
 		}
 	}, [visible]);
 	}, [visible]);
 
 
@@ -131,15 +152,72 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 		if (!node) return;
 		if (!node) return;
 		observerRef.current = new IntersectionObserver((entries) => {
 		observerRef.current = new IntersectionObserver((entries) => {
 			if (entries[0].isIntersecting && hasMoreRef.current && !loadingMoreRef.current && !loadingRef.current) {
 			if (entries[0].isIntersecting && hasMoreRef.current && !loadingMoreRef.current && !loadingRef.current) {
-				getVideoListRef.current?.(currentPageRef.current + 1, true);
+				getVideoListRef.current?.(currentPageRef.current + 1, 'append');
 			}
 			}
 		}, { rootMargin: '2000px' });
 		}, { rootMargin: '2000px' });
 		observerRef.current.observe(node);
 		observerRef.current.observe(node);
 	}, []);
 	}, []);
 
 
+	const ensureObserver = useCallback((body: HTMLElement) => {
+		pageObserverRef.current?.disconnect();
+		pageObserverRef.current = new IntersectionObserver((entries) => {
+			entries.forEach(entry => {
+				const pageStr = (entry.target as HTMLElement).dataset.pageAnchor;
+				if (!pageStr) return;
+				const page = Number(pageStr);
+				const rootTop = entry.rootBounds?.top ?? 0;
+				if (entry.boundingClientRect.top < rootTop) {
+					passedPagesRef.current.add(page);
+				} else {
+					passedPagesRef.current.delete(page);
+				}
+			});
+			const passed = passedPagesRef.current;
+			const max = passed.size > 0 ? Math.max(...Array.from(passed)) : 1;
+			setViewingPage(prev => prev === max ? prev : max);
+		}, { root: body, threshold: [0, 1] });
+	}, []);
+
+	const captureDrawerBody = useCallback((node: HTMLDivElement | null) => {
+		if (!node) return;
+		const body = node.closest('.ant-drawer-body') as HTMLElement | null;
+		if (body) {
+			drawerBodyRef.current = body;
+			ensureObserver(body);
+		}
+	}, [ensureObserver]);
+
+	useEffect(() => {
+		const obs = pageObserverRef.current;
+		const body = drawerBodyRef.current;
+		if (!obs || !body) return;
+		obs.disconnect();
+		passedPagesRef.current.clear();
+		body.querySelectorAll<HTMLElement>('[data-page-anchor]').forEach(el => obs.observe(el));
+	}, [videoList, pageAnchors]);
+
+	useEffect(() => {
+		return () => {
+			pageObserverRef.current?.disconnect();
+		};
+	}, []);
+
+	const jumpToPage = (page: number) => {
+		setHasMore(true);
+		setVideoList([]);
+		setPageAnchors(new Map());
+		passedPagesRef.current.clear();
+		setViewingPage(page);
+		drawerBodyRef.current?.scrollTo({ top: 0 });
+		getVideoList(page, 'jump');
+	};
+
 	const handleSearch = () => {
 	const handleSearch = () => {
 		setHasMore(true);
 		setHasMore(true);
-		getVideoList(1, false);
+		setViewingPage(1);
+		setPageAnchors(new Map());
+		passedPagesRef.current.clear();
+		getVideoList(1, 'replace');
 	};
 	};
 
 
 	const handleSelectVideo = (videoId: number) => {
 	const handleSelectVideo = (videoId: number) => {
@@ -182,8 +260,19 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 				placement="right"
 				placement="right"
 				styles={{ footer: { textAlign: 'right', padding: '10px 24px' } }}
 				styles={{ footer: { textAlign: 'right', padding: '10px 24px' } }}
 				footer={
 				footer={
-					<div className="flex justify-between items-center">
-						<Text type="secondary">已加载 {videoList.length} / 共 {total} 条</Text>
+					<div className="flex justify-between items-center gap-2">
+						<div className="flex items-center gap-3">
+							<Text type="secondary">已加载 {videoList.length} / 共 {total} 条</Text>
+							{total > PAGE_SIZE && (
+								<Pagination
+									simple
+									current={viewingPage}
+									total={total}
+									pageSize={PAGE_SIZE}
+									onChange={jumpToPage}
+								/>
+							)}
+						</div>
 						<Space>
 						<Space>
 							<Text>已选 {selectedVideoIds.size} / {MAX_SELECTION} 条视频</Text>
 							<Text>已选 {selectedVideoIds.size} / {MAX_SELECTION} 条视频</Text>
 							<Button onClick={onClose}>取消</Button>
 							<Button onClick={onClose}>取消</Button>
@@ -219,13 +308,15 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 				</div>
 				</div>
 
 
 				<Spin spinning={loading}>
 				<Spin spinning={loading}>
-				<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
+				<div ref={captureDrawerBody} className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
 					{videoList.map((video) => {
 					{videoList.map((video) => {
 						const isSelected = selectedVideoIds.has(video.videoId);
 						const isSelected = selectedVideoIds.has(video.videoId);
 						const isDisabled = !isSelected && selectedVideoIds.size >= MAX_SELECTION;
 						const isDisabled = !isSelected && selectedVideoIds.size >= MAX_SELECTION;
+						const startsPage = pageAnchors.get(video.videoId);
 						return (
 						return (
 							<Card
 							<Card
 								key={video.videoId}
 								key={video.videoId}
+								{...(startsPage ? { 'data-page-anchor': startsPage } : {})}
 								className={`relative ${isDisabled ? 'opacity-50' : 'cursor-pointer'} ${isSelected ? 'border-blue-500 border-2' : ''}`}
 								className={`relative ${isDisabled ? 'opacity-50' : 'cursor-pointer'} ${isSelected ? 'border-blue-500 border-2' : ''}`}
 								styles={{ body: { padding: 0 } }}
 								styles={{ body: { padding: 0 } }}
 								onClick={() => !isDisabled && handleSelectVideo(video.videoId)}
 								onClick={() => !isDisabled && handleSelectVideo(video.videoId)}