add Html and PlainText types, improved type safety

This commit is contained in:
Marko Andjelic 2026-07-07 13:33:51 +01:00
commit 8be29359fc
Signed by: marko
GPG key ID: 9C5E99C8C682FB59
3 changed files with 23 additions and 14 deletions

View file

@ -48,6 +48,8 @@ import Types
EpubEnv (..),
InternalPath (..),
TocEntry (..),
Html(..),
PlainText(..)
)
import Navigation (RawToc (..), guideToc, navDocHref, navToc, ncxToc)
@ -181,11 +183,11 @@ renderRef ref = do
env <- ask
let InternalPath p = refPath ref
pure $ case findEntry env p of
Nothing -> Chapter (refPath ref) (refTitle ref) "" (refIndex ref)
Nothing -> Chapter (refPath ref) (refTitle ref) (Html "") (refIndex ref)
Just entry ->
let withImages = embedImages env (takeDirectory p) (decodeEntry entry)
body = fromMaybe "" (scrapeStringLike withImages bodyScraper)
in Chapter (refPath ref) (refTitle ref) body (refIndex ref)
in Chapter (refPath ref) (refTitle ref) (Html body) (refIndex ref)
stripTags :: T.Text -> T.Text
stripTags = T.unwords . T.words . go
@ -197,14 +199,14 @@ stripTags = T.unwords . T.words . go
-- | Read a chapter file and render its @\<body\>@ to plaintext, excluding
-- any tags.
readPText :: ChapterRef -> EpubAction T.Text
readPText :: ChapterRef -> EpubAction PlainText
readPText ref = do
env <- ask
let InternalPath p = refPath ref
pure $ case findEntry env p of
Nothing -> ""
Nothing -> PlainText ""
Just entry ->
stripTags $ fromMaybe "" (scrapeStringLike (decodeEntry entry) bodyScraper)
PlainText $ stripTags $ fromMaybe "" (scrapeStringLike (decodeEntry entry) bodyScraper)
-- | Grab the contents of @\<body\>@, falling back to the whole document.
bodyScraper :: Scraper T.Text T.Text

View file

@ -25,6 +25,8 @@ module Types
EpubEnv (..),
EpubAction,
ReadMode(..),
Html(..),
PlainText(..),
)
where
@ -56,11 +58,14 @@ data ChapterRef = ChapterRef
data Chapter = Chapter
{ chapterPath :: InternalPath,
chapterTitle :: T.Text,
chapterHtml :: T.Text,
chapterHtml :: Html,
chapterIndex :: ChapterIndex
}
deriving (Show)
newtype Html = Html T.Text deriving (Show, Eq)
newtype PlainText = PlainText T.Text deriving (Show, Eq)
-- | A node in the (possibly nested) table of contents. @tocTarget@ is the
-- resolved chapter path; @tocFragment@ is the optional in-page anchor.
data TocEntry = TocEntry
@ -91,7 +96,7 @@ class BookInfo a where
renderChapter :: a -> ChapterRef -> IO (Either String Chapter)
-- | Extract a single chapter's plain text (tags stripped), for search.
chapterText :: a -> ChapterRef -> IO (Either String T.Text)
chapterText :: a -> ChapterRef -> IO (Either String PlainText)
-- | Mutable application state, held in a 'Control.Concurrent.STM.TVar'.
-- Rendered chapters accumulate in 'stCache' as the reader visits them.
@ -106,7 +111,7 @@ data AppState = AppState
stGen :: Int,
stUse :: M.Map ChapterIndex Int,
stTick :: Int,
stSearchIndex :: M.Map ChapterIndex T.Text,
stSearchIndex :: M.Map ChapterIndex PlainText,
stMode :: ReadMode
}

View file

@ -39,6 +39,8 @@ import Types
TocEntry (..),
UserAction (..),
ReadMode(..),
Html(..),
PlainText(..),
)
-- | Widget handles the rest of the module needs.
@ -402,7 +404,7 @@ showLoading :: Ctx -> ChapterRef -> IO ()
showLoading ctx ref = do
mode <- getMode ctx
setWindowTitle ctx (refIndex ref) (refTitle ref)
WebKit.webViewLoadHtml (appWebView (ctxWidgets ctx)) (wrapHtml "<p style=\"opacity:0.5\">Loading…</p>" StartTop mode) Nothing
WebKit.webViewLoadHtml (appWebView (ctxWidgets ctx)) (wrapHtml (Html "<p style=\"opacity:0.5\">Loading…</p>") StartTop mode) Nothing
setWindowTitle :: Ctx -> ChapterIndex -> T.Text -> IO ()
setWindowTitle ctx (ChapterIndex i) chapter = do
@ -419,8 +421,8 @@ setZoom ctx level = do
-- | Wrap a chapter's @\<body\>@ fragment in a full, styled HTML document
-- (WebKit renders bare fragments unreliably). If @frag@ names an anchor, a
-- small script scrolls it into view after load.
wrapHtml :: T.Text -> StartAt -> ReadMode -> T.Text
wrapHtml body start mode =
wrapHtml :: Html -> StartAt -> ReadMode -> T.Text
wrapHtml (Html body) start mode =
T.concat
[ "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><style>",
( case mode of
@ -492,11 +494,11 @@ evalJSBool wv src k =
b <- JSC.valueToBoolean val
k b)
chaptersMatching :: T.Text -> M.Map ChapterIndex T.Text -> [ChapterIndex]
chaptersMatching :: T.Text -> M.Map ChapterIndex PlainText -> [ChapterIndex]
chaptersMatching q = map fst . filter (matches q . snd) . M.toAscList
matches :: T.Text -> T.Text -> Bool
matches q txt = T.toCaseFold q `T.isInfixOf` T.toCaseFold txt
matches :: T.Text -> PlainText -> Bool
matches q (PlainText txt) = T.toCaseFold q `T.isInfixOf` T.toCaseFold txt
nextMatch :: ChapterIndex -> [ChapterIndex] -> Maybe ChapterIndex
nextMatch cur hits =