diff --git a/components/home/TagInput.tsx b/components/home/TagInput.tsx
new file mode 100644
index 0000000..50fa542
--- /dev/null
+++ b/components/home/TagInput.tsx
@@ -0,0 +1,34 @@
+'use client';
+
+import { Icons } from '@/components/ui/Icon';
+
+interface TagInputProps {
+ newTagInput: string;
+ onNewTagInputChange: (value: string) => void;
+ onAddTag: () => void;
+}
+
+export function TagInput({
+ newTagInput,
+ onNewTagInputChange,
+ onAddTag,
+}: TagInputProps) {
+ return (
+
+ onNewTagInputChange(e.target.value)}
+ onKeyDown={(e) => e.key === 'Enter' && onAddTag()}
+ placeholder="添加自定义标签..."
+ className="flex-1 bg-[var(--glass-bg)] backdrop-blur-xl border border-[var(--glass-border)] text-[var(--text-color)] px-4 py-2 focus:outline-none focus:border-[var(--accent-color)] transition-colors rounded-[var(--radius-2xl)]"
+ />
+
+
+ );
+}
diff --git a/components/home/TagList.tsx b/components/home/TagList.tsx
new file mode 100644
index 0000000..0209dca
--- /dev/null
+++ b/components/home/TagList.tsx
@@ -0,0 +1,118 @@
+'use client';
+
+import {
+ DndContext,
+ closestCenter,
+ KeyboardSensor,
+ PointerSensor,
+ useSensor,
+ useSensors,
+ DragEndEvent,
+ DragStartEvent,
+ DragOverlay,
+} from '@dnd-kit/core';
+import {
+ SortableContext,
+ sortableKeyboardCoordinates,
+ horizontalListSortingStrategy,
+} from '@dnd-kit/sortable';
+import { SortableTag, Tag } from './SortableTag';
+import { useState, useRef, useEffect } from 'react';
+
+interface TagListProps {
+ tags: Tag[];
+ selectedTag: string;
+ showTagManager: boolean;
+ justAddedTag: boolean;
+ onTagSelect: (tagId: string) => void;
+ onTagDelete: (tagId: string) => void;
+ onDragEnd: (event: DragEndEvent) => void;
+ onJustAddedTagHandled: () => void;
+}
+
+export function TagList({
+ tags,
+ selectedTag,
+ showTagManager,
+ justAddedTag,
+ onTagSelect,
+ onTagDelete,
+ onDragEnd,
+ onJustAddedTagHandled,
+}: TagListProps) {
+ const scrollContainerRef = useRef(null);
+ const [activeId, setActiveId] = useState(null);
+
+ const sensors = useSensors(
+ useSensor(PointerSensor, {
+ activationConstraint: {
+ distance: 8,
+ },
+ }),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ })
+ );
+
+ // Auto-scroll to end when new tag is added
+ useEffect(() => {
+ if (justAddedTag && scrollContainerRef.current) {
+ scrollContainerRef.current.scrollTo({
+ left: scrollContainerRef.current.scrollWidth,
+ behavior: 'smooth',
+ });
+ onJustAddedTagHandled();
+ }
+ }, [justAddedTag, onJustAddedTagHandled]);
+
+ const handleDragStart = (event: DragStartEvent) => {
+ setActiveId(event.active.id as string);
+ };
+
+ const handleDragEnd = (event: DragEndEvent) => {
+ setActiveId(null);
+ onDragEnd(event);
+ };
+
+ const activeTag = tags.find((t) => t.id === activeId);
+
+ return (
+
+
+ t.id)}
+ strategy={horizontalListSortingStrategy}
+ >
+ {tags.map((tag) => (
+
+ ))}
+
+
+
+
+ {activeId && activeTag ? (
+
+
+
+ ) : null}
+
+
+ );
+}
diff --git a/components/home/TagManager.tsx b/components/home/TagManager.tsx
index 4b90220..27eabee 100644
--- a/components/home/TagManager.tsx
+++ b/components/home/TagManager.tsx
@@ -1,24 +1,8 @@
-'use client';
-
-import { useEffect, useRef, useState } from 'react';
import { Icons } from '@/components/ui/Icon';
-import {
- DndContext,
- closestCenter,
- KeyboardSensor,
- PointerSensor,
- useSensor,
- useSensors,
- DragEndEvent,
- DragStartEvent,
- DragOverlay,
-} from '@dnd-kit/core';
-import {
- SortableContext,
- sortableKeyboardCoordinates,
- horizontalListSortingStrategy,
-} from '@dnd-kit/sortable';
-import { SortableTag, Tag } from './SortableTag';
+import { DragEndEvent } from '@dnd-kit/core';
+import { TagInput } from './TagInput';
+import { TagList } from './TagList';
+import { Tag } from './SortableTag';
interface TagManagerProps {
tags: Tag[];
@@ -51,42 +35,6 @@ export function TagManager({
onDragEnd,
onJustAddedTagHandled,
}: TagManagerProps) {
- const scrollContainerRef = useRef(null);
- const [activeId, setActiveId] = useState(null);
-
- const sensors = useSensors(
- useSensor(PointerSensor, {
- activationConstraint: {
- distance: 8,
- },
- }),
- useSensor(KeyboardSensor, {
- coordinateGetter: sortableKeyboardCoordinates,
- })
- );
-
- // Auto-scroll to end when new tag is added
- useEffect(() => {
- if (justAddedTag && scrollContainerRef.current) {
- scrollContainerRef.current.scrollTo({
- left: scrollContainerRef.current.scrollWidth,
- behavior: 'smooth',
- });
- onJustAddedTagHandled();
- }
- }, [justAddedTag, onJustAddedTagHandled]);
-
- const handleDragStart = (event: DragStartEvent) => {
- setActiveId(event.active.id as string);
- };
-
- const handleDragEnd = (event: DragEndEvent) => {
- setActiveId(null);
- onDragEnd(event);
- };
-
- const activeTag = tags.find((t) => t.id === activeId);
-
return (
<>
{/* Management Controls */}
@@ -111,62 +59,24 @@ export function TagManager({
{/* Add Custom Tag */}
{showTagManager && (
-
- onNewTagInputChange(e.target.value)}
- onKeyDown={(e) => e.key === 'Enter' && onAddTag()}
- placeholder="添加自定义标签..."
- className="flex-1 bg-[var(--glass-bg)] backdrop-blur-xl border border-[var(--glass-border)] text-[var(--text-color)] px-4 py-2 focus:outline-none focus:border-[var(--accent-color)] transition-colors rounded-[var(--radius-2xl)]"
- />
-
-
+
)}
{/* Tag Filter */}
-
-
- t.id)}
- strategy={horizontalListSortingStrategy}
- >
- {tags.map((tag) => (
-
- ))}
-
-
-
-
- {activeId && activeTag ? (
-
-
-
- ) : null}
-
-
+
>
);
}
diff --git a/components/player/VideoPlayer.tsx b/components/player/VideoPlayer.tsx
index 6db60f0..e5ae6e9 100644
--- a/components/player/VideoPlayer.tsx
+++ b/components/player/VideoPlayer.tsx
@@ -3,10 +3,10 @@
import { useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { Card } from '@/components/ui/Card';
-import { Button } from '@/components/ui/Button';
-import { Icons } from '@/components/ui/Icon';
import { useHistoryStore } from '@/lib/store/history-store';
import { CustomVideoPlayer } from './CustomVideoPlayer';
+import { VideoPlayerError } from './VideoPlayerError';
+import { VideoPlayerEmpty } from './VideoPlayerEmpty';
interface VideoPlayerProps {
playUrl: string;
@@ -111,53 +111,19 @@ export function VideoPlayer({ playUrl, videoId, currentEpisode, onBack }: VideoP
: playUrl;
if (!playUrl) {
- return (
-
-
-
- );
+ return ;
}
return (
{videoError ? (
-
-
-
-
播放失败
-
{videoError}
-
-
- {retryCount < MAX_MANUAL_RETRIES && (
-
- )}
-
-
-
+
) : (
+
+
+ );
+}
diff --git a/components/player/VideoPlayerError.tsx b/components/player/VideoPlayerError.tsx
new file mode 100644
index 0000000..430d9ee
--- /dev/null
+++ b/components/player/VideoPlayerError.tsx
@@ -0,0 +1,55 @@
+'use client';
+
+import { Button } from '@/components/ui/Button';
+import { Icons } from '@/components/ui/Icon';
+
+interface VideoPlayerErrorProps {
+ error: string;
+ onBack: () => void;
+ onRetry: () => void;
+ retryCount: number;
+ maxRetries: number;
+}
+
+export function VideoPlayerError({
+ error,
+ onBack,
+ onRetry,
+ retryCount,
+ maxRetries,
+}: VideoPlayerErrorProps) {
+ return (
+
+
+
+
播放失败
+
{error}
+
+
+ {retryCount < maxRetries && (
+
+ )}
+
+
+
+ );
+}
diff --git a/components/player/hooks/useDesktopPlayerLogic.ts b/components/player/hooks/useDesktopPlayerLogic.ts
index 3d256d9..94f15cd 100644
--- a/components/player/hooks/useDesktopPlayerLogic.ts
+++ b/components/player/hooks/useDesktopPlayerLogic.ts
@@ -7,6 +7,7 @@ import { useControlsVisibility } from './desktop/useControlsVisibility';
import { useUtilities } from './desktop/useUtilities';
import { useDesktopShortcuts } from './desktop/useDesktopShortcuts';
import { useDesktopPlayerState } from './useDesktopPlayerState';
+import { getCopyUrl } from '../utils/urlUtils';
type DesktopPlayerState = ReturnType;
@@ -132,27 +133,7 @@ export function useDesktopPlayerLogic({
skipBackward: skipControls.skipBackward,
changePlaybackSpeed: playbackControls.changePlaybackSpeed,
handleCopyLink: (type: 'original' | 'proxy' = 'original') => {
- let urlToCopy = src;
-
- // If user wants original link, strip proxy prefix if present
- if (type === 'original') {
- if (urlToCopy.includes('/api/proxy?url=')) {
- const match = urlToCopy.match(/url=([^&]*)/);
- if (match && match[1]) {
- urlToCopy = decodeURIComponent(match[1]);
- }
- }
- }
- // If user wants proxy link, ensure it has proxy prefix
- else if (type === 'proxy') {
- if (!urlToCopy.includes('/api/proxy?url=')) {
- urlToCopy = `${window.location.origin}/api/proxy?url=${encodeURIComponent(urlToCopy)}`;
- } else if (urlToCopy.startsWith('/')) {
- // Ensure absolute URL for copy
- urlToCopy = `${window.location.origin}${urlToCopy}`;
- }
- }
-
+ const urlToCopy = getCopyUrl(src, type);
utilities.handleCopyLink(urlToCopy);
},
startSpeedMenuTimeout: controlsVisibility.startSpeedMenuTimeout,
diff --git a/components/player/utils/urlUtils.ts b/components/player/utils/urlUtils.ts
new file mode 100644
index 0000000..c40b256
--- /dev/null
+++ b/components/player/utils/urlUtils.ts
@@ -0,0 +1,24 @@
+export function getCopyUrl(src: string, type: 'original' | 'proxy' = 'original'): string {
+ let urlToCopy = src;
+
+ // If user wants original link, strip proxy prefix if present
+ if (type === 'original') {
+ if (urlToCopy.includes('/api/proxy?url=')) {
+ const match = urlToCopy.match(/url=([^&]*)/);
+ if (match && match[1]) {
+ urlToCopy = decodeURIComponent(match[1]);
+ }
+ }
+ }
+ // If user wants proxy link, ensure it has proxy prefix
+ else if (type === 'proxy') {
+ if (!urlToCopy.includes('/api/proxy?url=')) {
+ urlToCopy = `${window.location.origin}/api/proxy?url=${encodeURIComponent(urlToCopy)}`;
+ } else if (urlToCopy.startsWith('/')) {
+ // Ensure absolute URL for copy
+ urlToCopy = `${window.location.origin}${urlToCopy}`;
+ }
+ }
+
+ return urlToCopy;
+}