feat: Implement premium search history, add 'auto' fullscreen option, and update project version.

This commit is contained in:
kuekhaoyang
2026-02-16 11:10:09 +08:00
parent e04fe79355
commit d54039663a
9 changed files with 99 additions and 80 deletions
+1
View File
@@ -41,6 +41,7 @@ function PremiumHomePage() {
checkedSources={completedSources}
totalSources={totalSources}
placeholder="输入关键词开始搜索..."
isPremium={true}
/>
</div>
+2 -2
View File
@@ -26,7 +26,7 @@ export function useSettingsPage() {
// Display settings
const [realtimeLatency, setRealtimeLatency] = useState(false);
const [searchDisplayMode, setSearchDisplayMode] = useState<SearchDisplayMode>('normal');
const [fullscreenType, setFullscreenType] = useState<'native' | 'window'>('native');
const [fullscreenType, setFullscreenType] = useState<'auto' | 'native' | 'window'>('auto');
const [proxyMode, setProxyMode] = useState<ProxyMode>('retry');
const [rememberScrollPosition, setRememberScrollPosition] = useState(true);
@@ -285,7 +285,7 @@ export function useSettingsPage() {
});
};
const handleFullscreenTypeChange = (type: 'native' | 'window') => {
const handleFullscreenTypeChange = (type: 'auto' | 'native' | 'window') => {
setFullscreenType(type);
const currentSettings = settingsStore.getSettings();
settingsStore.saveSettings({
+3 -2
View File
@@ -11,9 +11,10 @@ interface SearchBoxProps {
onClear?: () => void;
initialQuery?: string;
placeholder?: string;
isPremium?: boolean;
}
export function SearchBox({ onSearch, onClear, initialQuery = '', placeholder = '搜索电影、电视剧、综艺...' }: SearchBoxProps) {
export function SearchBox({ onSearch, onClear, initialQuery = '', placeholder = '搜索电影、电视剧、综艺...', isPremium = false }: SearchBoxProps) {
const [query, setQuery] = useState(initialQuery);
const inputRef = useRef<HTMLInputElement>(null);
@@ -35,7 +36,7 @@ export function SearchBox({ onSearch, onClear, initialQuery = '', placeholder =
onSearch(selectedQuery);
// Blur the input after selecting from history
inputRef.current?.blur();
});
}, isPremium);
// Update query when initialQuery changes
useEffect(() => {
+3
View File
@@ -12,6 +12,7 @@ interface SearchFormProps {
checkedSources?: number;
totalSources?: number;
placeholder?: string;
isPremium?: boolean;
}
export function SearchForm({
@@ -23,6 +24,7 @@ export function SearchForm({
checkedSources = 0,
totalSources = 16,
placeholder,
isPremium = false,
}: SearchFormProps) {
return (
<div className="max-w-3xl mx-auto">
@@ -31,6 +33,7 @@ export function SearchForm({
onClear={onClear}
initialQuery={initialQuery}
placeholder={placeholder}
isPremium={isPremium}
/>
{/* Loading Animation */}
+2 -2
View File
@@ -9,8 +9,8 @@ import { Icons } from '@/components/ui/Icon';
import type { ProxyMode } from '@/lib/store/settings-store';
interface PlayerSettingsProps {
fullscreenType: 'native' | 'window';
onFullscreenTypeChange: (type: 'native' | 'window') => void;
fullscreenType: 'auto' | 'native' | 'window';
onFullscreenTypeChange: (type: 'auto' | 'native' | 'window') => void;
proxyMode: ProxyMode;
onProxyModeChange: (mode: ProxyMode) => void;
}
+4 -3
View File
@@ -5,7 +5,7 @@
*/
import { useState, useEffect, useCallback, useRef } from 'react';
import { useSearchHistoryStore } from '@/lib/store/search-history-store';
import { useSearchHistoryStoreSelector } from '@/lib/store/search-history-store';
import type { SearchHistoryItem } from '@/lib/store/search-history-store';
interface UseSearchHistoryReturn {
@@ -23,7 +23,8 @@ interface UseSearchHistoryReturn {
}
export function useSearchHistory(
onSelectHistory?: (query: string) => void
onSelectHistory?: (query: string) => void,
isPremium: boolean = false
): UseSearchHistoryReturn {
const {
searchHistory,
@@ -31,7 +32,7 @@ export function useSearchHistory(
removeFromSearchHistory,
clearSearchHistory,
getRecentSearches,
} = useSearchHistoryStore();
} = useSearchHistoryStoreSelector(isPremium);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [highlightedIndex, setHighlightedIndex] = useState(-1);
+81 -68
View File
@@ -17,7 +17,7 @@ export interface SearchHistoryItem {
interface SearchHistoryStore {
searchHistory: SearchHistoryItem[];
// Actions
addToSearchHistory: (query: string, resultCount?: number) => void;
removeFromSearchHistory: (query: string) => void;
@@ -32,82 +32,95 @@ function normalizeQuery(query: string): string {
return query.trim().toLowerCase();
}
export const useSearchHistoryStore = create<SearchHistoryStore>()(
persist(
(set, get) => ({
searchHistory: [],
const createSearchHistoryStore = (name: string) =>
create<SearchHistoryStore>()(
persist(
(set, get) => ({
searchHistory: [],
addToSearchHistory: (query, resultCount) => {
const trimmedQuery = query.trim();
// Don't add empty queries
if (!trimmedQuery) return;
addToSearchHistory: (query, resultCount) => {
const trimmedQuery = query.trim();
const normalized = normalizeQuery(trimmedQuery);
const timestamp = Date.now();
// Don't add empty queries
if (!trimmedQuery) return;
set((state) => {
// Check if query already exists (case-insensitive)
const existingIndex = state.searchHistory.findIndex(
(item) => normalizeQuery(item.query) === normalized
);
const normalized = normalizeQuery(trimmedQuery);
const timestamp = Date.now();
let newHistory: SearchHistoryItem[];
set((state) => {
// Check if query already exists (case-insensitive)
const existingIndex = state.searchHistory.findIndex(
(item) => normalizeQuery(item.query) === normalized
);
if (existingIndex !== -1) {
// Update existing item and move to top
const updatedItem: SearchHistoryItem = {
query: trimmedQuery, // Keep original casing from new search
timestamp,
resultCount,
};
let newHistory: SearchHistoryItem[];
newHistory = [
updatedItem,
...state.searchHistory.filter((_, index) => index !== existingIndex),
];
} else {
// Add new item at the top
const newItem: SearchHistoryItem = {
query: trimmedQuery,
timestamp,
resultCount,
};
if (existingIndex !== -1) {
// Update existing item and move to top
const updatedItem: SearchHistoryItem = {
query: trimmedQuery, // Keep original casing from new search
timestamp,
resultCount,
};
newHistory = [newItem, ...state.searchHistory];
}
newHistory = [
updatedItem,
...state.searchHistory.filter((_, index) => index !== existingIndex),
];
} else {
// Add new item at the top
const newItem: SearchHistoryItem = {
query: trimmedQuery,
timestamp,
resultCount,
};
// Trim to max items
if (newHistory.length > MAX_HISTORY_ITEMS) {
newHistory = newHistory.slice(0, MAX_HISTORY_ITEMS);
}
newHistory = [newItem, ...state.searchHistory];
}
return { searchHistory: newHistory };
});
},
// Trim to max items
if (newHistory.length > MAX_HISTORY_ITEMS) {
newHistory = newHistory.slice(0, MAX_HISTORY_ITEMS);
}
removeFromSearchHistory: (query) => {
const normalized = normalizeQuery(query);
set((state) => ({
searchHistory: state.searchHistory.filter(
(item) => normalizeQuery(item.query) !== normalized
),
}));
},
return { searchHistory: newHistory };
});
},
clearSearchHistory: () => {
set({ searchHistory: [] });
},
removeFromSearchHistory: (query) => {
const normalized = normalizeQuery(query);
getRecentSearches: (limit = 10) => {
const history = get().searchHistory;
return history.slice(0, limit);
},
}),
{
name: 'kvideo-search-history',
version: 1,
}
)
);
set((state) => ({
searchHistory: state.searchHistory.filter(
(item) => normalizeQuery(item.query) !== normalized
),
}));
},
clearSearchHistory: () => {
set({ searchHistory: [] });
},
getRecentSearches: (limit = 10) => {
const history = get().searchHistory;
return history.slice(0, limit);
},
}),
{
name,
version: 1,
}
)
);
export const useSearchHistoryStore = createSearchHistoryStore('kvideo-search-history');
export const usePremiumSearchHistoryStore = createSearchHistoryStore('kvideo-premium-search-history');
/**
* Helper hook to get the appropriate search history store
*/
export function useSearchHistoryStoreSelector(isPremium = false) {
const normalStore = useSearchHistoryStore();
const premiumStore = usePremiumSearchHistoryStore();
return isPremium ? premiumStore : normalStore;
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "kvideo",
"version": "4.0.9",
"version": "4.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kvideo",
"version": "4.0.9",
"version": "4.1.0",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kvideo",
"version": "4.0.9",
"version": "4.1.0",
"private": true,
"scripts": {
"dev": "next dev",