Ryan Fox
6648fd840c
Soapbox does not seem to include mentions in the status text anymore. I wanted to add a "Replying to" option, but I have no clue how replies work.
337 lines
11 KiB
Elm
337 lines
11 KiB
Elm
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 "#" status.url), target "_blank" ]
|
|
[ Common.icon "time", text <| Common.formatDate status.created_at ]
|
|
, if showApp then
|
|
Common.appLink (baseBtnClasses ++ " applink") status.application
|
|
else
|
|
text ""
|
|
]
|
|
|
|
|
|
mentionView : Mention -> Html Msg
|
|
mentionView mention =
|
|
a
|
|
[ href mention.url
|
|
, class "mention" ]
|
|
[ text <| "@" ++ mention.username
|
|
]
|
|
|
|
|
|
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
|
|
, pollView status
|
|
, attachmentListView context 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" ]
|
|
[ text status.spoiler_text ]
|
|
, 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
|
|
, pollView status
|
|
, attachmentListView context 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" ])
|
|
[ text <| " " ++ if account.display_name == "" then
|
|
account.username
|
|
else
|
|
account.display_name]
|
|
, text " boosted"
|
|
]
|
|
, Lazy.lazy3 statusView context reblog clickOpen
|
|
]
|
|
|
|
Nothing ->
|
|
div statusAttribs
|
|
[ pin
|
|
, Common.accountAvatarLink False account
|
|
, div [ class "username" ]
|
|
[ a accountLinkAttributes
|
|
[ text (if account.display_name=="" then account.username else account.display_name)
|
|
, Common.accountAcctView True account
|
|
]
|
|
]
|
|
, Lazy.lazy2 statusContentView context status
|
|
]
|