Fix #23: Render user mentions in the second column. (#31)

* Render user mentions in the second column.
* Add a timeline type toggler.
This commit is contained in:
Nicolas Perriault 2017-04-22 16:39:19 +02:00 committed by GitHub
parent b10761a413
commit 440806fa80
5 changed files with 236 additions and 85 deletions

View File

@ -3,7 +3,7 @@ body {
} }
.status { .status {
min-height: 75px; min-height: 50px;
clear: both; clear: both;
} }
@ -11,9 +11,18 @@ body {
background: #493438; background: #493438;
} }
.reblog > p:first-of-type { .reblog > p:first-of-type,
.notification > p:first-of-type {
color: #999; color: #999;
} }
.reblog > p:first-of-type > a,
.notification > p:first-of-type > a {
color: #ccc;
}
.notification.follow > p {
margin-bottom: 0;
}
.panel-heading { .panel-heading {
font-weight: bold; font-weight: bold;
@ -53,7 +62,7 @@ body {
color: #efefef; color: #efefef;
} }
.status-text a, .u-url, .mention, .hashtag, .tag { .status-text a, .u-url, .status .mention, .hashtag, .tag {
color: #9baec8; color: #9baec8;
} }

View File

@ -7,6 +7,7 @@ module Mastodon
, Client , Client
, Error(..) , Error(..)
, Mention , Mention
, Notification
, Reblog(..) , Reblog(..)
, Status , Status
, StatusRequestBody , StatusRequestBody
@ -17,8 +18,9 @@ module Mastodon
, getAuthorizationUrl , getAuthorizationUrl
, getAccessToken , getAccessToken
, fetchAccount , fetchAccount
, fetchPublicTimeline
, fetchLocalTimeline , fetchLocalTimeline
, fetchNotifications
, fetchPublicTimeline
, fetchUserTimeline , fetchUserTimeline
, postStatus , postStatus
, send , send
@ -125,6 +127,22 @@ type alias Mention =
} }
type alias Notification =
{-
- id: The notification ID
- type_: One of: "mention", "reblog", "favourite", "follow"
- created_at: The time the notification was created
- account: The Account sending the notification to the user
- status: The Status associated with the notification, if applicable
-}
{ id : Int
, type_ : String
, created_at : String
, account : Account
, status : Maybe Status
}
type alias Tag = type alias Tag =
{ name : String { name : String
, url : String , url : String
@ -287,6 +305,16 @@ mentionDecoder =
|> Pipe.required "acct" Decode.string |> Pipe.required "acct" Decode.string
notificationDecoder : Decode.Decoder Notification
notificationDecoder =
Pipe.decode Notification
|> Pipe.required "id" Decode.int
|> Pipe.required "type" Decode.string
|> Pipe.required "created_at" Decode.string
|> Pipe.required "account" accountDecoder
|> Pipe.optional "status" (Decode.nullable statusDecoder) Nothing
tagDecoder : Decode.Decoder Tag tagDecoder : Decode.Decoder Tag
tagDecoder = tagDecoder =
Pipe.decode Tag Pipe.decode Tag
@ -450,17 +478,22 @@ fetchAccount client accountId =
fetchUserTimeline : Client -> Request (List Status) fetchUserTimeline : Client -> Request (List Status)
fetchUserTimeline client = fetchUserTimeline client =
fetch client "/api/v1/timelines/home" (Decode.list statusDecoder) fetch client "/api/v1/timelines/home" <| Decode.list statusDecoder
fetchLocalTimeline : Client -> Request (List Status) fetchLocalTimeline : Client -> Request (List Status)
fetchLocalTimeline client = fetchLocalTimeline client =
fetch client "/api/v1/timelines/public?local=true" (Decode.list statusDecoder) fetch client "/api/v1/timelines/public?local=true" <| Decode.list statusDecoder
fetchPublicTimeline : Client -> Request (List Status) fetchPublicTimeline : Client -> Request (List Status)
fetchPublicTimeline client = fetchPublicTimeline client =
fetch client "/api/v1/timelines/public" (Decode.list statusDecoder) fetch client "/api/v1/timelines/public" <| Decode.list statusDecoder
fetchNotifications : Client -> Request (List Notification)
fetchNotifications client =
fetch client "/api/v1/notifications" <| Decode.list notificationDecoder
postStatus : Client -> StatusRequestBody -> Request Status postStatus : Client -> StatusRequestBody -> Request Status

View File

@ -25,13 +25,15 @@ type Msg
| AppRegistered (Result Mastodon.Error Mastodon.AppRegistration) | AppRegistered (Result Mastodon.Error Mastodon.AppRegistration)
| DraftEvent DraftMsg | DraftEvent DraftMsg
| LocalTimeline (Result Mastodon.Error (List Mastodon.Status)) | LocalTimeline (Result Mastodon.Error (List Mastodon.Status))
| PublicTimeline (Result Mastodon.Error (List Mastodon.Status)) | Notifications (Result Mastodon.Error (List Mastodon.Notification))
| OnLoadUserAccount Int | OnLoadUserAccount Int
| PublicTimeline (Result Mastodon.Error (List Mastodon.Status))
| Register | Register
| ServerChange String | ServerChange String
| StatusPosted (Result Mastodon.Error Mastodon.Status) | StatusPosted (Result Mastodon.Error Mastodon.Status)
| SubmitDraft | SubmitDraft
| UrlChange Navigation.Location | UrlChange Navigation.Location
| UseGlobalTimeline Bool
| UserAccount (Result Mastodon.Error Mastodon.Account) | UserAccount (Result Mastodon.Error Mastodon.Account)
| UserTimeline (Result Mastodon.Error (List Mastodon.Status)) | UserTimeline (Result Mastodon.Error (List Mastodon.Status))
@ -43,10 +45,12 @@ type alias Model =
, userTimeline : List Mastodon.Status , userTimeline : List Mastodon.Status
, localTimeline : List Mastodon.Status , localTimeline : List Mastodon.Status
, publicTimeline : List Mastodon.Status , publicTimeline : List Mastodon.Status
, notifications : List Mastodon.Notification
, draft : Mastodon.StatusRequestBody , draft : Mastodon.StatusRequestBody
, account : Maybe Mastodon.Account , account : Maybe Mastodon.Account
, errors : List String , errors : List String
, location : Navigation.Location , location : Navigation.Location
, useGlobalTimeline : Bool
} }
@ -82,10 +86,12 @@ init flags location =
, userTimeline = [] , userTimeline = []
, localTimeline = [] , localTimeline = []
, publicTimeline = [] , publicTimeline = []
, notifications = []
, draft = defaultDraft , draft = defaultDraft
, account = Nothing , account = Nothing
, errors = [] , errors = []
, location = location , location = location
, useGlobalTimeline = False
} }
! [ initCommands flags.registration flags.client authCode ] ! [ initCommands flags.registration flags.client authCode ]
@ -143,6 +149,7 @@ loadTimelines client =
[ Mastodon.fetchUserTimeline client |> Mastodon.send UserTimeline [ Mastodon.fetchUserTimeline client |> Mastodon.send UserTimeline
, Mastodon.fetchLocalTimeline client |> Mastodon.send LocalTimeline , Mastodon.fetchLocalTimeline client |> Mastodon.send LocalTimeline
, Mastodon.fetchPublicTimeline client |> Mastodon.send PublicTimeline , Mastodon.fetchPublicTimeline client |> Mastodon.send PublicTimeline
, Mastodon.fetchNotifications client |> Mastodon.send Notifications
] ]
Nothing -> Nothing ->
@ -271,6 +278,9 @@ update msg model =
Nothing -> Nothing ->
[] []
UseGlobalTimeline flag ->
{ model | useGlobalTimeline = flag } ! []
LocalTimeline result -> LocalTimeline result ->
case result of case result of
Ok localTimeline -> Ok localTimeline ->
@ -297,3 +307,11 @@ update msg model =
StatusPosted _ -> StatusPosted _ ->
{ model | draft = defaultDraft } ! [ loadTimelines model.client ] { model | draft = defaultDraft } ! [ loadTimelines model.client ]
Notifications result ->
case result of
Ok notifications ->
{ model | notifications = notifications } ! []
Err error ->
{ model | notifications = [], errors = (errorText error) :: model.errors } ! []

