仅netdisk显示

This commit is contained in:
mtvpls
2026-07-23 15:42:13 +08:00
parent 5d90eb8ce7
commit 19f4957160
2 changed files with 78 additions and 43 deletions
+13 -18
View File
@@ -142,30 +142,25 @@ function applyClientAdProxyToEpisodes(
});
}
/**
* 网盘集标题:保留完整文件名(去掉常见视频扩展名),
* 供前端选集按钮长按/右键查看全名;按钮短标签仍由前端从文件名提取集数。
* `parsed` 仅用于排序,不再覆盖为「第N集」。
*/
function formatNetdiskEpisodeTitle(
parsed: {
_parsed: {
season?: number;
episode?: number;
},
fallback: string
) {
if (parsed.season && parsed.episode) {
const season = String(Math.trunc(parsed.season)).padStart(2, '0');
const episodeValue = parsed.episode;
const episode = Number.isInteger(episodeValue)
? String(Math.trunc(episodeValue)).padStart(2, '0')
: String(episodeValue);
return `S${season}E${episode}`;
}
if (parsed.episode) {
const episodeValue = parsed.episode;
return Number.isInteger(episodeValue)
? `${Math.trunc(episodeValue)}`
: `${episodeValue}`;
}
return fallback;
const name = (fallback || '').trim();
if (!name) return fallback;
// 去掉末尾视频扩展名,保留完整可读文件名
return name.replace(
/\.(mp4|mkv|ts|m2ts|avi|mov|wmv|flv|webm|m4v|rmvb|iso|mpg|mpeg|m3u8)$/i,
''
);
}
/**
+65 -25
View File
@@ -23,7 +23,7 @@ import EpisodeFilterSettings from '@/components/EpisodeFilterSettings';
import ProxyImage from '@/components/ProxyImage';
import { useLongPress } from '@/hooks/useLongPress';
/** 选集按钮上显示的短标签(数字等) */
/** 选集按钮上显示的短标签(数字等);全名仍保留在 originalTitle 供长按查看 */
function getEpisodeDisplayLabel(
title: string | undefined,
episodeNumber: number
@@ -31,19 +31,44 @@ function getEpisodeDisplayLabel(
if (!title) {
return String(episodeNumber);
}
// 如果是 OVA 格式,直接返回完整标题
if (title.match(/^OVA\s+\d+/i)) {
return title;
// OVA 单独展示
const ovaMatch = title.match(/OVA\s*(\d+(?:\.\d+)?)/i);
if (ovaMatch) {
return `OVA ${ovaMatch[1]}`;
}
// 如果匹配 S01E01 格式,只显示集数部分(去掉 SxxE)
// S01E05 / s01e05 → 5
const sxxexxMatch = title.match(/[Ss]\d+[Ee](\d{1,4}(?:\.\d+)?)/);
if (sxxexxMatch) {
return sxxexxMatch[1];
}
// 如果匹配"第X集"、"第X话"、"X集"、"X话"格式,提取中间的数字(支持小数)
const match = title.match(/(?:第)?(\d+(?:\.\d+)?)(?:集|话)/);
if (match) {
return match[1];
// 第12集 / 12话 → 12
const zhMatch = title.match(/(?:第)?(\d+(?:\.\d+)?)(?:集|话)/);
if (zhMatch) {
return zhMatch[1];
}
// [01] / (01) → 1(网盘常见)
const bracketMatch = title.match(/[[(【](\d+(?:\.\d+)?)[\])】]/);
if (bracketMatch) {
return bracketMatch[1];
}
// E01 / EP01 / ep.01 → 1
const epMatch = title.match(/(?:^|[^a-zA-Z])(?:EP|E|ep|e)[.\s_-]*(\d+(?:\.\d+)?)/);
if (epMatch) {
return epMatch[1];
}
// _01_ / -01- → 1
const sepMatch = title.match(/[_-](\d+(?:\.\d+)?)[_-]/);
if (sepMatch) {
return sepMatch[1];
}
// 纯数字开头:01.xxx / 01 xxx
const leadingNum = title.match(/^(\d+(?:\.\d+)?)[^\d.]/);
if (leadingNum) {
return leadingNum[1];
}
// 整串就是数字
if (/^\d+(?:\.\d+)?$/.test(title.trim())) {
return title.trim();
}
return title;
}
@@ -61,24 +86,30 @@ interface EpisodeButtonProps {
isWatched: boolean;
originalTitle?: string;
inactiveEpisodeClass: string;
/** 仅 netdisk 源启用长按/右键查看全名 */
enableOriginalNamePopup?: boolean;
onSelect: (zeroBasedIndex: number) => void;
onShowOriginalName: (title: string, rect: DOMRect) => void;
}
/** 单集按钮:点击选集;移动端长按 / 桌面右键显示原集名 popup */
/** 单集按钮:点击选集;netdisk 时移动端长按 / 桌面右键显示原集名 popup */
const EpisodeButton: React.FC<EpisodeButtonProps> = ({
episodeNumber,
isActive,
isWatched,
originalTitle,
inactiveEpisodeClass,
enableOriginalNamePopup = false,
onSelect,
onShowOriginalName,
}) => {
const buttonRef = useRef<HTMLButtonElement>(null);
const displayLabel = getEpisodeDisplayLabel(originalTitle, episodeNumber);
const canShowOriginalName =
!!originalTitle && originalTitle.trim() !== '' && originalTitle !== displayLabel;
enableOriginalNamePopup &&
!!originalTitle &&
originalTitle.trim() !== '' &&
originalTitle !== displayLabel;
const showOriginalName = useCallback(() => {
if (!canShowOriginalName || !buttonRef.current) return;
@@ -99,7 +130,8 @@ const EpisodeButton: React.FC<EpisodeButtonProps> = ({
<button
ref={buttonRef}
type='button'
// 不用 disabled,否则当前集无法长按/右键查看原名
// netdisk 不用 disabled,否则当前集无法长按/右键查看原名
disabled={canShowOriginalName ? undefined : isActive || undefined}
aria-disabled={isActive || undefined}
aria-current={isActive ? 'true' : undefined}
onClick={() => {
@@ -107,14 +139,19 @@ const EpisodeButton: React.FC<EpisodeButtonProps> = ({
onSelect(episodeNumber - 1);
}
}}
onContextMenu={(e) => {
if (!canShowOriginalName) return;
e.preventDefault();
e.stopPropagation();
showOriginalName();
}}
{...longPressProps}
className={`relative h-10 min-w-10 px-3 py-2 flex items-center justify-center text-sm font-medium rounded-md transition-all duration-200 whitespace-nowrap font-mono border select-none
onContextMenu={
canShowOriginalName
? (e) => {
e.preventDefault();
e.stopPropagation();
showOriginalName();
}
: undefined
}
{...(canShowOriginalName ? longPressProps : {})}
className={`relative h-10 min-w-10 px-3 py-2 flex items-center justify-center text-sm font-medium rounded-md transition-all duration-200 whitespace-nowrap font-mono border ${
canShowOriginalName ? 'select-none' : ''
}
${isActive
? 'bg-green-500 text-white border-green-400 shadow-lg shadow-green-500/25 dark:bg-green-600 cursor-default'
: isWatched
@@ -122,11 +159,13 @@ const EpisodeButton: React.FC<EpisodeButtonProps> = ({
: inactiveEpisodeClass
}`.trim()}
style={
{
WebkitUserSelect: 'none',
userSelect: 'none',
WebkitTouchCallout: 'none',
} as React.CSSProperties
canShowOriginalName
? ({
WebkitUserSelect: 'none',
userSelect: 'none',
WebkitTouchCallout: 'none',
} as React.CSSProperties)
: undefined
}
title={isWatched && !isActive ? '已观看过' : undefined}
>
@@ -1031,6 +1070,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
isWatched={watchedEpisodes.has(episodeNumber)}
originalTitle={episodes_titles?.[episodeNumber - 1]}
inactiveEpisodeClass={inactiveEpisodeClass}
enableOriginalNamePopup={isNetdiskSource(currentSource)}
onSelect={handleEpisodeClick}
onShowOriginalName={showEpisodeNamePopup}
/>