选集屏蔽增加相反模式

This commit is contained in:
mtvpls
2026-04-18 10:34:34 +08:00
parent 3520fd1ed3
commit b08fe23eab
7 changed files with 185 additions and 67 deletions
+80 -17
View File
@@ -6,6 +6,7 @@ import { useEffect, useRef,useState } from 'react';
import { createPortal } from 'react-dom';
import { getEpisodeFilterConfig, saveEpisodeFilterConfig } from '@/lib/db.client';
import { normalizeEpisodeFilterConfig } from '@/lib/episode-filter';
import { EpisodeFilterConfig, EpisodeFilterRule } from '@/lib/types';
interface EpisodeFilterSettingsProps {
@@ -21,7 +22,7 @@ export default function EpisodeFilterSettings({
onConfigUpdate,
onShowToast,
}: EpisodeFilterSettingsProps) {
const [config, setConfig] = useState<EpisodeFilterConfig>({ rules: [] });
const [config, setConfig] = useState<EpisodeFilterConfig>(normalizeEpisodeFilterConfig());
const [newKeyword, setNewKeyword] = useState('');
const [newType, setNewType] = useState<'normal' | 'regex'>('normal');
const [loading, setLoading] = useState(false);
@@ -130,9 +131,9 @@ export default function EpisodeFilterSettings({
try {
const loadedConfig = await getEpisodeFilterConfig();
if (loadedConfig) {
setConfig(loadedConfig);
setConfig(normalizeEpisodeFilterConfig(loadedConfig));
} else {
setConfig({ rules: [] });
setConfig(normalizeEpisodeFilterConfig());
}
} catch (error) {
console.error('加载集数过滤配置失败:', error);
@@ -141,13 +142,31 @@ export default function EpisodeFilterSettings({
}
};
const handleToggleReverseMode = () => {
setConfig((prev) => {
const normalizedConfig = normalizeEpisodeFilterConfig(prev);
return {
...normalizedConfig,
reverseMode: !normalizedConfig.reverseMode,
};
});
};
// 保存配置
const handleSave = async () => {
const normalizedConfig = normalizeEpisodeFilterConfig(config);
if (normalizedConfig.reverseMode && normalizedConfig.rules.length === 0) {
if (onShowToast) {
onShowToast('启用相反模式时,至少需要添加一条规则', 'info');
}
return;
}
setSaving(true);
try {
await saveEpisodeFilterConfig(config);
await saveEpisodeFilterConfig(normalizedConfig);
if (onConfigUpdate) {
onConfigUpdate(config);
onConfigUpdate(normalizedConfig);
}
if (onShowToast) {
onShowToast('保存成功!', 'success');
@@ -182,9 +201,13 @@ export default function EpisodeFilterSettings({
id: Date.now().toString(),
};
setConfig((prev) => ({
rules: [...prev.rules, newRule],
}));
setConfig((prev) => {
const normalizedConfig = normalizeEpisodeFilterConfig(prev);
return {
...normalizedConfig,
rules: [...normalizedConfig.rules, newRule],
};
});
// 清空输入框并强制重新渲染
setNewKeyword('');
@@ -202,19 +225,27 @@ export default function EpisodeFilterSettings({
// 删除规则
const handleDeleteRule = (id: string | undefined) => {
if (!id) return;
setConfig((prev) => ({
rules: prev.rules.filter((rule) => rule.id !== id),
}));
setConfig((prev) => {
const normalizedConfig = normalizeEpisodeFilterConfig(prev);
return {
...normalizedConfig,
rules: normalizedConfig.rules.filter((rule) => rule.id !== id),
};
});
};
// 切换规则启用状态
const handleToggleRule = (id: string | undefined) => {
if (!id) return;
setConfig((prev) => ({
rules: prev.rules.map((rule) =>
rule.id === id ? { ...rule, enabled: !rule.enabled } : rule
),
}));
setConfig((prev) => {
const normalizedConfig = normalizeEpisodeFilterConfig(prev);
return {
...normalizedConfig,
rules: normalizedConfig.rules.map((rule) =>
rule.id === id ? { ...rule, enabled: !rule.enabled } : rule
),
};
});
};
if (!isVisible || !mounted) return null;
@@ -294,6 +325,37 @@ export default function EpisodeFilterSettings({
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-4 min-h-0">
{/* 添加规则 */}
<div className="bg-gray-50 dark:bg-gray-800 rounded-xl p-4 space-y-3">
<div className="flex items-start justify-between gap-3 rounded-xl bg-white dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 px-4 py-3">
<div className="min-w-0">
<h3 className="text-sm font-medium text-gray-800 dark:text-gray-200">
</h3>
<p className="mt-1 text-xs leading-relaxed text-gray-500 dark:text-gray-400">
</p>
<p className="mt-1 text-xs leading-relaxed text-amber-600 dark:text-amber-400">
</p>
</div>
<button
onClick={handleToggleReverseMode}
className="flex-shrink-0 active:scale-95 transition-transform duration-150"
title={config.reverseMode ? '关闭相反模式' : '开启相反模式'}
>
{config.reverseMode ? (
<ToggleRight
size={28}
className="text-green-500 hover:text-green-400 transition-colors duration-150"
/>
) : (
<ToggleLeft
size={28}
className="text-gray-400 hover:text-gray-300 transition-colors duration-150"
/>
)}
</button>
</div>
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">
</h3>
@@ -333,7 +395,8 @@ export default function EpisodeFilterSettings({
</div>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 leading-relaxed">
💡 <br/>
💡 <br/>
🔄 <br/>
🔧 ^.*"预告"
</p>
</div>
+2 -23
View File
@@ -12,6 +12,7 @@ import React, {
import type { DanmakuComment,DanmakuSelection } from '@/lib/danmaku/types';
import { generateStorageKey, getCachedPlayRecordsSnapshot } from '@/lib/db.client';
import { isEpisodeHiddenByFilter } from '@/lib/episode-filter';
import { loadAllLocalEpisodeProgressRecords } from '@/lib/episode-progress';
import { EpisodeFilterConfig,SearchResult } from '@/lib/types';
import { getVideoResolutionFromM3u8 } from '@/lib/utils';
@@ -249,29 +250,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
// 获取集数标题
const title = episodes_titles?.[episodeNumber - 1];
if (!title) return false;
// 检查每个启用的规则
for (const rule of episodeFilterConfig.rules) {
if (!rule.enabled) continue;
try {
if (rule.type === 'normal') {
// 普通模式:字符串包含匹配
if (title.includes(rule.keyword)) {
return true;
}
} else if (rule.type === 'regex') {
// 正则模式:正则表达式匹配
if (new RegExp(rule.keyword).test(title)) {
return true;
}
}
} catch (e) {
console.error('集数过滤规则错误:', e);
}
}
return false;
return isEpisodeHiddenByFilter(title, episodeFilterConfig);
},
[episodeFilterConfig, episodes_titles]
);