* Render user mentions in the second column. * Add a timeline type toggler.
This commit is contained in:
parent
b10761a413
commit
440806fa80
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 } ! []
|
||||
|
244
src/View.elm
244
src/View.elm
@ -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"
|
||||
]
|
||||
|
||||
|
||||
|
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user