explicit types

This commit is contained in:
Marko Andjelic 2026-02-12 11:17:36 +00:00
commit 55b04ecf44
6 changed files with 51 additions and 36 deletions

View file

@ -26,8 +26,8 @@ import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import System.FilePath (normalise, takeDirectory, (</>))
import Text.HTML.Scalpel (Scraper, ScraperT, anySelector, attr, chroot, chroots, html, innerHTML, scrapeStringLike, text)
import Types (Chapter (..), EpubAction, EpubEnv (..))
import Navigation (navFinder)
import Types (Chapter (..), EpubAction, EpubEnv (..), InternalPath(..), ChapterIndex(..))
import Navigation (navFinder, lndmkScraper)
openEpub :: FilePath -> IO (Either String EpubEnv)
openEpub path = do
@ -74,7 +74,7 @@ ncxScraper = chroots "navPoint" $ do
title <- T.strip <$> text "navLabel"
pure (path, title)
resolveSpine :: EpubAction [(FilePath, T.Text)]
resolveSpine :: EpubAction [(InternalPath, T.Text)]
resolveSpine = do
env <- ask
let xmlStr = opfXml env
@ -99,12 +99,12 @@ resolveSpine = do
pure $ fromMaybe [] (scrapeStringLike raw ncxScraper)
pure [(p, fromMaybe ("Chapter " <> T.pack (show (i :: Int))) (lookup p tocMap)) | (ref, i) <- zip refs [1..] , let ident = DSpin.siIdRef ref , Just p <- [lookupHref ident]]
pure [(InternalPath p, fromMaybe ("Chapter " <> T.pack (show (i :: Int))) (lookup p tocMap)) | (ref, i) <- zip refs [1..] , let ident = DSpin.siIdRef ref , Just p <- [lookupHref ident]]
_ -> pure []
getChapter :: (FilePath, T.Text) -> Int -> EpubAction Chapter
getChapter (filename, tocTitle) idx = do
getChapter :: (InternalPath, T.Text) -> ChapterIndex -> EpubAction Chapter
getChapter (InternalPath filename, tocTitle) idx = do
env <- ask
let full = normalise (baseDir env </> filename)
case findEntryByPath full (archive env) of

View file

@ -7,7 +7,7 @@ module Navigation
where
import Text.HTML.Scalpel (Scraper, attr, (@:), (@=), chroot, chroots, text, match)
import Types (NavItem(..))
import Types (NavItem(..), EpubType (..), InternalPath(..))
import Data.Maybe (fromMaybe)
import Control.Applicative (optional)
import Control.Monad (guard)
@ -21,21 +21,21 @@ import qualified Data.Text as T
navFinder :: Scraper String FilePath
navFinder = attr "href" ("item" @: ["properties" @= "nav"])
itemScraper :: [T.Text] -> Scraper T.Text NavItem
itemScraper :: [EpubType] -> Scraper T.Text NavItem
itemScraper allowedTypes = do
label <- text "a"
path <- attr "href" "a"
rawTypes <- fromMaybe "" <$> optional (attr "epub:type" "a")
let foundTypes = T.words rawTypes
let foundTypes = map EpubType $ T.words rawTypes
guard $ any (`elem` allowedTypes) foundTypes
pure $ NavItem label (T.unpack path) foundTypes
pure $ NavItem label (InternalPath $ T.unpack path) foundTypes
lndmkScraper :: [T.Text] -> Scraper T.Text [NavItem]
lndmkScraper :: [EpubType] -> Scraper T.Text [NavItem]
lndmkScraper labels =
chroot ("nav" @: [match navPredicate]) $ chroots "li" (itemScraper labels)
where
navPredicate "epub:type" xs = T.pack xs `elem` labels
navPredicate "epub:type" xs = EpubType (T.pack xs) `elem` labels
navPredicate _ _ = False

View file

@ -10,9 +10,10 @@ import qualified Data.Text.IO as TIO
import System.Directory (XdgDirectory (XdgData), createDirectoryIfMissing, getXdgDirectory)
import System.FilePath ((</>))
import Text.Read (readMaybe)
import Types (ChapterIndex(..))
saveLastRead :: FilePath -> Int -> IO ()
saveLastRead bPath chapterIdx = do
saveLastRead :: FilePath -> ChapterIndex -> IO ()
saveLastRead bPath (ChapterIndex chapterIdx) = do
dataDir <- getXdgDirectory XdgData "svitak"
createDirectoryIfMissing True dataDir
@ -22,7 +23,7 @@ saveLastRead bPath chapterIdx = do
TIO.writeFile configFile content
putStrLn $ "Progress saved to: " ++ configFile
loadLastRead :: IO (Maybe (FilePath, Int))
loadLastRead :: IO (Maybe (FilePath, ChapterIndex))
loadLastRead = do
dataDir <- getXdgDirectory XdgData "svitak"
let configFile = dataDir </> "last_read.txt"
@ -36,5 +37,5 @@ loadLastRead = do
case linesOfFile of
[path, idxStr]
| Just idx <- readMaybe (T.unpack idxStr) ->
pure $ Just (T.unpack path, idx)
pure $ Just (T.unpack path, ChapterIndex idx)
_ -> pure Nothing

View file