View File

@ -39,6 +39,15 @@ icon name =
i [ class <| "glyphicon glyphicon-" ++ name ] [] i [ class <| "glyphicon glyphicon-" ++ name ] []
accountLink : Mastodon.Account -> Html Msg
accountLink account =
a
[ href account.url
, ViewHelper.onClickWithPreventAndStop (OnLoadUserAccount account.id)
]
[ text <| "@" ++ account.username ]
attachmentPreview : Maybe Bool -> Mastodon.Attachment -> Html Msg attachmentPreview : Maybe Bool -> Mastodon.Attachment -> Html Msg
attachmentPreview sensitive ({ url, preview_url } as attachment) = attachmentPreview sensitive ({ url, preview_url } as attachment) =
let let
@ -227,6 +236,61 @@ timelineView statuses label iconName =
] ]
notificationHeading : Mastodon.Account -> String -> String -> Html Msg
notificationHeading account str iconType =
p [] <|
List.intersperse (text " ")
[ icon iconType, accountLink account, text str ]
notificationStatusView : Mastodon.Status -> Mastodon.Notification -> Html Msg
notificationStatusView status { type_, account } =
div [ class "notification mention" ]
[ case type_ of
"reblog" ->
notificationHeading account "boosted your toot" "fire"
"favourite" ->
notificationHeading account "favourited your toot" "star"
_ ->
text ""
, statusView status
]
notificationFollowView : Mastodon.Notification -> Html Msg
notificationFollowView { account } =
div [ class "notification follow" ]
[ notificationHeading account "started following you" "user" ]
notificationEntryView : Mastodon.Notification -> Html Msg
notificationEntryView notification =
li [ class "list-group-item" ]
[ case notification.status of
Just status ->
notificationStatusView status notification
Nothing ->
notificationFollowView notification
]
notificationListView : List Mastodon.Notification -> Html Msg
notificationListView notifications =
div [ class "col-md-3" ]
[ div [ class "panel panel-default" ]
[ div [ class "panel-heading" ]
[ icon "bell"
, text "Notifications"
]
, ul [ class "list-group" ] <|
List.map notificationEntryView notifications
]
]
draftView : Model -> Html Msg draftView : Model -> Html Msg
draftView { draft } = draftView { draft } =
let let
@ -237,108 +301,136 @@ draftView { draft } =
option [ value visibility ] option [ value visibility ]
[ text <| visibility ++ ": " ++ description ] [ text <| visibility ++ ": " ++ description ]
in in
div [ class "col-md-3" ] div [ class "panel panel-default" ]
[ div [ class "panel panel-default" ] [ div [ class "panel-heading" ] [ icon "envelope", text "Post a message" ]
[ div [ class "panel-heading" ] [ icon "envelope", text "Post a message" ] , div [ class "panel-body" ]
, div [ class "panel-body" ] [ Html.form [ class "form", onSubmit SubmitDraft ]
[ Html.form [ class "form", onSubmit SubmitDraft ] [ div [ class "form-group checkbox" ]
[ div [ class "form-group checkbox" ] [ label []
[ label [] [ input
[ input [ type_ "checkbox"
[ type_ "checkbox" , onCheck <| DraftEvent << ToggleSpoiler
, onCheck <| DraftEvent << ToggleSpoiler , checked hasSpoiler
, checked hasSpoiler
]
[]
, text " Add a spoiler"
] ]
[]
, text " Add a spoiler"
] ]
, if hasSpoiler then ]
div [ class "form-group" ] , if hasSpoiler then
[ label [ for "spoiler" ] [ text "Visible part" ] div [ class "form-group" ]
, textarea [ label [ for "spoiler" ] [ text "Visible part" ]
[ id "spoiler"
, class "form-control"
, rows 5
, placeholder "This text will always be visible."
, onInput <| DraftEvent << UpdateSpoiler
, required True
, value <| Maybe.withDefault "" draft.spoiler_text
]
[]
]
else
text ""
, div [ class "form-group" ]
[ label [ for "status" ]
[ text <|
if hasSpoiler then
"Hidden part"
else
"Status"
]
, textarea , textarea
[ id "status" [ id "spoiler"
, class "form-control" , class "form-control"
, rows 8 , rows 5
, placeholder <| , placeholder "This text will always be visible."
if hasSpoiler then , onInput <| DraftEvent << UpdateSpoiler
"This text will be hidden by default, as you have enabled a spoiler."
else
"Once upon a time..."
, onInput <| DraftEvent << UpdateStatus
, required True , required True
, value draft.status , value <| Maybe.withDefault "" draft.spoiler_text
] ]
[] []
] ]
, div [ class "form-group" ] else
[ label [ for "visibility" ] [ text "Visibility" ] text ""
, select , div [ class "form-group" ]
[ id "visibility" [ label [ for "status" ]
, class "form-control" [ text <|
, onInput <| DraftEvent << UpdateVisibility if hasSpoiler then
, required True "Hidden part"
, value draft.visibility else
"Status"
]
, textarea
[ id "status"
, class "form-control"
, rows 8
, placeholder <|
if hasSpoiler then
"This text will be hidden by default, as you have enabled a spoiler."
else
"Once upon a time..."
, onInput <| DraftEvent << UpdateStatus
, required True
, value draft.status
]
[]
]
, div [ class "form-group" ]
[ label [ for "visibility" ] [ text "Visibility" ]
, select
[ id "visibility"
, class "form-control"
, onInput <| DraftEvent << UpdateVisibility
, required True
, value draft.visibility
]
<|
List.map visibilityOptionView <|
Dict.toList visibilities
]
, div [ class "form-group checkbox" ]
[ label []
[ input
[ type_ "checkbox"
, onCheck <| DraftEvent << UpdateSensitive
, checked draft.sensitive
] ]
<| []
List.map visibilityOptionView <| , text " This post is NSFW"
Dict.toList visibilities
]
, div [ class "form-group checkbox" ]
[ label []
[ input
[ type_ "checkbox"
, onCheck <| DraftEvent << UpdateSensitive
, checked draft.sensitive
]
[]
, text " This post is NSFW"
]
]
, p [ class "text-right" ]
[ button [ class "btn btn-primary" ]
[ text "Toot!" ]
] ]
] ]
, p [ class "text-right" ]
[ button [ class "btn btn-primary" ]
[ text "Toot!" ]
]
] ]
] ]
] ]
optionsView : Model -> Html Msg
optionsView model =
div [ class "panel panel-default" ]
[ div [ class "panel-heading" ] [ icon "cog", text "options" ]
, div [ class "panel-body" ]
[ div [ class "checkbox" ]
[ label []
[ input
[ type_ "checkbox"
, onCheck UseGlobalTimeline
]
[]
, text " 4th column renders the global timeline"
]
]
]
]
sidebarView : Model -> Html Msg
sidebarView model =
div [ class "col-md-3" ]
[ draftView model
, optionsView model
]
homepageView : Model -> Html Msg homepageView : Model -> Html Msg
homepageView model = homepageView model =
div [ class "row" ] div [ class "row" ]
[ draftView model [ sidebarView model
, timelineView model.userTimeline "Home timeline" "home" , timelineView model.userTimeline "Home timeline" "home"
, timelineView model.localTimeline "Local timeline" "th-large" , notificationListView model.notifications
, case model.account of , case model.account of
Just account -> Just account ->
-- Todo: Load the user timeline -- Todo: Load the user timeline
accountTimelineView account [] "Account" "user" accountTimelineView account [] "Account" "user"
Nothing -> Nothing ->
timelineView model.publicTimeline "Public timeline" "globe" if model.useGlobalTimeline then
timelineView model.publicTimeline "Global timeline" "globe"
else
timelineView model.localTimeline "Local timeline" "th-large"
] ]

View File

@ -11,7 +11,6 @@ import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onWithOptions) import Html.Events exposing (onWithOptions)
import HtmlParser import HtmlParser
import HtmlParser.Util
import Json.Decode as Decode import Json.Decode as Decode
import Mastodon import Mastodon
import Model exposing (Msg(OnLoadUserAccount)) import Model exposing (Msg(OnLoadUserAccount))