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

View File

@ -7,6 +7,7 @@ module Mastodon
, Client
, Error(..)
, Mention
, Notification
, Reblog(..)
, Status
, StatusRequestBody
@ -17,8 +18,9 @@ module Mastodon
, getAuthorizationUrl
, getAccessToken
, fetchAccount
, fetchPublicTimeline
, fetchLocalTimeline
, fetchNotifications
, fetchPublicTimeline
, fetchUserTimeline
, postStatus
, 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 =
{ name : String
, url : String
@ -287,6 +305,16 @@ mentionDecoder =
|> 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 =
Pipe.decode Tag
@ -450,17 +478,22 @@ fetchAccount client accountId =
fetchUserTimeline : Client -> Request (List Status)
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 =
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 =
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

View File

@ -25,13 +25,15 @@ type Msg
| AppRegistered (Result Mastodon.Error Mastodon.AppRegistration)
| DraftEvent DraftMsg
| LocalTimeline (Result Mastodon.Error (List Mastodon.Status))
| PublicTimeline (Result Mastodon.Error (List Mastodon.Status))
| Notifications (Result Mastodon.Error (List Mastodon.Notification))
| OnLoadUserAccount Int
| PublicTimeline (Result Mastodon.Error (List Mastodon.Status))
| Register
| ServerChange String
| StatusPosted (Result Mastodon.Error Mastodon.Status)
| SubmitDraft
| UrlChange Navigation.Location
| UseGlobalTimeline Bool
| UserAccount (Result Mastodon.Error Mastodon.Account)
| UserTimeline (Result Mastodon.Error (List Mastodon.Status))
@ -43,10 +45,12 @@ type alias Model =
, userTimeline : List Mastodon.Status
, localTimeline : List Mastodon.Status
, publicTimeline : List Mastodon.Status
, notifications : List Mastodon.Notification
, draft : Mastodon.StatusRequestBody
, account : Maybe Mastodon.Account
, errors : List String
, location : Navigation.Location
, useGlobalTimeline : Bool
}
@ -82,10 +86,12 @@ init flags location =
, userTimeline = []
, localTimeline = []
, publicTimeline = []
, notifications = []
, draft = defaultDraft
, account = Nothing
, errors = []
, location = location
, useGlobalTimeline = False
}
! [ initCommands flags.registration flags.client authCode ]
@ -143,6 +149,7 @@ loadTimelines client =
[ Mastodon.fetchUserTimeline client |> Mastodon.send UserTimeline
, Mastodon.fetchLocalTimeline client |> Mastodon.send LocalTimeline
, Mastodon.fetchPublicTimeline client |> Mastodon.send PublicTimeline
, Mastodon.fetchNotifications client |> Mastodon.send Notifications
]
Nothing ->
@ -271,6 +278,9 @@ update msg model =
Nothing ->
[]
UseGlobalTimeline flag ->
{ model | useGlobalTimeline = flag } ! []
LocalTimeline result ->
case result of
Ok localTimeline ->
@ -297,3 +307,11 @@ update msg model =
StatusPosted _ ->
{ 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 ] []
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 sensitive ({ url, preview_url } as attachment) =
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 { draft } =
let
@ -237,108 +301,136 @@ draftView { draft } =
option [ value visibility ]
[ text <| visibility ++ ": " ++ description ]
in
div [ class "col-md-3" ]
[ div [ class "panel panel-default" ]
[ div [ class "panel-heading" ] [ icon "envelope", text "Post a message" ]
, div [ class "panel-body" ]
[ Html.form [ class "form", onSubmit SubmitDraft ]
[ div [ class "form-group checkbox" ]
[ label []
[ input
[ type_ "checkbox"
, onCheck <| DraftEvent << ToggleSpoiler
, checked hasSpoiler
]
[]
, text " Add a spoiler"
div [ class "panel panel-default" ]
[ div [ class "panel-heading" ] [ icon "envelope", text "Post a message" ]
, div [ class "panel-body" ]
[ Html.form [ class "form", onSubmit SubmitDraft ]
[ div [ class "form-group checkbox" ]
[ label []
[ input
[ type_ "checkbox"
, onCheck <| DraftEvent << ToggleSpoiler
, checked hasSpoiler
]
[]
, text " Add a spoiler"
]
, if hasSpoiler then
div [ class "form-group" ]
[ label [ for "spoiler" ] [ text "Visible part" ]
, textarea
[ 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"
]
]
, if hasSpoiler then
div [ class "form-group" ]
[ label [ for "spoiler" ] [ text "Visible part" ]
, textarea
[ id "status"
[ id "spoiler"
, 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
, rows 5
, placeholder "This text will always be visible."
, onInput <| DraftEvent << UpdateSpoiler
, required True
, value draft.status
, value <| Maybe.withDefault "" draft.spoiler_text
]
[]
]
, div [ class "form-group" ]
[ label [ for "visibility" ] [ text "Visibility" ]
, select
[ id "visibility"
, class "form-control"
, onInput <| DraftEvent << UpdateVisibility
, required True
, value draft.visibility
else
text ""
, div [ class "form-group" ]
[ label [ for "status" ]
[ text <|
if hasSpoiler then
"Hidden part"
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 <|
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!" ]
[]
, text " This post is NSFW"
]
]
, 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 =
div [ class "row" ]
[ draftView model
[ sidebarView model
, timelineView model.userTimeline "Home timeline" "home"
, timelineView model.localTimeline "Local timeline" "th-large"
, notificationListView model.notifications
, case model.account of
Just account ->
-- Todo: Load the user timeline
accountTimelineView account [] "Account" "user"
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.Events exposing (onWithOptions)
import HtmlParser
import HtmlParser.Util
import Json.Decode as Decode
import Mastodon
import Model exposing (Msg(OnLoadUserAccount))