From 44f8caa1ea89fa81944c8daad6a1974a4dc162e0 Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Sat, 29 Apr 2017 09:20:26 +0200 Subject: [PATCH] Fix #97: Allow deleting a toot. (#102) --- public/style.css | 80 +++++++++++----------- src/Mastodon/ApiUrl.elm | 6 ++ src/Mastodon/Helper.elm | 14 +++- src/Mastodon/Http.elm | 8 +++ src/Model.elm | 43 ++++++++---- src/View.elm | 142 +++++++++++++++++++++++++++------------- 6 files changed, 199 insertions(+), 94 deletions(-) diff --git a/public/style.css b/public/style.css index acce05e..093febc 100644 --- a/public/style.css +++ b/public/style.css @@ -4,43 +4,6 @@ body { overflow: hidden; } -::-webkit-scrollbar { - width: 10px; - height: 8px -} - -::-webkit-scrollbar-thumb { - background: #444; - border: 0px none #ffffff; - border-radius: 50px; -} - -::-webkit-scrollbar-thumb:hover { - background: #777; -} - -::-webkit-scrollbar-thumb:active { - background: #777; -} - -::-webkit-scrollbar-track { - border: 0px none #ffffff; - border-radius: 0; - background: rgba(0,0,0,0.1); -} - -::-webkit-scrollbar-track:hover { - background: #2a2e31; -} - -::-webkit-scrollbar-track:active { - background: #2a2e31; -} - -::-webkit-scrollbar-corner { - background: transparent; -} - .timeline { overflow-y: auto; overflow-x: hidden; @@ -193,6 +156,10 @@ body { color: #d56344; } +.btn-delete .glyphicon { + font-size: 80%; +} + /* Attachments */ .attachments { @@ -441,3 +408,42 @@ body { width: 100%; } } + +/* Scrollbars */ + +::-webkit-scrollbar { + width: 10px; + height: 8px +} + +::-webkit-scrollbar-thumb { + background: #444; + border: 0px none #ffffff; + border-radius: 50px; +} + +::-webkit-scrollbar-thumb:hover { + background: #777; +} + +::-webkit-scrollbar-thumb:active { + background: #777; +} + +::-webkit-scrollbar-track { + border: 0px none #ffffff; + border-radius: 0; + background: rgba(0,0,0,0.1); +} + +::-webkit-scrollbar-track:hover { + background: #2a2e31; +} + +::-webkit-scrollbar-track:active { + background: #2a2e31; +} + +::-webkit-scrollbar-corner { + background: transparent; +} diff --git a/src/Mastodon/ApiUrl.elm b/src/Mastodon/ApiUrl.elm index e5879d2..cbcbc49 100644 --- a/src/Mastodon/ApiUrl.elm +++ b/src/Mastodon/ApiUrl.elm @@ -6,6 +6,7 @@ module Mastodon.ApiUrl , userAccount , account , accountTimeline + , status , homeTimeline , publicTimeline , notifications @@ -97,6 +98,11 @@ reblog server id = statuses server ++ "/" ++ (toString id) ++ "/reblog" +status : Server -> Int -> String +status server id = + statuses server ++ "/" ++ (toString id) + + unreblog : Server -> Int -> String unreblog server id = statuses server ++ "/" ++ (toString id) ++ "/unreblog" diff --git a/src/Mastodon/Helper.elm b/src/Mastodon/Helper.elm index 95e9f9a..f5f444b 100644 --- a/src/Mastodon/Helper.elm +++ b/src/Mastodon/Helper.elm @@ -1,9 +1,11 @@ module Mastodon.Helper exposing - ( extractReblog + ( accountMentioned + , extractReblog , aggregateNotifications , addNotificationToAggregates , notificationToAggregate + , sameAccount ) import List.Extra exposing (groupWhile, uniqueBy) @@ -139,3 +141,13 @@ aggregateNotifications notifications = |> List.concat |> List.sortBy .created_at |> List.reverse + + +accountMentioned : Mastodon.Model.Account -> Mastodon.Model.Mention -> Bool +accountMentioned { acct, username } mention = + acct == mention.acct && username == mention.username + + +sameAccount : Mastodon.Model.Account -> Mastodon.Model.Account -> Bool +sameAccount { acct, username } account = + acct == account.acct && username == account.username diff --git a/src/Mastodon/Http.elm b/src/Mastodon/Http.elm index cac7813..ce4e00e 100644 --- a/src/Mastodon/Http.elm +++ b/src/Mastodon/Http.elm @@ -16,6 +16,7 @@ module Mastodon.Http , fetchGlobalTimeline , fetchUserTimeline , postStatus + , deleteStatus , userAccount , send ) @@ -148,6 +149,13 @@ postStatus client statusRequestBody = |> HttpBuilder.withJsonBody (statusRequestBodyEncoder statusRequestBody) +deleteStatus : Client -> Int -> Request Int +deleteStatus client id = + HttpBuilder.delete (ApiUrl.status client.server id) + |> HttpBuilder.withExpect (Http.expectJson <| Decode.succeed id) + |> HttpBuilder.withHeader "Authorization" ("Bearer " ++ client.token) + + context : Client -> Int -> Request Context context client id = HttpBuilder.get (ApiUrl.context client.server id) diff --git a/src/Model.elm b/src/Model.elm index b596f33..8d55d43 100644 --- a/src/Model.elm +++ b/src/Model.elm @@ -52,6 +52,7 @@ type MastodonMsg | Notifications (Result Mastodon.Model.Error (List Mastodon.Model.Notification)) | GlobalTimeline (Result Mastodon.Model.Error (List Mastodon.Model.Status)) | Reblogged (Result Mastodon.Model.Error Mastodon.Model.Status) + | StatusDeleted (Result Mastodon.Model.Error Int) | StatusPosted (Result Mastodon.Model.Error Mastodon.Model.Status) | Unreblogged (Result Mastodon.Model.Error Mastodon.Model.Status) | Account (Result Mastodon.Model.Error Mastodon.Model.Account) @@ -70,6 +71,7 @@ type Msg | ClearOpenedAccount | CloseThread | DomResult (Result Dom.Error ()) + | DeleteStatus Int | DraftEvent DraftMsg | LoadAccount Int | MastodonEvent MastodonMsg @@ -287,22 +289,18 @@ truncate entries = List.take maxBuffer entries -accountMentioned : Mastodon.Model.Account -> Mastodon.Model.Mention -> Bool -accountMentioned { acct, username } mention = - acct == mention.acct && username == mention.username - - -sameAccount : Mastodon.Model.Account -> Mastodon.Model.Account -> Bool -sameAccount { acct, username } account = - acct == account.acct && username == account.username - - postStatus : Mastodon.Model.Client -> Mastodon.Model.StatusRequestBody -> Cmd Msg postStatus client draft = Mastodon.Http.postStatus client draft |> Mastodon.Http.send (MastodonEvent << StatusPosted) +deleteStatus : Mastodon.Model.Client -> Int -> Cmd Msg +deleteStatus client id = + Mastodon.Http.deleteStatus client id + |> Mastodon.Http.send (MastodonEvent << StatusDeleted) + + errorText : Mastodon.Model.Error -> String errorText error = case error of @@ -405,12 +403,12 @@ updateDraft draftMsg currentUser draft = let mentions = status.mentions - |> List.filter (\m -> not (accountMentioned currentUser m)) + |> List.filter (\m -> not (Mastodon.Helper.accountMentioned currentUser m)) |> List.map (\m -> "@" ++ m.acct) |> String.join " " newStatus = - if sameAccount status.account currentUser then + if Mastodon.Helper.sameAccount status.account currentUser then mentions else "@" ++ status.account.acct ++ " " ++ mentions @@ -544,6 +542,19 @@ processMastodonEvent msg model = StatusPosted _ -> { model | draft = defaultDraft } ! [] + StatusDeleted result -> + case result of + Ok id -> + { model + | userTimeline = deleteStatusFromTimeline id model.userTimeline + , localTimeline = deleteStatusFromTimeline id model.localTimeline + , globalTimeline = deleteStatusFromTimeline id model.globalTimeline + } + ! [] + + Err error -> + { model | errors = (errorText error) :: model.errors } ! [] + Unreblogged result -> case result of Ok status -> @@ -714,6 +725,14 @@ update msg model = CloseThread -> { model | currentView = preferredTimeline model } ! [] + DeleteStatus id -> + case model.client of + Just client -> + model ! [ deleteStatus client id ] + + Nothing -> + model ! [] + Reblog id -> -- Note: The case of reblogging is specific as it seems the server -- response takes a lot of time to be received by the client, so we diff --git a/src/View.elm b/src/View.elm index 535f5c8..0122ee0 100644 --- a/src/View.elm +++ b/src/View.elm @@ -85,7 +85,12 @@ accountAvatarLink account = [ img [ class "avatar", src account.avatar ] [] ] -attachmentPreview : String -> Maybe Bool -> List Mastodon.Model.Attachment -> Mastodon.Model.Attachment -> Html Msg +attachmentPreview : + String + -> Maybe Bool + -> List Mastodon.Model.Attachment + -> Mastodon.Model.Attachment + -> Html Msg attachmentPreview context sensitive attachments ({ url, preview_url } as attachment) = let nsfw = @@ -202,13 +207,21 @@ statusView context ({ account, content, media_attachments, reblog, mentions } as ] -accountTimelineView : Mastodon.Model.Account -> List Mastodon.Model.Status -> String -> String -> Html Msg +accountTimelineView : + Mastodon.Model.Account + -> List Mastodon.Model.Status + -> String + -> String + -> Html Msg accountTimelineView account statuses label iconName = div [ class "col-md-3 column" ] [ div [ class "panel panel-default" ] [ closeablePanelheading iconName label ClearOpenedAccount , div [ class "timeline" ] - [ div [ class "account-detail", style [ ( "background-image", "url('" ++ account.header ++ "')" ) ] ] + [ div + [ class "account-detail" + , style [ ( "background-image", "url('" ++ account.header ++ "')" ) ] + ] [ div [ class "opacity-layer" ] [ img [ src account.avatar ] [] , span [ class "account-display-name" ] [ text account.display_name ] @@ -245,8 +258,8 @@ accountTimelineView account statuses label iconName = ] -statusActionsView : Mastodon.Model.Status -> Html Msg -statusActionsView status = +statusActionsView : Mastodon.Model.Status -> Mastodon.Model.Account -> Html Msg +statusActionsView status currentUser = let targetStatus = Mastodon.Helper.extractReblog status @@ -294,17 +307,23 @@ statusActionsView status = , onClickWithPreventAndStop favEvent ] [ icon "star", text (toString status.favourites_count) ] + , if Mastodon.Helper.sameAccount status.account currentUser then + a + [ class <| baseBtnClasses ++ " btn-delete" + , href "" + , onClickWithPreventAndStop <| DeleteStatus status.id + ] + [ icon "trash" ] + else + text "" , a - [ class baseBtnClasses - , href status.url - , onClickWithPreventAndStop <| OpenThread status - ] + [ class baseBtnClasses, href status.url, target "_blank" ] [ icon "time", formatDate ] ] -statusEntryView : String -> String -> Mastodon.Model.Status -> Html Msg -statusEntryView context className status = +statusEntryView : String -> String -> Mastodon.Model.Account -> Mastodon.Model.Status -> Html Msg +statusEntryView context className currentUser status = let nsfwClass = case status.sensitive of @@ -316,19 +335,25 @@ statusEntryView context className status = in li [ class <| "list-group-item " ++ className ++ " " ++ nsfwClass ] [ statusView context status - , statusActionsView status + , statusActionsView status currentUser ] -timelineView : String -> String -> String -> List Mastodon.Model.Status -> Html Msg -timelineView label iconName context statuses = +timelineView : + String + -> String + -> String + -> Mastodon.Model.Account + -> List Mastodon.Model.Status + -> Html Msg +timelineView label iconName context currentUser statuses = div [ class "col-md-3 column" ] [ div [ class "panel panel-default" ] [ a [ href "", onClickWithPreventAndStop <| ScrollColumn context ] [ div [ class "panel-heading" ] [ icon iconName, text label ] ] , ul [ id context, class "list-group timeline" ] <| - List.map (statusEntryView context "") statuses + List.map (statusEntryView context "" currentUser) statuses ] ] @@ -346,8 +371,13 @@ notificationHeading accounts str iconType = ] -notificationStatusView : String -> Mastodon.Model.Status -> Mastodon.Model.NotificationAggregate -> Html Msg -notificationStatusView context status { type_, accounts } = +notificationStatusView : + String + -> Mastodon.Model.Account + -> Mastodon.Model.Status + -> Mastodon.Model.NotificationAggregate + -> Html Msg +notificationStatusView context currentUser status { type_, accounts } = div [ class <| "notification " ++ type_ ] [ case type_ of "reblog" -> @@ -359,12 +389,12 @@ notificationStatusView context status { type_, accounts } = _ -> text "" , statusView context status - , statusActionsView status + , statusActionsView status currentUser ] -notificationFollowView : Mastodon.Model.NotificationAggregate -> Html Msg -notificationFollowView { accounts } = +notificationFollowView : Mastodon.Model.Account -> Mastodon.Model.NotificationAggregate -> Html Msg +notificationFollowView currentUser { accounts } = let profileView account = div [ class "status follow-profile" ] @@ -384,27 +414,30 @@ notificationFollowView { accounts } = ] -notificationEntryView : Mastodon.Model.NotificationAggregate -> Html Msg -notificationEntryView notification = +notificationEntryView : + Mastodon.Model.Account + -> Mastodon.Model.NotificationAggregate + -> Html Msg +notificationEntryView currentUser notification = li [ class "list-group-item" ] [ case notification.status of Just status -> - notificationStatusView "notification" status notification + notificationStatusView "notification" currentUser status notification Nothing -> - notificationFollowView notification + notificationFollowView currentUser notification ] -notificationListView : List Mastodon.Model.NotificationAggregate -> Html Msg -notificationListView notifications = +notificationListView : Mastodon.Model.Account -> List Mastodon.Model.NotificationAggregate -> Html Msg +notificationListView currentUser notifications = div [ class "col-md-3 column" ] [ div [ class "panel panel-default" ] [ a [ href "", onClickWithPreventAndStop <| ScrollColumn "notifications" ] [ div [ class "panel-heading" ] [ icon "bell", text "Notifications" ] ] , ul [ id "notifications", class "list-group timeline" ] <| - List.map notificationEntryView notifications + List.map (notificationEntryView currentUser) notifications ] ] @@ -561,8 +594,8 @@ draftView { draft, currentUser } = ] -threadView : Thread -> Html Msg -threadView thread = +threadView : Mastodon.Model.Account -> Thread -> Html Msg +threadView currentUser thread = let statuses = List.concat @@ -578,6 +611,7 @@ threadView thread = else "" ) + currentUser status in div [ class "col-md-3 column" ] @@ -614,24 +648,44 @@ sidebarView model = homepageView : Model -> Html Msg homepageView model = - div [ class "row" ] - [ sidebarView model - , timelineView "Home timeline" "home" "home" model.userTimeline - , notificationListView model.notifications - , case model.currentView of - Model.LocalTimelineView -> - timelineView "Local timeline" "th-large" "local" model.localTimeline + case model.currentUser of + Nothing -> + text "" - Model.GlobalTimelineView -> - timelineView "Global timeline" "globe" "global" model.globalTimeline + Just currentUser -> + div [ class "row" ] + [ sidebarView model + , timelineView + "Home timeline" + "home" + "home" + currentUser + model.userTimeline + , notificationListView currentUser model.notifications + , case model.currentView of + Model.LocalTimelineView -> + timelineView + "Local timeline" + "th-large" + "local" + currentUser + model.localTimeline - Model.AccountView account -> - -- Todo: Load the user timeline - accountTimelineView account model.accountTimeline "Account" "user" + Model.GlobalTimelineView -> + timelineView + "Global timeline" + "globe" + "global" + currentUser + model.globalTimeline - Model.ThreadView thread -> - threadView thread - ] + Model.AccountView account -> + -- Todo: Load the user timeline + accountTimelineView account model.accountTimeline "Account" "user" + + Model.ThreadView thread -> + threadView currentUser thread + ] authView : Model -> Html Msg