补充规则以及修改电子书馆来源选项卡为单行

This commit is contained in:
mtvpls
2026-05-20 11:48:09 +08:00
parent 3583366ea6
commit beb51ce1c0
5 changed files with 144 additions and 9 deletions
+1 -1
View File
@@ -12419,7 +12419,7 @@ const OPDSConfigComponent = ({
<div className='mb-3 flex items-center justify-between gap-3'>
<div>
<h4 className='text-sm font-medium text-amber-900 dark:text-amber-100'>Legado </h4>
<p className='mt-1 text-xs text-amber-800 dark:text-amber-200'> URL </p>
<p className='mt-1 text-xs text-amber-800 dark:text-amber-200'></p>
</div>
<button type='button' onClick={importLegadoSubscription} disabled={!legadoSubscriptionUrl.trim() || isLoading('importLegadoSubscription')} className={buttonStyles.primarySmall}>{isLoading('importLegadoSubscription') ? '导入中...' : '导入订阅'}</button>
</div>
+84 -2
View File
@@ -72,9 +72,12 @@ export default function BooksCatalogPage() {
const [error, setError] = useState('');
const [loadingMore, setLoadingMore] = useState(false);
const loaderRef = useRef<HTMLDivElement | null>(null);
const sourceScrollerRef = useRef<HTMLDivElement | null>(null);
const navScrollerRef = useRef<HTMLDivElement | null>(null);
const loadedPageHrefsRef = useRef<Set<string>>(new Set());
const failedPageHrefsRef = useRef<Set<string>>(new Set());
const sourceDragStateRef = useRef<{ pointerId: number; startX: number; startScrollLeft: number; moved: boolean; pointerType: string } | null>(null);
const suppressSourceClickRef = useRef(false);
const navDragStateRef = useRef<{ pointerId: number; startX: number; startScrollLeft: number; moved: boolean; pointerType: string } | null>(null);
const suppressNavClickRef = useRef(false);
@@ -157,6 +160,64 @@ export default function BooksCatalogPage() {
return () => observer.disconnect();
}, [data, nextHref, loadingMore, loadCatalog]);
const handleSourcePointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
if (event.pointerType === 'mouse' && event.button !== 0) return;
const node = sourceScrollerRef.current;
if (!node) return;
sourceDragStateRef.current = {
pointerId: event.pointerId,
startX: event.clientX,
startScrollLeft: node.scrollLeft,
moved: false,
pointerType: event.pointerType,
};
suppressSourceClickRef.current = false;
if (event.pointerType !== 'mouse') {
node.setPointerCapture?.(event.pointerId);
}
}, []);
const handleSourcePointerMove = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
const node = sourceScrollerRef.current;
const dragState = sourceDragStateRef.current;
if (!node || !dragState || dragState.pointerId !== event.pointerId) return;
const deltaX = event.clientX - dragState.startX;
const moveThreshold = dragState.pointerType === 'mouse' ? 8 : 4;
if (Math.abs(deltaX) > moveThreshold) {
dragState.moved = true;
suppressSourceClickRef.current = true;
}
node.scrollLeft = dragState.startScrollLeft - deltaX;
}, []);
const handleSourcePointerUp = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
const node = sourceScrollerRef.current;
const dragState = sourceDragStateRef.current;
if (!dragState || dragState.pointerId !== event.pointerId) return;
if (dragState.moved) {
event.preventDefault();
window.setTimeout(() => {
suppressSourceClickRef.current = false;
}, 0);
}
sourceDragStateRef.current = null;
if (dragState.pointerType !== 'mouse') {
node?.releasePointerCapture?.(event.pointerId);
}
}, []);
const handleSourcePointerLeave = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
if (event.pointerType === 'mouse') return;
handleSourcePointerUp(event);
}, [handleSourcePointerUp]);
const handleSourceWheel = useCallback((event: ReactWheelEvent<HTMLDivElement>) => {
const node = sourceScrollerRef.current;
if (!node) return;
const delta = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY;
if (!delta) return;
node.scrollLeft += delta;
}, []);
const handleNavPointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
if (event.pointerType === 'mouse' && event.button !== 0) return;
@@ -235,9 +296,30 @@ export default function BooksCatalogPage() {
return (
<div className='space-y-6'>
<div className='flex flex-wrap gap-2'>
<div
ref={sourceScrollerRef}
className='flex flex-nowrap gap-2 overflow-x-auto pb-1 cursor-grab select-none touch-pan-x active:cursor-grabbing'
onPointerDown={handleSourcePointerDown}
onPointerMove={handleSourcePointerMove}
onPointerUp={handleSourcePointerUp}
onPointerCancel={handleSourcePointerUp}
onPointerLeave={handleSourcePointerLeave}
onWheel={handleSourceWheel}
>
{sources.map((source) => (
<Link key={source.id} href={`/books/catalog?sourceId=${encodeURIComponent(source.id)}`} className={`rounded-full px-4 py-2 text-sm ${source.id === sourceId ? 'bg-sky-600 text-white' : 'border border-gray-200 dark:border-gray-700'}`}>
<Link
key={source.id}
href={`/books/catalog?sourceId=${encodeURIComponent(source.id)}`}
draggable={false}
onDragStart={(event) => event.preventDefault()}
onClick={(event) => {
if (suppressSourceClickRef.current) {
event.preventDefault();
suppressSourceClickRef.current = false;
}
}}
className={`shrink-0 whitespace-nowrap rounded-full px-4 py-2 text-sm ${source.id === sourceId ? 'bg-sky-600 text-white' : 'border border-gray-200 dark:border-gray-700'}`}
>
{source.name}
</Link>
))}
-1
View File
@@ -46,7 +46,6 @@ export default function BooksHomePage() {
<div className='space-y-6'>
<section className='rounded-3xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-950'>
<h1 className='text-lg font-semibold'></h1>
<p className='mt-1 text-sm text-gray-500 dark:text-gray-400'> OPDS Legado 线</p>
</section>
{loading ? <BooksHomeSkeleton /> : null}