refactor: streaming loader, hierarchical TOC, parser robustness
Rework the reader around a clean BookInfo interface and lazy chapter loading. Types/parser: - BookInfo now splits into loadStructure (cheap: spine refs + TOC tree) and renderChapter (one chapter on demand). EpubEnv carries the parsed archive. - resolveZipPath collapses ./.. and strips #fragments (normalise does not), so relative hrefs like ../Images/x.jpg resolve; used for chapters and images. - decodeEntry tolerates encodings: UTF-8/UTF-16 BOMs, Latin-1 fallback instead of throwing on invalid bytes. - Skip spine items with no manifest entry instead of throwing an error. - Scan the zip for a .opf if container.xml is missing. - Image inlining rewrites the src URL string, not the whole <img> tag (scalpel re-serialises tags, so a whole-tag replace silently missed self-closing imgs). Navigation: - Hierarchical TOC: ncxToc (EPUB 2), navToc (EPUB 3), guideToc (<guide> fallback). Nested scrapers use atDepth 1 to take only direct children. UI: - Streaming: render the opened chapter on demand, cache it, preload neighbours so arrow paging is instant with a placeholder that covers uncached jumps. - Sidebar shows the TOC tree with CSS styling. Clicks scroll to #fragments. - wrapHtml wraps each body in a full HTML document for reliable WebKit render. Test: 'svitak-test <book> <idx>' dumps one rendered chapter. cabal test stanza fixed to list all needed modules/deps.
This commit is contained in:
parent
269c2055f1
commit
2011d638b0
7 changed files with 738 additions and 427 deletions
51
app/State.hs
51
app/State.hs
|
|
@ -1,41 +1,22 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module State
|
||||
( initialState,
|
||||
prepareView
|
||||
)
|
||||
where
|
||||
-- | The mutable application state's initial value. Navigation logic lives in
|
||||
-- the UI, which renders chapters lazily into 'stCache'.
|
||||
module State (initialState) where
|
||||
|
||||
import Types (AppState(..), Chapter(chapterTags, chapterTitle), ChapterView(..), EpubEnv, ChapterIndex(..))
|
||||
import qualified Data.Map.Strict as M
|
||||
import qualified Data.Text as T
|
||||
import Types (AppState (..), ChapterIndex (..))
|
||||
|
||||
-- Get an element from a list without crashing
|
||||
safeGet :: ChapterIndex -> [a] -> Maybe a
|
||||
safeGet (ChapterIndex n) xs
|
||||
| n < 0 = Nothing
|
||||
| otherwise = case drop n xs of
|
||||
(x : _) -> Just x
|
||||
[] -> Nothing
|
||||
|
||||
-- loading template
|
||||
initialState :: EpubEnv -> AppState
|
||||
initialState env =
|
||||
-- | Empty state shown while the book's structure loads in the background.
|
||||
initialState :: T.Text -> FilePath -> AppState
|
||||
initialState title path =
|
||||
AppState
|
||||
{ cEnv = env,
|
||||
cIdx = ChapterIndex 0,
|
||||
cSpine = [],
|
||||
bTitle = "Svitak - Loading...",
|
||||
zoomlvl = 1.0,
|
||||
activeTask = Nothing,
|
||||
cAllChapters = [],
|
||||
taskVersion = 0
|
||||
}
|
||||
|
||||
-- prepare the data for the UI
|
||||
prepareView :: AppState -> ChapterIndex -> Maybe ChapterView
|
||||
prepareView st index = do
|
||||
chapter <- safeGet index (cAllChapters st)
|
||||
pure $ ChapterView
|
||||
{ viewTitle = bTitle st <> " - " <> chapterTitle chapter
|
||||
, viewHtml = chapterTags chapter
|
||||
, viewIdx = index
|
||||
{ stRefs = [],
|
||||
stToc = [],
|
||||
stCache = M.empty,
|
||||
stIndex = ChapterIndex 0,
|
||||
stZoom = 1.0,
|
||||
stTitle = title,
|
||||
stBookPath = path
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue