matterhorn-90000.1.0: src/Matterhorn/Windows/ViewMessage.hs
module Matterhorn.Windows.ViewMessage
( viewMessageWindowTemplate
, viewMessageKeybindings
, viewMessageKeyHandlers
, viewMessageReactionsKeybindings
, viewMessageReactionsKeyHandlers
)
where
import Prelude ()
import Matterhorn.Prelude
import Brick
import Brick.Keybindings
import Brick.Widgets.Border
import qualified Data.Set as S
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Foldable as F
import qualified Graphics.Vty as Vty
import Lens.Micro.Platform ( to )
import Network.Mattermost.Types ( TeamId, Post (postId) )
import Matterhorn.Constants
import Matterhorn.Themes
import Matterhorn.Types
import Matterhorn.Types.RichText ( Inline(EUser) )
import Matterhorn.Draw.RichText
import Matterhorn.Draw.Messages ( renderMessage, MessageData(..), printableNameForUserRef )
-- | The template for "View Message" windows triggered by message
-- selection mode.
viewMessageWindowTemplate :: TeamId -> TabbedWindowTemplate ChatState MH Name ViewMessageWindowTab
viewMessageWindowTemplate tId =
TabbedWindowTemplate { twtEntries = [ messageEntry tId
, reactionsEntry tId
, authorInfoEntry tId
]
, twtTitle = const $ txt "View Message"
}
messageEntry :: TeamId -> TabbedWindowEntry ChatState MH Name ViewMessageWindowTab
messageEntry tId =
TabbedWindowEntry { tweValue = VMTabMessage
, tweRender = renderTab tId
, tweHandleEvent = handleEvent tId
, tweTitle = tabTitle
, tweShowHandler = onShow tId
}
reactionsEntry :: TeamId -> TabbedWindowEntry ChatState MH Name ViewMessageWindowTab
reactionsEntry tId =
TabbedWindowEntry { tweValue = VMTabReactions
, tweRender = renderTab tId
, tweHandleEvent = handleEvent tId
, tweTitle = tabTitle
, tweShowHandler = onShow tId
}
authorInfoEntry :: TeamId -> TabbedWindowEntry ChatState MH Name ViewMessageWindowTab
authorInfoEntry tId =
TabbedWindowEntry { tweValue = VMTabAuthorInfo
, tweRender = renderTab tId
, tweHandleEvent = handleEvent tId
, tweTitle = tabTitle
, tweShowHandler = onShow tId
}
tabTitle :: ViewMessageWindowTab -> Bool -> T.Text
tabTitle VMTabMessage _ = "Message"
tabTitle VMTabReactions _ = "Reactions"
tabTitle VMTabAuthorInfo _ = "Author"
-- When we show the tabs, we need to reset the viewport scroll position
-- for viewports in that tab. This is because an older View Message
-- window used the same handle for the viewport and we don't want that
-- old state affecting this window. This also means that switching tabs
-- in an existing window resets this state, too.
onShow :: TeamId -> ViewMessageWindowTab -> MH ()
onShow tId VMTabMessage = resetVp $ ViewMessageArea tId
onShow tId VMTabReactions = resetVp $ ViewMessageReactionsArea tId
onShow tId VMTabAuthorInfo = resetVp $ ViewMessageAuthorArea tId
resetVp :: Name -> MH ()
resetVp n = do
let vs = viewportScroll n
mh $ do
vScrollToBeginning vs
hScrollToBeginning vs
renderTab :: TeamId -> ViewMessageWindowTab -> ChatState -> Widget Name
renderTab tId tab cs =
let mLatestMessage = case cs^.csTeam(tId).tsViewedMessage of
Nothing -> error "BUG: no message to show, please report!"
Just (m, _) -> getLatestMessage cs tId m
in case mLatestMessage of
Nothing -> emptyWidget
Just latestMessage ->
case tab of
VMTabMessage -> viewMessageBox cs tId latestMessage
VMTabReactions -> reactionsText cs tId latestMessage
VMTabAuthorInfo -> authorInfo cs tId latestMessage
getLatestMessage :: ChatState -> TeamId -> Message -> Maybe Message
getLatestMessage cs tId m =
case m^.mMessageId of
Nothing -> Just m
Just mId -> do
cId <- cs^.csCurrentChannelId(tId)
chan <- cs^?csChannel(cId)
findMessage mId $ chan^.ccMessageInterface.miMessages
handleEvent :: TeamId -> ViewMessageWindowTab -> Vty.Event -> MH ()
handleEvent tId VMTabMessage =
void . mhHandleKeyboardEvent (viewMessageKeybindings tId)
handleEvent tId VMTabReactions =
void . mhHandleKeyboardEvent (viewMessageReactionsKeybindings tId)
handleEvent _ VMTabAuthorInfo =
const $ return ()
authorInfo :: ChatState -> TeamId -> Message -> Widget Name
authorInfo cs tId m =
let mkPairs u = [ ("Username:", _uiName u)
, ("Nickname:", fromMaybe "(none)" $ _uiNickName u)
, ("Full name:", _uiFirstName u <> " " <> _uiLastName u)
]
renderPair (label, value) =
txt label <+> padBottom (Pad 1) (padLeft (Pad 1) (withDefAttr clientEmphAttr $ txt value))
in case _mUser m of
NoUser -> txt "No author."
UserOverride _ label -> txt $ "User: " <> label
UserI _ uId ->
case userById uId cs of
Nothing ->
txt "Author info not loaded yet."
Just author ->
viewport (ViewMessageAuthorArea tId) Vertical $
vBox $ renderPair <$> mkPairs author
reactionsText :: ChatState -> TeamId -> Message -> Widget Name
reactionsText st tId m = viewport vpName Vertical body
where
vpName = ViewMessageReactionsArea tId
body = case null reacList of
True -> txt "This message has no reactions."
False -> vBox $ mkEntry <$> reacList
reacList = M.toList (m^.mReactions)
mkEntry (reactionName, userIdSet) =
let count = str $ "(" <> show (S.size userIdSet) <> ")"
name = withDefAttr emojiAttr $ txt $ ":" <> reactionName <> ":"
clickableName = makeClickableName name reactionName userIdSet
usernameList = usernameText userIdSet
in (clickableName <+> (padLeft (Pad 1) count)) <=>
(padLeft (Pad 2) usernameList)
hs = getHighlightSet st tId
clickableUsernames i (EUser un) =
Just $ ClickableUsername Nothing vpName i un
clickableUsernames _ _ =
Nothing
usernameText uids =
renderText' Nothing (myUsername st) hs (Just clickableUsernames) $
T.intercalate ", " $
fmap addUserSigil $
catMaybes (lookupUsername <$> F.toList uids)
lookupUsername uid = usernameForUserId uid st
makeName e us = do
pid <- postId <$> m^.mOriginalPost
Just $ ClickableReaction pid vpName e us
makeClickableName w e us =
case makeName e us of
Just n -> clickable n w
Nothing -> w
viewMessageBox :: ChatState -> TeamId -> Message -> Widget Name
viewMessageBox st tId msg =
let maybeWarn = if not (msg^.mDeleted) then id else warn
warn w = vBox [w, hBorder, deleteWarning]
deleteWarning = withDefAttr errorMessageAttr $
txtWrap $ "Alert: this message has been deleted and " <>
"will no longer be accessible once this window " <>
"is closed."
mkBody vpWidth =
let hs = getHighlightSet st tId
parent = case msg^.mInReplyToMsg of
NotAReply -> Nothing
InReplyTo pId -> getMessageForPostId st pId
md = MessageData { mdEditThreshold = Nothing
, mdShowOlderEdits = False
, mdMessage = msg
, mdUserName = msg^.mUser.to (printableNameForUserRef st)
, mdParentMessage = parent
, mdParentUserName = parent >>= (^.mUser.to (printableNameForUserRef st))
, mdRenderReplyParent = True
, mdHighlightSet = hs
, mdIndentBlocks = True
, mdThreadState = NoThread
, mdShowReactions = False
, mdMessageWidthLimit = Just vpWidth
, mdMyUsername = myUsername st
, mdMyUserId = myUserId st
, mdWrapNonhighlightedCodeBlocks = False
, mdTruncateVerbatimBlocks = Nothing
, mdClickableNameTag = ViewMessageArea tId
, mdRenderReplyIndent = False
}
in renderMessage md
in Widget Greedy Greedy $ do
ctx <- getContext
render $ maybeWarn $ viewport (ViewMessageArea tId) Both $ mkBody (ctx^.availWidthL)
viewMessageKeybindings :: TeamId -> KeyConfig KeyEvent -> KeyDispatcher KeyEvent MH
viewMessageKeybindings tId kc = unsafeKeyDispatcher kc (viewMessageKeyHandlers tId)
viewMessageKeyHandlers :: TeamId -> [MHKeyEventHandler]
viewMessageKeyHandlers tId =
let vs = viewportScroll . ViewMessageArea
in [ onEvent PageUpEvent "Page up" $ do
mh $ vScrollBy (vs tId) (-1 * pageAmount)
, onEvent PageDownEvent "Page down" $ do
mh $ vScrollBy (vs tId) pageAmount
, onEvent PageLeftEvent "Page left" $ do
mh $ hScrollBy (vs tId) (-2 * pageAmount)
, onEvent PageRightEvent "Page right" $ do
mh $ hScrollBy (vs tId) (2 * pageAmount)
, onEvent ScrollUpEvent "Scroll up" $ do
mh $ vScrollBy (vs tId) (-1)
, onEvent ScrollDownEvent "Scroll down" $ do
mh $ vScrollBy (vs tId) 1
, onEvent ScrollLeftEvent "Scroll left" $ do
mh $ hScrollBy (vs tId) (-1)
, onEvent ScrollRightEvent "Scroll right" $ do
mh $ hScrollBy (vs tId) 1
, onEvent ScrollBottomEvent "Scroll to the end of the message" $ do
mh $ vScrollToEnd (vs tId)
, onEvent ScrollTopEvent "Scroll to the beginning of the message" $ do
mh $ vScrollToBeginning (vs tId)
]
viewMessageReactionsKeybindings :: TeamId -> KeyConfig KeyEvent -> KeyDispatcher KeyEvent MH
viewMessageReactionsKeybindings tId kc = unsafeKeyDispatcher kc (viewMessageReactionsKeyHandlers tId)
viewMessageReactionsKeyHandlers :: TeamId -> [MHKeyEventHandler]
viewMessageReactionsKeyHandlers tId =
let vs = viewportScroll . ViewMessageReactionsArea
in [ onEvent PageUpEvent "Page up" $ do
mh $ vScrollBy (vs tId) (-1 * pageAmount)
, onEvent PageDownEvent "Page down" $ do
mh $ vScrollBy (vs tId) pageAmount
, onEvent ScrollUpEvent "Scroll up" $ do
mh $ vScrollBy (vs tId) (-1)
, onEvent ScrollDownEvent "Scroll down" $ do
mh $ vScrollBy (vs tId) 1
, onEvent ScrollBottomEvent "Scroll to the end of the reactions list" $ do
mh $ vScrollToEnd (vs tId)
, onEvent ScrollTopEvent "Scroll to the beginning of the reactions list" $ do
mh $ vScrollToBeginning (vs tId)
]