From 6b75c90ef672e5a4008783ced35dd998ab1d778b Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Sun, 23 Apr 2017 21:49:04 +0200 Subject: [PATCH] Aggregate notifications. (#37) --- elm-package.json | 1 + public/style.css | 24 ++++++++++++++++---- src/Mastodon.elm | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ src/Model.elm | 9 ++------ src/View.elm | 50 +++++++++++++++++++++++++++-------------- 5 files changed, 114 insertions(+), 28 deletions(-) diff --git a/elm-package.json b/elm-package.json index fbe5a43..8ca0524 100644 --- a/elm-package.json +++ b/elm-package.json @@ -9,6 +9,7 @@ "exposed-modules": [], "dependencies": { "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0", + "elm-community/list-extra": "6.0.0 <= v < 7.0.0", "elm-lang/core": "5.1.1 <= v < 6.0.0", "elm-lang/dom": "1.1.1 <= v < 2.0.0", "elm-lang/html": "2.0.0 <= v < 3.0.0", diff --git a/public/style.css b/public/style.css index 06342b5..86fad1b 100644 --- a/public/style.css +++ b/public/style.css @@ -17,16 +17,32 @@ body { color: #999; margin-bottom: 8px; } -.reblog > p:first-of-type > a, -.notification > p:first-of-type > a { + +.status-info, .status-info a { color: #ccc; + line-height: 1.6em; +} + +.status-info > .avatars { + margin-bottom: 8px; +} + +.status-info > .avatars img { + width: 24px; + height: 24px; + margin-right: 5px; + border-radius: 2px; +} + +.notification.reblog, +.notification.favourite { + opacity: .75; } .notification.follow > p { margin-bottom: 0; } -.notification .spoiled, -.notification .attachments { +.notification .spoiled { display: none; } diff --git a/src/Mastodon.elm b/src/Mastodon.elm index 7a8f686..333fd30 100644 --- a/src/Mastodon.elm +++ b/src/Mastodon.elm @@ -8,6 +8,7 @@ module Mastodon , Error(..) , Mention , Notification + , NotificationAggregate , Reblog(..) , Status , StatusRequestBody @@ -19,6 +20,7 @@ module Mastodon , extractReblog , register , registrationEncoder + , aggregateNotifications , clientEncoder , getAuthorizationUrl , getAccessToken @@ -36,6 +38,7 @@ import HttpBuilder import Json.Decode.Pipeline as Pipe import Json.Decode as Decode import Json.Encode as Encode +import List.Extra exposing (groupWhile) -- Types @@ -148,6 +151,14 @@ type alias Notification = } +type alias NotificationAggregate = + { type_ : String + , status : Maybe Status + , accounts : List Account + , created_at : String + } + + type alias Tag = { name : String , url : String @@ -437,6 +448,53 @@ fetch client endpoint decoder = -- Public API +aggregateNotifications : List Notification -> List NotificationAggregate +aggregateNotifications notifications = + let + only type_ notifications = + List.filter (\n -> n.type_ == type_) notifications + + sameStatus n1 n2 = + case ( n1.status, n2.status ) of + ( Just r1, Just r2 ) -> + r1.id == r2.id + + _ -> + False + + sameAccount n1 n2 = + n1.account.id == n2.account.id + + extractAggregate statusGroup = + let + accounts = + List.map .account statusGroup + in + case statusGroup of + notification :: _ -> + [ NotificationAggregate + notification.type_ + notification.status + accounts + notification.created_at + ] + + [] -> + [] + + aggregate statusGroups = + List.map extractAggregate statusGroups |> List.concat + in + [ notifications |> only "reblog" |> groupWhile sameStatus |> aggregate + , notifications |> only "favourite" |> groupWhile sameStatus |> aggregate + , notifications |> only "mention" |> groupWhile sameStatus |> aggregate + , notifications |> only "follow" |> groupWhile sameAccount |> aggregate + ] + |> List.concat + |> List.sortBy .created_at + |> List.reverse + + clientEncoder : Client -> Encode.Value clientEncoder client = Encode.object diff --git a/src/Model.elm b/src/Model.elm index 332594b..68bcfc0 100644 --- a/src/Model.elm +++ b/src/Model.elm @@ -58,11 +58,6 @@ type | UserTimeline (Result Mastodon.Error (List Mastodon.Status)) -type Crud - = Add - | Remove - - type alias Draft = { status : String , in_reply_to : Maybe Mastodon.Status @@ -79,7 +74,7 @@ type alias Model = , userTimeline : List Mastodon.Status , localTimeline : List Mastodon.Status , publicTimeline : List Mastodon.Status - , notifications : List Mastodon.Notification + , notifications : List Mastodon.NotificationAggregate , draft : Draft , account : Maybe Mastodon.Account , errors : List String @@ -499,7 +494,7 @@ update msg model = Notifications result -> case result of Ok notifications -> - { model | notifications = notifications } ! [] + { model | notifications = Mastodon.aggregateNotifications notifications } ! [] Err error -> { model | notifications = [], errors = (errorText error) :: model.errors } ! [] diff --git a/src/View.elm b/src/View.elm index 65d938a..808407a 100644 --- a/src/View.elm +++ b/src/View.elm @@ -54,6 +54,16 @@ accountLink account = [ text <| "@" ++ account.username ] +accountAvatarLink : Mastodon.Account -> Html Msg +accountAvatarLink account = + a + [ href account.url + , ViewHelper.onClickWithPreventAndStop (OnLoadUserAccount account.id) + , title <| "@" ++ account.username + ] + [ img [ src account.avatar ] [] ] + + attachmentPreview : Maybe Bool -> Mastodon.Attachment -> Html Msg attachmentPreview sensitive ({ url, preview_url } as attachment) = let @@ -146,10 +156,10 @@ statusView ({ account, content, media_attachments, reblog, mentions } as status) case reblog of Just (Mastodon.Reblog reblog) -> div [ class "reblog" ] - [ p [] + [ p [ class "status-info" ] [ icon "fire" , a (accountLinkAttributes ++ [ class "reblogger" ]) - [ text <| " " ++ account.username ] + [ text <| " @" ++ account.username ] , text " boosted" ] , statusView reblog @@ -297,22 +307,28 @@ 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 ] +notificationHeading : List Mastodon.Account -> String -> String -> Html Msg +notificationHeading accounts str iconType = + div [ class "status-info" ] + [ div [ class "avatars" ] <| List.map accountAvatarLink accounts + , p [] <| + List.intersperse (text " ") + [ icon iconType + , span [] <| List.intersperse (text ", ") (List.map accountLink accounts) + , text str + ] + ] -notificationStatusView : Mastodon.Status -> Mastodon.Notification -> Html Msg -notificationStatusView status { type_, account } = - div [ class "notification" ] +notificationStatusView : Mastodon.Status -> Mastodon.NotificationAggregate -> Html Msg +notificationStatusView status { type_, accounts } = + div [ class <| "notification " ++ type_ ] [ case type_ of "reblog" -> - notificationHeading account "boosted your toot" "fire" + notificationHeading accounts "boosted your toot" "fire" "favourite" -> - notificationHeading account "favourited your toot" "star" + notificationHeading accounts "favourited your toot" "star" _ -> text "" @@ -321,13 +337,13 @@ notificationStatusView status { type_, account } = ] -notificationFollowView : Mastodon.Notification -> Html Msg -notificationFollowView { account } = +notificationFollowView : Mastodon.NotificationAggregate -> Html Msg +notificationFollowView { accounts } = div [ class "notification follow" ] - [ notificationHeading account "started following you" "user" ] + [ notificationHeading accounts "started following you" "user" ] -notificationEntryView : Mastodon.Notification -> Html Msg +notificationEntryView : Mastodon.NotificationAggregate -> Html Msg notificationEntryView notification = li [ class "list-group-item" ] [ case notification.status of @@ -339,7 +355,7 @@ notificationEntryView notification = ] -notificationListView : List Mastodon.Notification -> Html Msg +notificationListView : List Mastodon.NotificationAggregate -> Html Msg notificationListView notifications = div [ class "col-md-3" ] [ div [ class "panel panel-default" ]