Refactor progress following for TOC and Booknote items

This commit is contained in:
chrox
2024-11-11 16:08:41 +01:00
parent 7a337f17c2
commit 18ef1284fb
5 changed files with 42 additions and 62 deletions
@@ -1,5 +1,5 @@
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import * as CFI from 'foliate-js/epubcfi.js';
import { useEnv } from '@/context/EnvContext';
@@ -17,6 +17,7 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item, editable = f
const { settings, getConfig, saveConfig, getProgress, getView } = useReaderStore();
const { updateBooknotes, setNotebookEditAnnotation, setNotebookVisible } = useReaderStore();
const [isCurrent, setIsCurrent] = useState(false);
const viewRef = useRef<HTMLLIElement | null>(null);
const { text, cfi, note } = item;
const progress = getProgress(bookKey);
@@ -26,7 +27,17 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item, editable = f
const { location } = progress;
const start = CFI.collapse(location);
const end = CFI.collapse(location, true);
setIsCurrent(CFI.compare(start, cfi) * CFI.compare(end, cfi) <= 0);
const isCurrent = CFI.compare(cfi, start) >= 0 && CFI.compare(cfi, end) <= 0;
setIsCurrent(isCurrent);
if (isCurrent && viewRef.current) {
const rect = viewRef.current.getBoundingClientRect();
const isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight;
if (!isVisible) {
(viewRef.current as HTMLElement).scrollIntoView({ behavior: 'smooth', block: 'center' });
}
(viewRef.current as HTMLElement).setAttribute('aria-current', 'page');
}
}, [progress]);
const handleClickItem = (event: React.MouseEvent) => {
@@ -55,6 +66,7 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item, editable = f
return (
<li
ref={viewRef}
className={clsx(
'border-base-300 my-2 cursor-pointer rounded-lg p-2 text-sm',
editable && 'collapse-arrow collapse',
@@ -7,18 +7,15 @@ import BooknoteView from './BooknoteView';
const SidebarContent: React.FC<{
activeTab: string;
bookDoc: BookDoc;
currentHref: string | null;
sideBarBookKey: string;
}> = ({ activeTab, bookDoc, currentHref, sideBarBookKey }) => (
}> = ({ activeTab, bookDoc, sideBarBookKey }) => (
<div className='sidebar-content overflow-y-auto font-sans text-sm font-light shadow-inner'>
{activeTab === 'toc' && bookDoc.toc && (
<TOCView toc={bookDoc.toc} bookKey={sideBarBookKey} currentHref={currentHref} />
)}
{activeTab === 'toc' && bookDoc.toc && <TOCView toc={bookDoc.toc} bookKey={sideBarBookKey} />}
{activeTab === 'annotations' && (
<BooknoteView type={'annotation'} toc={bookDoc.toc} bookKey={sideBarBookKey} />
<BooknoteView type='annotation' toc={bookDoc.toc} bookKey={sideBarBookKey} />
)}
{activeTab === 'bookmarks' && (
<BooknoteView type={'bookmark'} toc={bookDoc.toc} bookKey={sideBarBookKey} />
<BooknoteView type='bookmark' toc={bookDoc.toc} bookKey={sideBarBookKey} />
)}
</div>
);
@@ -1,5 +1,5 @@
import clsx from 'clsx';
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
@@ -21,9 +21,8 @@ const SideBar: React.FC<{
}> = ({ width, isPinned, onGoToLibrary, onOpenSplitView }) => {
const { envConfig } = useEnv();
const { sideBarBookKey, settings } = useReaderStore();
const { saveSettings, getBookData, getProgress } = useReaderStore();
const { saveSettings, getBookData } = useReaderStore();
const [activeTab, setActiveTab] = useState(settings.globalReadSettings.sideBarTab);
const [currentHref, setCurrentHref] = useState<string | null>(null);
const {
sideBarWidth,
isSideBarVisible,
@@ -39,12 +38,6 @@ const SideBar: React.FC<{
};
const { handleMouseDown } = useDragBar(handleDragMove);
useEffect(() => {
if (!sideBarBookKey) return;
const progress = getProgress(sideBarBookKey);
setCurrentHref(progress?.tocHref || null);
}, [sideBarBookKey]);
const handleClickOverlay = () => {
setSideBarVisible(false);
};
@@ -86,12 +79,7 @@ const SideBar: React.FC<{
<div className='border-b px-3'>
<BookCard cover={book.coverImageUrl!} title={book.title} author={book.author} />
</div>
<SidebarContent
activeTab={activeTab}
bookDoc={bookDoc}
currentHref={currentHref}
sideBarBookKey={sideBarBookKey!}
/>
<SidebarContent activeTab={activeTab} bookDoc={bookDoc} sideBarBookKey={sideBarBookKey!} />
<TabNavigation activeTab={activeTab} onTabChange={handleTabChange} />
<div
className='drag-bar absolute right-0 top-0 h-full w-0.5 cursor-col-resize'
@@ -1,12 +1,9 @@
import React, { useEffect, useRef, useState } from 'react';
import { md5 } from 'js-md5';
import { TOCItem } from '@/libs/document';
import { useReaderStore } from '@/store/readerStore';
import { findParentPath } from '@/utils/toc';
import { useFoliateEvents } from '../../hooks/useFoliateEvents';
const getHrefMd5 = (href: string) => md5(JSON.stringify(href));
import { getContentMd5 } from '@/utils/misc';
const createExpanderIcon = (isExpanded: boolean) => {
return (
@@ -26,12 +23,11 @@ const TOCItemView: React.FC<{
bookKey: string;
item: TOCItem;
depth: number;
setCurrentHref: (href: string) => void;
currentHref: string | null;
expandedItems: string[];
}> = ({ bookKey, item, depth, setCurrentHref, currentHref, expandedItems }) => {
}> = ({ bookKey, item, depth, expandedItems }) => {
const [isExpanded, setIsExpanded] = useState(expandedItems.includes(item.href || ''));
const { getView } = useReaderStore();
const { getView, getProgress } = useReaderStore();
const progress = getProgress(bookKey);
const handleToggleExpand = (event: React.MouseEvent) => {
event.preventDefault();
@@ -43,11 +39,10 @@ const TOCItemView: React.FC<{
event.preventDefault();
if (item.href) {
getView(bookKey)?.goTo(item.href);
setCurrentHref(item.href);
}
};
const isActive = currentHref === item.href;
const isActive = progress ? progress.tocHref === item.href : false;
useEffect(() => {
setIsExpanded(expandedItems.includes(item.href || ''));
@@ -62,7 +57,7 @@ const TOCItemView: React.FC<{
style={{ paddingInlineStart: `${(depth + 1) * 12}px` }}
aria-expanded={isExpanded ? 'true' : 'false'}
aria-selected={isActive ? 'true' : 'false'}
data-href={item.href ? getHrefMd5(item.href) : undefined}
data-href={item.href ? getContentMd5(item.href) : undefined}
className={`flex w-full cursor-pointer items-center rounded-md py-2 ${
isActive ? 'bg-gray-300 hover:bg-gray-400' : 'hover:bg-gray-300'
}`}
@@ -91,8 +86,6 @@ const TOCItemView: React.FC<{
key={`${index}-${subitem.href}`}
item={subitem}
depth={depth + 1}
setCurrentHref={setCurrentHref}
currentHref={currentHref}
expandedItems={expandedItems}
/>
))}
@@ -105,26 +98,12 @@ const TOCItemView: React.FC<{
const TOCView: React.FC<{
bookKey: string;
toc: TOCItem[];
currentHref: string | null;
}> = ({ bookKey, toc, currentHref: href }) => {
const [currentHref, setCurrentHref] = useState<string | null>(href);
}> = ({ bookKey, toc }) => {
const { sideBarBookKey, getProgress } = useReaderStore();
const progress = getProgress(bookKey);
const [expandedItems, setExpandedItems] = useState<string[]>([]);
const { getView } = useReaderStore();
const tocRef = useRef<HTMLUListElement | null>(null);
const tocRelocateHandler = (event: Event) => {
const detail = (event as CustomEvent).detail;
const { tocItem } = detail;
if (tocItem?.href) {
setCurrentHref(tocItem.href);
}
};
useFoliateEvents(getView(bookKey), { onRelocate: tocRelocateHandler });
useEffect(() => {
setCurrentHref(href);
}, [href]);
const viewRef = useRef<HTMLUListElement | null>(null);
const expandParents = (toc: TOCItem[], href: string) => {
const parentPath = findParentPath(toc, href).map((item) => item.href);
@@ -132,8 +111,10 @@ const TOCView: React.FC<{
};
useEffect(() => {
const hrefMd5 = currentHref ? getHrefMd5(currentHref) : '';
const currentItem = tocRef.current?.querySelector(`[data-href="${hrefMd5}"]`);
if (!progress) return;
const { tocHref: currentHref } = progress;
const hrefMd5 = currentHref ? getContentMd5(currentHref) : '';
const currentItem = viewRef.current?.querySelector(`[data-href="${hrefMd5}"]`);
if (currentItem) {
const rect = currentItem.getBoundingClientRect();
const isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight;
@@ -145,12 +126,12 @@ const TOCView: React.FC<{
if (currentHref) {
expandParents(toc, currentHref);
}
}, [toc, currentHref]);
}, [toc, progress, sideBarBookKey]);
return (
<div className='relative'>
<div className='max-h-[calc(100vh-173px)] overflow-y-auto rounded pt-2'>
<ul role='tree' ref={tocRef} className='overflow-y-auto px-2'>
<ul role='tree' ref={viewRef} className='overflow-y-auto px-2'>
{toc &&
toc.map((item, index) => (
<TOCItemView
@@ -158,8 +139,6 @@ const TOCView: React.FC<{
key={`${index}-${item.href}`}
item={item}
depth={0}
setCurrentHref={setCurrentHref}
currentHref={currentHref}
expandedItems={expandedItems}
/>
))}
+4
View File
@@ -1 +1,5 @@
import { md5 } from 'js-md5';
export const uniqueId = () => Math.random().toString(36).substring(2, 9);
export const getContentMd5 = (content: unknown) => md5(JSON.stringify(content));