diff --git a/docs/html/_config.yml b/docs/html/_config.yml index a14dddc8..95da5bc1 100755 --- a/docs/html/_config.yml +++ b/docs/html/_config.yml @@ -3,7 +3,7 @@ title: taskell tagline: Command-line Kanban board/task managment baseurl: "" locale: "en" -version: 1.2.2 +version: 1.2.3 destination: _site/public exclude: [deployment, Capfile, log, Gemfile, Gemfile.lock] diff --git a/package.yaml b/package.yaml index c8620d13..18111958 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: taskell -version: '1.2.2.0' +version: '1.2.3.0' category: CLI author: Mark Wales maintainer: mark@smallhadroncollider.com diff --git a/roadmap.md b/roadmap.md index 459583e0..26d258df 100644 --- a/roadmap.md +++ b/roadmap.md @@ -62,7 +62,6 @@ > Use a `# Title` at top of file and display title somewhere in taskell - Add tags/labels with `t` - URL field - plus config to run specific command when selected (e.g. `open -a Chrome.app #{url}`) -- Should change list numbering to letters when in move list mode - Redo functionality - Make trello token UX better * Open link automatically? @@ -79,10 +78,6 @@ ## In Progress -- Improve Trello checklist import - * ~Take checklist fetch errors into account~ - * ~Refactor code~ - * Use Reader to pass around trello token? ## Done @@ -218,3 +213,8 @@ * ~Add due date support~ * ~Add sub-tasks support~ * ~Add card summary support~ +- Improve Trello checklist import + * ~Take checklist fetch errors into account~ + * ~Refactor code~ + * ~Use Reader to pass around trello token?~ +- Should change list numbering to letters when in move list mode diff --git a/src/App.hs b/src/App.hs index 2e8287e5..f701ce7c 100644 --- a/src/App.hs +++ b/src/App.hs @@ -9,9 +9,9 @@ import Graphics.Vty (Mode(BracketedPaste), outputIface, supportsMode, setMode) import Graphics.Vty.Input.Events (Event(..)) import Data.Taskell.Lists (Lists) -import Data.Taskell.Date (currentDay, deadline) +import Data.Taskell.Date (currentDay) import Events.Actions (event) -import Events.State (State, Mode(..), continue, path, mode, io, current) +import Events.State (State, Mode(..), ModalType(..), continue, path, mode, io, current, lists) import IO.Config (Config, layout, generateAttrMap) import IO.Taskell (writeData) import UI.Draw (draw, chooseCursor) @@ -34,16 +34,25 @@ clearCache state = do invalidateCacheEntry (RNList li) invalidateCacheEntry (RNTask (li, ti)) +clearAllTitles :: State -> EventM ResourceName () +clearAllTitles state = do + let count = length (lists state) + let range = [0 .. (count - 1)] + void . sequence $ invalidateCacheEntry . RNList <$> range + void . sequence $ invalidateCacheEntry . (\x -> RNTask (x, -1)) <$> range + handleVtyEvent :: Config -> State -> Event -> EventM ResourceName (Next State) handleVtyEvent config previousState e = do let state = event e previousState case mode previousState of Search _ _ -> invalidateCache - _ -> return () + (Modal MoveTo) -> clearAllTitles previousState + _ -> return () case mode state of Shutdown -> Brick.halt state + (Modal MoveTo) -> clearAllTitles state >> next config state _ -> clearCache previousState >> clearCache state >> next config state -- App code @@ -63,9 +72,9 @@ appStart state = do go :: Config -> State -> IO () go config initial = do attrMap' <- const <$> generateAttrMap - deadlineFn <- deadline <$> currentDay + today <- currentDay let app = App { - appDraw = draw (layout config) deadlineFn + appDraw = draw (layout config) today , appChooseCursor = chooseCursor , appHandleEvent = handleEvent config , appStartEvent = appStart diff --git a/src/Config.hs b/src/Config.hs index 66f2f182..013ae23a 100644 --- a/src/Config.hs +++ b/src/Config.hs @@ -8,7 +8,7 @@ import ClassyPrelude import Data.FileEmbed (embedFile) version :: Text -version = "1.2.2" +version = "1.2.3" usage :: Text usage = decodeUtf8 $(embedFile "templates/usage.txt") diff --git a/src/Data/Taskell/Date.hs b/src/Data/Taskell/Date.hs index c8a0d61a..4a374e85 100644 --- a/src/Data/Taskell/Date.hs +++ b/src/Data/Taskell/Date.hs @@ -17,14 +17,17 @@ import ClassyPrelude import Data.Time (Day) import Data.Time.Clock (secondsToDiffTime) import Data.Time.Format (parseTimeM, formatTime) -import Data.Time.Calendar (diffDays) +import Data.Time.Calendar (toGregorian, diffDays) import Data.Time.LocalTime (TimeZone, utcToZonedTime, zonedTimeToLocalTime, localDay) data Deadline = Passed | Today | Tomorrow | ThisWeek | Plenty deriving (Show, Eq) type DeadlineFn = Day -> Deadline -dayToText :: Day -> Text -dayToText day = pack $ formatTime defaultTimeLocale "%d-%b" (UTCTime day (secondsToDiffTime 0)) +dayToText :: Day -> Day -> Text +dayToText today day = pack $ formatTime defaultTimeLocale format (UTCTime day (secondsToDiffTime 0)) + where (currentYear, _, _) = toGregorian today + (dateYear, _, _) = toGregorian day + format = if currentYear == dateYear then "%d-%b" else "%d-%b %Y" dayToOutput :: Day -> Text dayToOutput day = pack $ formatTime defaultTimeLocale "%Y-%m-%d" (UTCTime day (secondsToDiffTime 0)) diff --git a/src/IO/Taskell.hs b/src/IO/Taskell.hs index c39a941c..56429992 100644 --- a/src/IO/Taskell.hs +++ b/src/IO/Taskell.hs @@ -57,7 +57,7 @@ createTrello boardID path = do case maybeToken of Nothing -> return $ Output $ decodeUtf8 $(embedFile "templates/token.txt") Just trelloToken -> do - lists <- lift $ getCards trelloToken boardID + lists <- lift $ runReaderT (getCards boardID) trelloToken case lists of Left txt -> return $ Output txt Right ls -> do diff --git a/src/IO/Trello.hs b/src/IO/Trello.hs index 5db9a657..9e6aa522 100644 --- a/src/IO/Trello.hs +++ b/src/IO/Trello.hs @@ -11,12 +11,14 @@ import ClassyPrelude import Network.HTTP.Simple (parseRequest, httpBS, getResponseBody, getResponseStatusCode) import Data.Aeson -import IO.Trello.List (List, trelloListToList, cards) +import IO.Trello.List (List, trelloListToList, setCards, cards) import IO.Trello.Card (Card, idChecklists, setChecklists) import IO.Trello.ChecklistItem (ChecklistItem, checkItems) import Data.Taskell.Lists (Lists) import Data.Time.LocalTime (TimeZone, getCurrentTimeZone) +type ReaderTrelloToken a = ReaderT TrelloToken IO a + type TrelloToken = Text type TrelloBoardID = Text type TrelloChecklistID = Text @@ -27,10 +29,12 @@ key = "80dbcf6f88f62cc5639774e13342c20b" root :: Text root = "https://api.trello.com/1/" -fullURL :: Text -> TrelloToken -> String -fullURL uri token = unpack $ concat [root, uri, "&key=", key, "&token=", token] +fullURL :: Text -> ReaderTrelloToken String +fullURL uri = do + token <- ask + return . unpack $ concat [root, uri, "&key=", key, "&token=", token] -boardURL :: TrelloBoardID -> TrelloToken -> String +boardURL :: TrelloBoardID -> ReaderTrelloToken String boardURL board = fullURL $ concat [ "boards/", board, "/lists", "?cards=open", @@ -38,7 +42,7 @@ boardURL board = fullURL $ concat [ "&fields=id,name,cards" ] -checklistURL :: TrelloChecklistID -> TrelloToken -> String +checklistURL :: TrelloChecklistID -> ReaderTrelloToken String checklistURL checklist = fullURL $ concat [ "checklists/", checklist, "?fields=id", @@ -54,9 +58,10 @@ fetch url = do response <- httpBS request return (getResponseStatusCode response, getResponseBody response) -getChecklist :: TrelloToken -> TrelloChecklistID -> IO (Either Text [ChecklistItem]) -getChecklist token checklist = do - (status, body) <- fetch (checklistURL checklist token) +getChecklist :: TrelloChecklistID -> ReaderTrelloToken (Either Text [ChecklistItem]) +getChecklist checklist = do + url <- checklistURL checklist + (status, body) <- lift $ fetch url return $ case status of 200 -> case checkItems <$> decodeStrict body of @@ -65,35 +70,27 @@ getChecklist token checklist = do 429 -> Left "Too many checklists" _ -> Left $ tshow status ++ " error while fetching checklist " ++ checklist -updateCard :: TrelloToken -> Card -> IO (Either Text Card) -updateCard token card = do - let ids = idChecklists card - checklists <- sequence $ getChecklist token <$> ids - return $ setChecklists card . concat <$> sequence checklists +updateCard :: Card -> ReaderTrelloToken (Either Text Card) +updateCard card = (setChecklists card . concat <$>) . sequence <$> checklists + where checklists = sequence $ getChecklist <$> idChecklists card -updateList :: TrelloToken -> List -> IO (Either Text List) -updateList token l = do - let set c = l { cards = c } - cs <- sequence $ updateCard token <$> cards l - return $ set <$> sequence cs +updateList :: List -> ReaderTrelloToken (Either Text List) +updateList l = (setCards l <$>) . sequence <$> sequence (updateCard <$> cards l) -getChecklists :: TrelloToken -> [List] -> IO (Either Text [List]) -getChecklists token ls = do - lists <- sequence $ updateList token <$> ls - return $ sequence lists +getChecklists :: [List] -> ReaderTrelloToken (Either Text [List]) +getChecklists ls = sequence <$> sequence (updateList <$> ls) -getCards :: TrelloToken -> TrelloBoardID -> IO (Either Text Lists) -getCards token board = do - (status, body) <- fetch (boardURL board token) - timezone <- getCurrentTimeZone +getCards :: TrelloBoardID -> ReaderTrelloToken (Either Text Lists) +getCards board = do + url <- boardURL board + (status, body) <- lift $ fetch url + timezone <- lift getCurrentTimeZone putStrLn "Fetching from Trello..." case status of 200 -> case decodeStrict body of - Just raw -> do - lists <- getChecklists token raw - return $ trelloListsToLists timezone <$> lists + Just raw -> fmap (trelloListsToLists timezone) <$> getChecklists raw Nothing -> return $ Left "Could not parse response. Please file an Issue on GitHub." 404 -> return . Left $ "Could not find Trello board " ++ board ++ ". Make sure the ID is correct" 401 -> return . Left $ "You do not have permission to view Trello board " ++ board diff --git a/src/IO/Trello/List.hs b/src/IO/Trello/List.hs index 4b2b3a30..ecfa918c 100644 --- a/src/IO/Trello/List.hs +++ b/src/IO/Trello/List.hs @@ -2,6 +2,7 @@ {-# LANGUAGE DeriveGeneric, DeriveAnyClass #-} module IO.Trello.List ( List(..) + , setCards , trelloListToList ) where @@ -18,6 +19,9 @@ data List = List { , cards :: [Card] } deriving (Eq, Show, Generic, ToJSON, FromJSON) +setCards :: List -> [Card] -> List +setCards list cs = list { cards = cs } + trelloListToList :: TimeZone -> List -> TL.List trelloListToList tz ls = TL.List { TL.title = name ls diff --git a/src/UI/Draw.hs b/src/UI/Draw.hs index bced9c4b..d0535afc 100644 --- a/src/UI/Draw.hs +++ b/src/UI/Draw.hs @@ -7,11 +7,14 @@ module UI.Draw ( import ClassyPrelude +import Data.Char (ord, chr) import Data.Sequence (mapWithIndex) +import Control.Monad.Reader (runReader) import Brick -import Data.Taskell.Date (Day, DeadlineFn, dayToText) +import Data.Taskell.Date (Day, dayToText, deadline) +import Data.Taskell.Lists (Lists) import Data.Taskell.List (List, tasks, title) import Data.Taskell.Task (Task, description, hasSubTasks, countSubTasks, countCompleteSubTasks, summary, due) import Events.State (lists, current, mode, normalise) @@ -22,114 +25,157 @@ import UI.Modal (showModal) import UI.Theme import UI.Types (ResourceName(..)) -renderDate :: DeadlineFn -> Maybe Day -> Maybe (Widget ResourceName) -renderDate deadlineFn day = do - attr <- withAttr . dlToAttr . deadlineFn <$> day - widget <- txt . dayToText <$> day - return $ attr widget - +-- | Draw needs to know various pieces of information, so keep track of them in a record +data DrawState = DrawState { + dsLists :: Lists + , dsMode :: Mode + , dsLayout :: LayoutConfig + , dsToday :: Day + , dsCurrent :: Pointer + , dsField :: Maybe Field + , dsEditingTitle :: Bool +} + +-- | Use a Reader to pass around DrawState +type ReaderDrawState = ReaderT DrawState Identity + +-- | Takes a task's 'due' property and renders a date with appropriate styling (e.g. red if overdue) +renderDate :: Maybe Day -> ReaderDrawState (Maybe (Widget ResourceName)) +renderDate dueDay = do + today <- dsToday <$> ask -- get the value of `today` from DrawState + let attr = withAttr . dlToAttr . deadline today <$> dueDay -- create a `Maybe (Widget -> Widget)` attribute function + widget = txt . dayToText today <$> dueDay -- get the formatted due date `Maybe Text` + return $ attr <*> widget + +-- | Renders the appropriate completed sub task count e.g. "[2/3]" renderSubTaskCount :: Task -> Widget ResourceName -renderSubTaskCount t = str $ concat [ - "[" - , show $ countCompleteSubTasks t - , "/" - , show $ countSubTasks t - , "]" - ] - -indicators :: DeadlineFn -> Task -> Widget ResourceName -indicators deadlineFn t = hBox $ padRight (Pad 1) <$> catMaybes [ - const (txt "≡") <$> summary t - , bool Nothing (Just (renderSubTaskCount t)) (hasSubTasks t) - , renderDate deadlineFn (due t) - ] - -renderTask :: DeadlineFn -> Maybe Field -> Bool -> Pointer -> Int -> Int -> Task -> Widget ResourceName -renderTask deadlineFn f eTitle p li ti t = - cached name - . (if not eTitle && cur then visible else id) - . padBottom (Pad 1) - . (<=> withAttr disabledAttr after) - . withAttr (if cur then taskCurrentAttr else taskAttr) - $ if cur && not eTitle then widget' else widget - - where cur = (li, ti) == p - text = description t - after = indicators deadlineFn t - name = RNTask (li, ti) - widget = textField text - widget' = widgetFromMaybe widget f - -columnNumber :: Int -> Text -columnNumber i = if col >= 1 && col <= 9 then pack (show col) ++ ". " else "" - where col = i + 1 - -renderTitle :: Maybe Field -> Bool -> Pointer -> Int -> List -> Widget ResourceName -renderTitle f eTitle (p, i) li l = - if cur || p /= li || i == 0 - then visible title' - else title' - - where cur = p == li && eTitle - text = title l - col = txt $ columnNumber li - attr = if p == li then titleCurrentAttr else titleAttr - title' = padBottom (Pad 1) . withAttr attr . (col <+>) $ if cur then widget' else widget - widget = textField text - widget' = widgetFromMaybe widget f - -renderList :: LayoutConfig -> DeadlineFn -> Maybe Field -> Bool -> Pointer -> Int -> List -> Widget ResourceName -renderList layout deadlineFn f eTitle p li l = if fst p == li then visible list else list - where list = - (if not eTitle then cached (RNList li) else id) +renderSubTaskCount task = txt $ concat ["[" , tshow $ countCompleteSubTasks task , "/" , tshow $ countSubTasks task , "]"] + +-- | Renders the appropriate indicators: summary, sub task count, and due date +indicators :: Task -> ReaderDrawState (Widget ResourceName) +indicators task = do + dateWidget <- renderDate (due task) -- get the due date widget + return . hBox $ padRight (Pad 1) <$> catMaybes [ + const (txt "≡") <$> summary task -- show the summary indicator if one is set + , bool Nothing (Just (renderSubTaskCount task)) (hasSubTasks task) -- if it has subtasks, render the sub task count + , dateWidget + ] + +-- | Renders an individual task +renderTask :: Int -> Int -> Task -> ReaderDrawState (Widget ResourceName) +renderTask listIndex taskIndex task = do + eTitle <- dsEditingTitle <$> ask -- is the title being edited? (for visibility) + selected <- (== (listIndex, taskIndex)) . dsCurrent <$>ask -- is the current task selected? + taskField <- dsField <$> ask -- get the field, if it's being edited + after <- indicators task -- get the indicators widget + + let text = description task + name = RNTask (listIndex, taskIndex) + widget = textField text + widget' = widgetFromMaybe widget taskField + + return $ cached name + . (if selected && not eTitle then visible else id) + . padBottom (Pad 1) + . (<=> withAttr disabledAttr after) + . withAttr (if selected then taskCurrentAttr else taskAttr) + $ if selected && not eTitle then widget' else widget + + +-- | Gets the relevant column prefix - number in normal mode, letter in moveTo +columnPrefix :: Int -> Int -> ReaderDrawState Text +columnPrefix selectedList i = do + m <- dsMode <$> ask + if moveTo m + then do + let col = chr (i + ord 'a') + return $ if i /= selectedList && i >= 0 && i <= 26 then singleton col ++ ". " else "" + else do + let col = i + 1 + return $ if col >= 1 && col <= 9 then tshow col ++ ". " else "" + +-- | Renders the title for a list +renderTitle :: Int -> List -> ReaderDrawState (Widget ResourceName) +renderTitle listIndex list = do + (selectedList, selectedTask) <- dsCurrent <$> ask + editing <- (selectedList == listIndex &&) . dsEditingTitle <$> ask + titleField <- dsField <$> ask + col <- txt <$> columnPrefix selectedList listIndex + + let text = title list + attr = if selectedList == listIndex then titleCurrentAttr else titleAttr + widget = textField text + widget' = widgetFromMaybe widget titleField + title' = padBottom (Pad 1) . withAttr attr . (col <+>) $ if editing then widget' else widget + + return $ if editing || selectedList /= listIndex || selectedTask == 0 then visible title' else title' + +-- | Renders a list +renderList :: Int -> List -> ReaderDrawState (Widget ResourceName) +renderList listIndex list = do + layout <- dsLayout <$> ask + eTitle <- dsEditingTitle <$> ask + titleWidget <- renderTitle listIndex list + (currentList, _) <- dsCurrent <$> ask + taskWidgets <- sequence $ renderTask listIndex `mapWithIndex` tasks list + + let widget = (if not eTitle then cached (RNList listIndex) else id) . padLeftRight (columnPadding layout) . hLimit (columnWidth layout) - . viewport (RNList li) Vertical + . viewport (RNList listIndex) Vertical . vBox - . (renderTitle f eTitle p li l :) - . toList - $ renderTask deadlineFn f eTitle p li `mapWithIndex` tasks l - -searchImage :: LayoutConfig -> State -> Widget ResourceName -> Widget ResourceName -searchImage layout s i = case mode s of - Search ent f -> - let attr = if ent then taskCurrentAttr else taskAttr - in - i <=> ( - withAttr attr - . padTopBottom 1 - . padLeftRight (columnPadding layout) - $ txt "/" <+> field f - ) - _ -> i - -main :: LayoutConfig -> DeadlineFn -> State -> Widget ResourceName -main layout deadlineFn s = - searchImage layout s - . viewport RNLists Horizontal - . padTopBottom 1 - . hBox - . toList - $ renderList layout deadlineFn (getField s) (editingTitle s) (current s) `mapWithIndex` ls - - where ls = lists s - -getField :: State -> Maybe Field -getField state = case mode state of - Insert _ _ f -> Just f - _ -> Nothing - - -editingTitle :: State -> Bool -editingTitle state = case mode state of - Insert IList _ _ -> True - _ -> False + . (titleWidget :) + $ toList taskWidgets + + return $ if currentList == listIndex then visible widget else widget + +-- | Renders the search area +renderSearch :: Widget ResourceName -> ReaderDrawState (Widget ResourceName) +renderSearch mainWidget = do + m <- dsMode <$> ask + case m of + Search editing searchField -> do + colPad <- columnPadding . dsLayout <$> ask + let attr = withAttr $ if editing then taskCurrentAttr else taskAttr + let widget = attr . padTopBottom 1 . padLeftRight colPad $ txt "/" <+> field searchField + return $ mainWidget <=> widget + _ -> return mainWidget + +-- | Renders the main widget +main :: ReaderDrawState (Widget ResourceName) +main = do + ls <- dsLists <$> ask + listWidgets <- toList <$> sequence (renderList `mapWithIndex` ls) + let mainWidget = viewport RNLists Horizontal . padTopBottom 1 $ hBox listWidgets + renderSearch mainWidget + +getField :: Mode -> Maybe Field +getField (Insert _ _ f) = Just f +getField _ = Nothing + +editingTitle :: Mode -> Bool +editingTitle (Insert IList _ _) = True +editingTitle _ = False + +moveTo :: Mode -> Bool +moveTo (Modal MoveTo) = True +moveTo _ = False -- draw -draw :: LayoutConfig -> DeadlineFn -> State -> [Widget ResourceName] -draw layout deadlineFn state = - let s = normalise state in - showModal s deadlineFn [main layout deadlineFn s] +draw :: LayoutConfig -> Day -> State -> [Widget ResourceName] +draw layout today state = + showModal normalisedState today [runReader main DrawState { + dsLists = lists normalisedState + , dsMode = stateMode + , dsLayout = layout + , dsToday = today + , dsField = getField stateMode + , dsCurrent = current normalisedState + , dsEditingTitle = editingTitle stateMode + }] + + where normalisedState = normalise state + stateMode = mode state -- cursors chooseCursor :: State -> [CursorLocation ResourceName] -> Maybe (CursorLocation ResourceName) diff --git a/src/UI/Modal.hs b/src/UI/Modal.hs index 669afbfb..21a84a12 100644 --- a/src/UI/Modal.hs +++ b/src/UI/Modal.hs @@ -9,7 +9,7 @@ import Brick import Brick.Widgets.Center import Brick.Widgets.Border -import Data.Taskell.Date (DeadlineFn) +import Data.Taskell.Date (Day) import Events.State (State, Mode(..), ModalType(..), mode) import UI.Field (textField) import UI.Modal.Help (help) @@ -32,9 +32,9 @@ surround (title, widget) = where t = padBottom (Pad 1) . withAttr titleAttr $ textField title -showModal :: State -> DeadlineFn -> [Widget ResourceName] -> [Widget ResourceName] -showModal s deadlineFn view = case mode s of +showModal :: State -> Day -> [Widget ResourceName] -> [Widget ResourceName] +showModal s today view = case mode s of Modal Help -> surround help : view - Modal Detail {} -> surround (detail s deadlineFn) : view + Modal Detail {} -> surround (detail s today) : view Modal MoveTo -> surround (moveTo s) : view _ -> view diff --git a/src/UI/Modal/Detail.hs b/src/UI/Modal/Detail.hs index 4ab84952..c0fbcf86 100644 --- a/src/UI/Modal/Detail.hs +++ b/src/UI/Modal/Detail.hs @@ -10,7 +10,7 @@ import Data.Sequence (mapWithIndex) import Brick -import Data.Taskell.Date (DeadlineFn, dayToOutput) +import Data.Taskell.Date (Day, dayToOutput, deadline) import Data.Taskell.Task (Task, SubTask, description, subTasks, name, complete, summary, due) import Events.State (State, getCurrentTask) import Events.State.Types (DetailItem(..)) @@ -40,18 +40,18 @@ renderSummary f i task = padTop (Pad 1) $ padBottom (Pad 2) w' DetailDescription -> visible $ widgetFromMaybe w f _ -> w -renderDate :: DeadlineFn -> Maybe Field -> DetailItem -> Task -> Widget ResourceName -renderDate deadlineFn field item task = case item of +renderDate :: Day -> Maybe Field -> DetailItem -> Task -> Widget ResourceName +renderDate today field item task = case item of DetailDate -> visible $ prefix <+> widgetFromMaybe widget field _ -> case day of - Just d -> prefix <+> withAttr (dlToAttr (deadlineFn d)) widget + Just d -> prefix <+> withAttr (dlToAttr (deadline today d)) widget Nothing -> emptyWidget where day = due task prefix = txt "Due: " widget = textField $ maybe "" dayToOutput day -detail :: State -> DeadlineFn -> (Text, Widget ResourceName) -detail state deadlineFn = fromMaybe ("Error", txt "Oops") $ do +detail :: State -> Day -> (Text, Widget ResourceName) +detail state today = fromMaybe ("Error", txt "Oops") $ do task <- getCurrentTask state i <- getCurrentItem state let f = getField state @@ -60,4 +60,4 @@ detail state deadlineFn = fromMaybe ("Error", txt "Oops") $ do w | null sts = withAttr disabledAttr $ txt "No sub-tasks" | otherwise = vBox . toList $ renderSubTask f i `mapWithIndex` sts - return (description task, renderDate deadlineFn f i task <=> renderSummary f i task <=> w) + return (description task, renderDate today f i task <=> renderSummary f i task <=> w) diff --git a/test/Data/Taskell/DateTest.hs b/test/Data/Taskell/DateTest.hs index 37a25187..5b9f1a24 100644 --- a/test/Data/Taskell/DateTest.hs +++ b/test/Data/Taskell/DateTest.hs @@ -26,12 +26,26 @@ test_date = (Just "2018-05-18") (dayToOutput <$> fromGregorianValid 2018 05 18) ) - , testCase "dayToText" ( - assertEqual - "Date in 18-May format" - (Just "18-May") - (dayToText <$> fromGregorianValid 2018 05 18) - ) + , testGroup "dayToText" [ + testCase "same year" ( + assertEqual + "Date in 18-May format" + (Just "18-May") + (dayToText <$> fromGregorianValid 2018 08 18 <*> fromGregorianValid 2018 05 18) + ) + , testCase "different year" ( + assertEqual + "Date in 18-May 2019 format" + (Just "18-May 2019") + (dayToText <$> fromGregorianValid 2018 08 18 <*> fromGregorianValid 2019 05 18) + ) + , testCase "different year" ( + assertEqual + "Date in 18-May 2017 format" + (Just "18-May 2017") + (dayToText <$> fromGregorianValid 2018 08 18 <*> fromGregorianValid 2017 05 18) + ) + ] , testCase "textToDay" ( assertEqual "A valid Day"