feat: Add GitHub link to navbar and implement direct video playback links for secret page.

This commit is contained in:
kuekhaoyang
2025-11-28 20:42:10 +08:00
parent c488642177
commit 8d3ac02602
7 changed files with 45 additions and 7 deletions
+1
View File
@@ -49,6 +49,7 @@ function SecretHomePage() {
results={results}
availableSources={availableSources}
loading={loading}
openDirectLinks={true}
/>
)}
+3 -2
View File
@@ -11,9 +11,10 @@ interface SearchResultsProps {
results: Video[];
availableSources: SourceBadge[];
loading: boolean;
openDirectLinks?: boolean;
}
export function SearchResults({ results, availableSources, loading }: SearchResultsProps) {
export function SearchResults({ results, availableSources, loading, openDirectLinks = false }: SearchResultsProps) {
// Source badges hook - filters by video source
const {
selectedSources,
@@ -61,7 +62,7 @@ export function SearchResults({ results, availableSources, loading }: SearchResu
)}
{/* Display filtered videos (both source and type filters applied) */}
<VideoGrid videos={finalFilteredVideos} />
<VideoGrid videos={finalFilteredVideos} openDirectLinks={openDirectLinks} />
</div>
);
}
+10
View File
@@ -1,6 +1,7 @@
import Link from 'next/link';
import Image from 'next/image';
import { ThemeSwitcher } from '@/components/ThemeSwitcher';
import { Icons } from '@/components/ui/Icon';
interface NavbarProps {
onReset: () => void;
@@ -38,6 +39,15 @@ export function Navbar({ onReset }: NavbarProps) {
</Link>
<div className="flex items-center gap-3">
<a
href="https://github.com/KuekHaoYang/KVideo"
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 flex items-center justify-center rounded-[var(--radius-full)] bg-[var(--glass-bg)] border border-[var(--glass-border)] text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)] transition-all duration-200 cursor-pointer"
aria-label="GitHub"
>
<Icons.Github size={20} />
</a>
<Link
href="/settings"
className="w-10 h-10 flex items-center justify-center rounded-[var(--radius-full)] bg-[var(--glass-bg)] border border-[var(--glass-border)] text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)] transition-all duration-200 cursor-pointer"
+2 -2
View File
@@ -15,7 +15,7 @@ interface VideoCardProps {
videoUrl: string;
cardId: string;
isActive: boolean;
onCardClick: (e: React.MouseEvent, cardId: string, videoUrl: string) => void;
onCardClick: (e: React.MouseEvent, cardId: string, videoUrl: string, video: Video) => void;
}
export const VideoCard = memo<VideoCardProps>(({
@@ -35,7 +35,7 @@ export const VideoCard = memo<VideoCardProps>(({
<Link
key={cardId}
href={videoUrl}
onClick={(e) => onCardClick(e, cardId, videoUrl)}
onClick={(e) => onCardClick(e, cardId, videoUrl, video)}
role="listitem"
aria-label={`${video.vod_name}${video.vod_remarks ? ` - ${video.vod_remarks}` : ''}`}
prefetch={false}
+22 -3
View File
@@ -8,9 +8,10 @@ import { Video } from '@/lib/types';
interface VideoGridProps {
videos: Video[];
className?: string;
openDirectLinks?: boolean;
}
export const VideoGrid = memo(function VideoGrid({ videos, className = '' }: VideoGridProps) {
export const VideoGrid = memo(function VideoGrid({ videos, className = '', openDirectLinks = false }: VideoGridProps) {
const [activeCardId, setActiveCardId] = useState<string | null>(null);
const [visibleCount, setVisibleCount] = useState(24);
const gridRef = useRef<HTMLDivElement>(null);
@@ -36,7 +37,25 @@ export const VideoGrid = memo(function VideoGrid({ videos, className = '' }: Vid
}, []);
// Memoize the click handler to prevent re-renders
const handleCardClick = useCallback((e: React.MouseEvent, videoId: string, videoUrl: string) => {
const handleCardClick = useCallback((e: React.MouseEvent, videoId: string, videoUrl: string, video?: Video) => {
if (openDirectLinks && video?.vod_play_url) {
// For secret page: extract first video URL and open directly
e.preventDefault();
const playUrls = video.vod_play_url.split('$$$');
if (playUrls.length > 0) {
const firstPlaylist = playUrls[0];
const segments = firstPlaylist.split('#');
if (segments.length > 0) {
const urls = segments[0].split('$');
if (urls.length > 1) {
window.open(urls[1], '_blank');
return;
}
}
}
return;
}
// Check if it's a mobile device
const isMobile = window.innerWidth < 1024; // lg breakpoint
@@ -52,7 +71,7 @@ export const VideoGrid = memo(function VideoGrid({ videos, className = '' }: Vid
}
}
// On desktop, let the Link work normally
}, [activeCardId]);
}, [activeCardId, openDirectLinks]);
// Memoize video items to prevent unnecessary re-computations
const videoItems = useMemo(() => {
+6
View File
@@ -121,4 +121,10 @@ export const UtilityIcons = {
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
),
Github: ({ className = "", size = 24 }: IconProps) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" />
</svg>
),
};
+1
View File
@@ -26,6 +26,7 @@ export interface VideoItem {
vod_actor?: string;
vod_director?: string;
vod_content?: string;
vod_play_url?: string;
source: string;
latency?: number; // Response time in milliseconds
}