From 18ef1284fb636ba52107918a6c07ad17ea307bf3 Mon Sep 17 00:00:00 2001 From: chrox Date: Mon, 11 Nov 2024 16:08:41 +0100 Subject: [PATCH] Refactor progress following for TOC and Booknote items --- .../components/sidebar/BooknoteItem.tsx | 16 +++++- .../app/reader/components/sidebar/Content.tsx | 11 ++-- .../app/reader/components/sidebar/SideBar.tsx | 18 +----- .../app/reader/components/sidebar/TOCView.tsx | 55 ++++++------------- apps/readest-app/src/utils/misc.ts | 4 ++ 5 files changed, 42 insertions(+), 62 deletions(-) diff --git a/apps/readest-app/src/app/reader/components/sidebar/BooknoteItem.tsx b/apps/readest-app/src/app/reader/components/sidebar/BooknoteItem.tsx index b5e0fa08..cfc5eb63 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/BooknoteItem.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/BooknoteItem.tsx @@ -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 = ({ bookKey, item, editable = f const { settings, getConfig, saveConfig, getProgress, getView } = useReaderStore(); const { updateBooknotes, setNotebookEditAnnotation, setNotebookVisible } = useReaderStore(); const [isCurrent, setIsCurrent] = useState(false); + const viewRef = useRef(null); const { text, cfi, note } = item; const progress = getProgress(bookKey); @@ -26,7 +27,17 @@ const BooknoteItem: React.FC = ({ 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 = ({ bookKey, item, editable = f return (
  • = ({ activeTab, bookDoc, currentHref, sideBarBookKey }) => ( +}> = ({ activeTab, bookDoc, sideBarBookKey }) => (
    - {activeTab === 'toc' && bookDoc.toc && ( - - )} + {activeTab === 'toc' && bookDoc.toc && } {activeTab === 'annotations' && ( - + )} {activeTab === 'bookmarks' && ( - + )}
    ); diff --git a/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx b/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx index 4e30a83a..86f26227 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx @@ -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(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<{
    - +
    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(href); +}> = ({ bookKey, toc }) => { + const { sideBarBookKey, getProgress } = useReaderStore(); + const progress = getProgress(bookKey); + const [expandedItems, setExpandedItems] = useState([]); - const { getView } = useReaderStore(); - const tocRef = useRef(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(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 (
    -
      +
        {toc && toc.map((item, index) => ( ))} diff --git a/apps/readest-app/src/utils/misc.ts b/apps/readest-app/src/utils/misc.ts index a578c966..57392730 100644 --- a/apps/readest-app/src/utils/misc.ts +++ b/apps/readest-app/src/utils/misc.ts @@ -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));