module View.Status exposing ( statusView , statusActionsView , statusEntryView ) import Html exposing (..) import Html.Attributes exposing (..) import Html.Keyed as Keyed import Html.Lazy as Lazy import Mastodon.Helper exposing (extractStatusId) import Mastodon.Model exposing (..) import Types exposing (..) import View.Common as Common import View.Events exposing (..) import View.Formatter exposing (formatContent) type alias CurrentUser = Account attachmentPreview : String -> Maybe Bool -> List Attachment -> Attachment -> Html Msg attachmentPreview context sensitive attachments ({ url, preview_url } as attachment) = let nsfw = case sensitive of Just sensitive -> sensitive Nothing -> False attId = "att" ++ attachment.id ++ context previewUrl = if preview_url == "" then url else preview_url media = case attachment.type_ of "audio" -> a [ if nsfw then class "attachment-image nsfw" else class "attachment-image" ] [ audio [ controls True , style [("width", "100%")] ] [ source [ src url ] [] ] ] _ -> a [ if nsfw then class "attachment-image nsfw" else class "attachment-image" , href url , onClickWithPreventAndStop <| ViewerEvent (OpenViewer attachments attachment) ] [ img [ src previewUrl ] [] ] in li [ class "attachment-entry" ] <| if nsfw then [ input [ onClickWithStop NoOp, type_ "radio", id attId ] [] , label [ onClickWithStop NoOp, for attId ] [ text "Sensitive content" , br [] [] , br [] [] , text "click to show attachment" ] , media ] else [ media ] attachmentListView : String -> Status -> Html Msg attachmentListView context { media_attachments, sensitive } = let keyedEntry attachments attachment = ( attachment.id , attachmentPreview context sensitive attachments attachment ) in case media_attachments of [] -> text "" attachments -> Keyed.ul [ class "attachments" ] <| List.map (keyedEntry attachments) attachments pollView : Status -> Html Msg pollView status = let pollStatus poll = if poll.voted then text "Voted" else if poll.expired then text "Expired" else text "Not voted" optionPercentage option = 100*option.votes_count//status.poll.votes_count optionPercentageText option = span [ class "poll-percentage" ] [ text <| (toString (optionPercentage option))++"%" ] optionBar option = span [ class "poll-bar" , style [("width", toString (optionPercentage option)++"%")] ] [] optionEntry option = li [] [ optionPercentageText option , text option.title , optionBar option ] in if status.poll.id == PollId("") then text "" else div [ class "poll" ] [ ul [] <| List.map optionEntry status.poll.options , span [ class "poll-footer" ] [ text <| (toString status.poll.votes_count)++" votes" , text " • " , pollStatus status.poll ] ] statusActionsView : Status -> CurrentUser -> Bool -> Html Msg statusActionsView status currentUser showApp = let sourceStatus = Mastodon.Helper.extractReblog status baseBtnClasses = "btn btn-sm btn-default" ( reblogClasses, reblogEvent ) = case status.reblogged of Just True -> ( baseBtnClasses ++ " reblogged", UnreblogStatus sourceStatus ) _ -> ( baseBtnClasses, ReblogStatus sourceStatus ) ( favClasses, favEvent ) = case status.favourited of Just True -> ( baseBtnClasses ++ " favourited", RemoveFavorite sourceStatus ) _ -> ( baseBtnClasses, AddFavorite sourceStatus ) in div [ class "btn-group actions" ] [ a [ class baseBtnClasses , onClickWithPreventAndStop <| DraftEvent (UpdateReplyTo status) ] [ Common.icon "share-alt" ] , if status.visibility == "private" then span [ class <| reblogClasses ++ " disabled" ] [ span [ title "Private" ] [ Common.icon "lock" ] ] else if status.visibility == "direct" then span [ class <| reblogClasses ++ " disabled" ] [ span [ title "Direct" ] [ Common.icon "envelope" ] ] else a [ class reblogClasses, onClickWithPreventAndStop reblogEvent ] [ Common.icon "fire", text (toString sourceStatus.reblogs_count) ] , a [ class favClasses, onClickWithPreventAndStop favEvent ] [ Common.icon "star", text (toString sourceStatus.favourites_count) ] , if Mastodon.Helper.sameAccount sourceStatus.account currentUser then a [ class <| baseBtnClasses ++ " btn-delete" , href "" , onClickWithPreventAndStop <| AskConfirm "Are you sure you want to delete this toot?" (DeleteStatus sourceStatus.id) NoOp ] [ Common.icon "trash" ] else text "" , a [ class baseBtnClasses, href (Maybe.withDefault "#" sourceStatus.url), target "_blank" ] [ Common.icon "time", text <| Common.formatDate sourceStatus.created_at ] , if showApp then Common.appLink (baseBtnClasses ++ " applink") sourceStatus.application else text "" ] mentionView : Mention -> Html Msg mentionView mention = a [ href ("#account/"++mention.id) , class "mention" ] [ text <| "@" ++ mention.username ] quoteView : Status -> Html Msg quoteView status = let quote = Mastodon.Helper.extractQuote status in case quote of Just (Quote quote) -> div [ class "status-quote" ] [ statusView "quote" quote True ] Nothing -> text "" mentionsView : List Mention -> Html Msg mentionsView mentions = let mentionLinks = List.map mentionView mentions in if (List.isEmpty mentions) then text "" else div [ class "status-mentions" ] ( List.append [ text "Mentioning" ] mentionLinks ) statusContentView : String -> Status -> Html Msg statusContentView context status = case status.spoiler_text of "" -> div [ class "status-text" ] [ mentionsView status.mentions , div [] <| formatContent status.content status.mentions status.emojis , pollView status , attachmentListView context status , quoteView status ] spoiler -> -- Note: Spoilers are dealt with using pure CSS. let statusId = "spoiler" ++ extractStatusId status.id ++ context in div [ class "status-text spoiled" ] [ div [ class "spoiler" ] <| formatContent status.spoiler_text status.mentions status.emojis , input [ onClickWithStop NoOp, type_ "checkbox", id statusId, class "spoiler-toggler" ] [] , label [ onClickWithStop NoOp, for statusId ] [ text "Reveal content" ] , div [ class "spoiled-content" ] [ mentionsView status.mentions , div [] <| formatContent status.content status.mentions status.emojis , pollView status , attachmentListView context status , quoteView status ] ] statusEntryView : String -> String -> CurrentUser -> Status -> Html Msg statusEntryView context className currentUser status = let nsfwClass = case status.sensitive of Just True -> "nsfw" _ -> "" liAttributes = [ class <| "list-group-item " ++ className ++ " " ++ nsfwClass ] ++ if context == "thread" then [ id <| "thread-status-" ++ extractStatusId status.id ] else [] in li liAttributes [ Lazy.lazy3 statusView context status (className /= "thread-target") , Lazy.lazy3 statusActionsView status currentUser (className == "thread-target") ] statusView : String -> Status -> Bool -> Html Msg statusView context ({ account, content, media_attachments, reblog, mentions, pinned } as status) clickOpen = let statusAttribs = if clickOpen then [ class "status" , onClickWithStop <| OpenThread status ] else [ class "status" ] accountLinkAttributes = [ href <| "#account/" ++ account.id ] pin = if pinned then p [ class "status-info" ] [ Common.icon "pushpin" , text " Pinned status" ] else text "" in case reblog of Just (Reblog reblog) -> div [ class "reblog" ] [ p [ class "status-info" ] [ Common.icon "fire" , a (accountLinkAttributes ++ [ class "reblogger" ]) [ span [ class "display-name" ] <| Common.accountDisplayNameRich account ] , text "boosted" ] , Lazy.lazy3 statusView context reblog clickOpen ] Nothing -> div statusAttribs [ pin , Common.accountAvatarLink False account , div [ class "username" ] [ a accountLinkAttributes [ span [ class "display-name" ] <| Common.accountDisplayNameRich account , Common.accountAcctView True account ] ] , Lazy.lazy2 statusContentView context status ]