@ -6,11 +6,11 @@ module State
)
where
import Types (AppState(..), Chapter(chapterTags, chapterTitle), ChapterView(..), EpubEnv)
import Types (AppState(..), Chapter(chapterTags, chapterTitle), ChapterView(..), EpubEnv, ChapterIndex(..))
-- Get an element from a list without crashing
safeGet :: Int -> [a] -> Maybe a
safeGet n xs
safeGet :: ChapterIndex -> [a] -> Maybe a
safeGet (ChapterIndex n) xs
| n < 0 = Nothing
| otherwise = case drop n xs of
(x : _) -> Just x
@ -21,7 +21,7 @@ initialState :: EpubEnv -> AppState
initialState env =
AppState
{ cEnv = env,
cIdx = 0,
cIdx = ChapterIndex 0,
cSpine = [],
bTitle = "Svitak - Loading...",
zoomlvl = 1.0,
@ -31,10 +31,10 @@ initialState env =
}
-- prepare the data for the UI
prepareView :: AppState -> Int -> Maybe ChapterView
prepareView :: AppState -> ChapterIndex -> Maybe ChapterView
prepareView st index = do
chapter <- safeGet index (cAllChapters st)
return $ ChapterView
pure $ ChapterView
{ viewTitle = bTitle st <> " - " <> chapterTitle chapter
, viewHtml = chapterTags chapter
, viewIdx = index

View file

@ -1,3 +1,5 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Types
( EpubEnv(..),
ChapterView(..),
@ -5,7 +7,10 @@ module Types
AppState(..),
UserAction(..),
EpubAction,
NavItem(..)
NavItem(..),
ChapterIndex(..),
InternalPath(..),
EpubType(..),
)
where
@ -15,6 +20,16 @@ import qualified Codec.Epub.Data.Metadata as DMeta
import qualified Codec.Epub.Data.Manifest as DMan
import Control.Concurrent.Async (Async)
import Control.Monad.Reader (ReaderT)
import Data.String (IsString)
newtype ChapterIndex = ChapterIndex Int
deriving (Show, Eq, Ord, Num)
newtype InternalPath = InternalPath FilePath
deriving (Show, Eq)
newtype EpubType = EpubType T.Text
deriving (Show,Eq, IsString)
data EpubEnv = EpubEnv
{ archive :: Archive,
@ -28,20 +43,20 @@ data EpubEnv = EpubEnv
data ChapterView = ChapterView
{ viewTitle :: T.Text,
viewHtml :: T.Text,
viewIdx :: Int
viewIdx :: ChapterIndex
}
data Chapter = Chapter
{ chapterTitle :: T.Text,
chapterTags :: T.Text,
chapterIdx :: Int
chapterIdx :: ChapterIndex
}
data AppState = AppState
{ cEnv :: EpubEnv,
cIdx :: Int,
cIdx :: ChapterIndex,
zoomlvl :: Double,
cSpine :: [(FilePath, T.Text)],
cSpine :: [(InternalPath, T.Text)],
bTitle :: T.Text,
cAllChapters :: [Chapter],
activeTask :: Maybe (Async ()),
@ -55,10 +70,10 @@ data UserAction
| PrevChapter
| ZoomIn
| ZoomOut
| LoadSpecific Int
| LoadSpecific ChapterIndex
data NavItem = NavItem
{ navLabel :: T.Text,
navPath :: FilePath,
navTypes :: [T.Text]
navPath :: InternalPath,
navTypes :: [EpubType]
} deriving (Show)

View file

@ -22,10 +22,10 @@ import qualified GI.WebKit as WebKit
import Persistence (loadLastRead, saveLastRead)
import State (initialState, prepareView)
import Text.HTML.Scalpel (Scraper, attr, chroots, text)
import Types (AppState (..), ChapterView (..), UserAction (..))
import Types (AppState (..), ChapterView (..), UserAction (..), ChapterIndex(..))
updateUI :: Gtk.ApplicationWindow -> WebKit.WebView -> ChapterView -> Int -> Int -> IO ()
updateUI window webView view currentIdx totalCount = do
updateUI :: Gtk.ApplicationWindow -> WebKit.WebView -> ChapterView -> ChapterIndex -> Int -> IO ()
updateUI window webView view (ChapterIndex currentIdx) totalCount = do
let ttext = T.pack (show (currentIdx + 1)) <> "/ " <> T.pack (show totalCount) <> ": " <> viewTitle view
Gtk.set window [#title := ttext]
WebKit.webViewLoadHtml webView (viewHtml view) Nothing
@ -46,9 +46,8 @@ handleAction stateTVar window webView action = do
NextChapter -> cIdx st + 1
PrevChapter -> cIdx st - 1
LoadSpecific i -> i
_ -> cIdx st
let safeIdx = max 0 (min (totalChapters - 1) nextIdx)
let safeIdx = max 0 (min (ChapterIndex (totalChapters - 1)) nextIdx)
case prepareView st safeIdx of
Nothing -> pure ()
@ -67,7 +66,7 @@ initAppServices :: EpubEnv -> TVar AppState -> (UserAction -> IO ()) -> IO ()
initAppServices env stateTVar actionHandler = do
_ <- async $ do
ispine <- runReaderT resolveSpine env
allChapters <- runReaderT (zipWithM getChapter ispine [0 ..]) env
allChapters <- runReaderT (zipWithM getChapter ispine (map ChapterIndex [0 ..])) env
let titles = metaTitles (eMetadata env)
let bookTitle = case find (\t -> titleType t == Just "main") titles of