Browse Source

Fix #196: Fix incompatibilities with the Mastodon API v2.0.0. (#197)

master
Nicolas Perriault 5 years ago
committed by GitHub
parent
commit
2e89ad0b0f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 14
      public/index.html
  3. 37
      src/Command.elm
  4. 8
      src/Init.elm
  5. 78
      src/Mastodon/ApiUrl.elm
  6. 91
      src/Mastodon/Decoder.elm
  7. 13
      src/Mastodon/Encoder.elm
  8. 8
      src/Mastodon/Helper.elm
  9. 27
      src/Mastodon/Model.elm
  10. 10
      src/Mastodon/WebSocket.elm
  11. 12
      src/Types.elm
  12. 4
      src/Update/Draft.elm
  13. 3
      src/Update/Main.elm
  14. 6
      src/Update/Mastodon.elm
  15. 22
      src/Update/Route.elm
  16. 10
      src/Update/Timeline.elm
  17. 27
      src/Update/WebSocket.elm
  18. 14
      src/View/Account.elm
  19. 4
      src/View/Blocks.elm
  20. 4
      src/View/Common.elm
  21. 2
      src/View/Draft.elm
  22. 2
      src/View/Formatter.elm
  23. 4
      src/View/Mutes.elm
  24. 4
      src/View/Notification.elm
  25. 2
      src/View/Search.elm
  26. 12
      src/View/Status.elm
  27. 3
      src/View/Thread.elm
  28. 3
      src/View/Timeline.elm
  29. 42
      tests/Fixtures.elm

1
.gitignore

@ -2,3 +2,4 @@
/elm-stuff
/node_modules
app.js
package-lock.json

14
public/index.html

@ -17,20 +17,8 @@
<body>
<script src="app.js"></script>
<script>
// Note: this is a transitional upgrade to new client storage format, which
// is now now a list of clients, which all require an "account" property set.
const oldClient = JSON.parse(localStorage.getItem("tooty.client"));
const defaultClients = oldClient ? [oldClient] : [];
const clients = (JSON.parse(localStorage.getItem("tooty.clients")) || defaultClients)
.map(client => {
if (!client.hasOwnProperty("account")) {
client.account = null;
}
return client;
});
const app = Elm.Main.fullscreen({
clients: clients,
clients: localStorage.getItem("tooty.clients") || "[]",
registration: JSON.parse(localStorage.getItem("tooty.registration"))
});

37
src/Command.elm

@ -54,6 +54,7 @@ import HttpBuilder
import Mastodon.ApiUrl as ApiUrl
import Mastodon.Decoder exposing (..)
import Mastodon.Encoder exposing (..)
import Mastodon.Helper exposing (extractStatusId)
import Mastodon.Http exposing (..)
import Mastodon.Model exposing (..)
import Navigation
@ -166,7 +167,7 @@ loadUserAccount client =
Cmd.none
loadAccount : Maybe Client -> Int -> Cmd Msg
loadAccount : Maybe Client -> String -> Cmd Msg
loadAccount client accountId =
case client of
Just client ->
@ -183,7 +184,7 @@ loadAccount client accountId =
Cmd.none
loadAccountFollowers : Maybe Client -> Int -> Maybe String -> Cmd Msg
loadAccountFollowers : Maybe Client -> String -> Maybe String -> Cmd Msg
loadAccountFollowers client accountId url =
case client of
Just client ->
@ -196,7 +197,7 @@ loadAccountFollowers client accountId url =
Cmd.none
loadAccountFollowing : Maybe Client -> Int -> Maybe String -> Cmd Msg
loadAccountFollowing : Maybe Client -> String -> Maybe String -> Cmd Msg
loadAccountFollowing client accountId url =
case client of
Just client ->
@ -256,16 +257,16 @@ searchAccounts client query limit resolve =
Cmd.none
requestRelationships : Client -> List Int -> Request (List Relationship)
requestRelationships : Client -> List String -> Request (List Relationship)
requestRelationships client ids =
HttpBuilder.get ApiUrl.relationships
|> withClient client
|> withBodyDecoder (Decode.list relationshipDecoder)
|> withQueryParams
(List.map (\id -> ( "id[]", toString id )) ids)
(List.map (\id -> ( "id[]", id )) ids)
loadRelationships : Maybe Client -> List Int -> Cmd Msg
loadRelationships : Maybe Client -> List String -> Cmd Msg
loadRelationships client ids =
if List.length ids > 0 then
case client of
@ -279,7 +280,7 @@ loadRelationships client ids =
Cmd.none
loadThread : Maybe Client -> Int -> Cmd Msg
loadThread : Maybe Client -> StatusId -> Cmd Msg
loadThread client id =
case client of
Just client ->
@ -340,7 +341,7 @@ loadGlobalTimeline client url =
Cmd.none
loadAccountTimeline : Maybe Client -> Int -> Maybe String -> Cmd Msg
loadAccountTimeline : Maybe Client -> String -> Maybe String -> Cmd Msg
loadAccountTimeline client accountId url =
case client of
Just client ->
@ -497,7 +498,7 @@ updateDomStatus statusText =
Ports.setStatus { id = "status", status = statusText }
deleteStatus : Maybe Client -> Int -> Cmd Msg
deleteStatus : Maybe Client -> StatusId -> Cmd Msg
deleteStatus client id =
case client of
Just client ->
@ -510,7 +511,7 @@ deleteStatus client id =
Cmd.none
reblogStatus : Maybe Client -> Int -> Cmd Msg
reblogStatus : Maybe Client -> StatusId -> Cmd Msg
reblogStatus client statusId =
case client of
Just client ->
@ -523,7 +524,7 @@ reblogStatus client statusId =
Cmd.none
unreblogStatus : Maybe Client -> Int -> Cmd Msg
unreblogStatus : Maybe Client -> StatusId -> Cmd Msg
unreblogStatus client statusId =
case client of
Just client ->
@ -536,7 +537,7 @@ unreblogStatus client statusId =
Cmd.none
favouriteStatus : Maybe Client -> Int -> Cmd Msg
favouriteStatus : Maybe Client -> StatusId -> Cmd Msg
favouriteStatus client statusId =
case client of
Just client ->
@ -549,7 +550,7 @@ favouriteStatus client statusId =
Cmd.none
unfavouriteStatus : Maybe Client -> Int -> Cmd Msg
unfavouriteStatus : Maybe Client -> StatusId -> Cmd Msg
unfavouriteStatus client statusId =
case client of
Just client ->
@ -680,7 +681,7 @@ notifyStatus status =
{ title = status.account.acct
, icon = status.account.avatar
, body = status.content |> textContent
, clickUrl = "#thread/" ++ (toString status.id)
, clickUrl = "#thread/" ++ extractStatusId status.id
}
@ -694,7 +695,7 @@ notifyNotification notification =
{ title = notification.account.acct ++ " reboosted"
, icon = notification.account.avatar
, body = status.content |> textContent
, clickUrl = "#thread/" ++ (toString status.id)
, clickUrl = "#thread/" ++ extractStatusId status.id
}
"favourite" ->
@ -702,7 +703,7 @@ notifyNotification notification =
{ title = notification.account.acct ++ " favorited"
, icon = notification.account.avatar
, body = status.content |> textContent
, clickUrl = "#thread/" ++ (toString status.id)
, clickUrl = "#thread/" ++ extractStatusId status.id
}
"mention" ->
@ -710,7 +711,7 @@ notifyNotification notification =
{ title = notification.account.acct ++ " mentioned you"
, icon = notification.account.avatar
, body = status.content |> textContent
, clickUrl = "#thread/" ++ (toString status.id)
, clickUrl = "#thread/" ++ extractStatusId status.id
}
_ ->
@ -723,7 +724,7 @@ notifyNotification notification =
{ title = notification.account.acct ++ " follows you"
, icon = notification.account.avatar
, body = notification.account.note
, clickUrl = "#account/" ++ (toString notification.account.id)
, clickUrl = "#account/" ++ notification.account.id
}
_ ->

8
src/Init.elm

@ -1,6 +1,7 @@
module Init exposing (init)
import Command
import Mastodon.Decoder exposing (decodeClients)
import Navigation
import Types exposing (..)
import Update.AccountInfo
@ -13,12 +14,15 @@ import Util
init : Flags -> Navigation.Location -> ( Model, Cmd Msg )
init { registration, clients } location =
let
decodedClients =
Result.withDefault [] <| decodeClients clients
( model, commands ) =
Update.Route.update
{ server = ""
, currentTime = 0
, registration = registration
, clients = clients
, clients = decodedClients
, homeTimeline = Update.Timeline.empty "home-timeline"
, localTimeline = Update.Timeline.empty "local-timeline"
, globalTimeline = Update.Timeline.empty "global-timeline"
@ -41,4 +45,4 @@ init { registration, clients } location =
}
in
model
! [ commands, Command.initCommands registration (List.head clients) (Util.extractAuthCode location) ]
! [ commands, Command.initCommands registration (List.head decodedClients) (Util.extractAuthCode location) ]

78
src/Mastodon/ApiUrl.elm

@ -35,6 +35,8 @@ module Mastodon.ApiUrl
, search
)
import Mastodon.Model exposing (StatusId(..))
apiPrefix : String
apiPrefix =
@ -61,39 +63,39 @@ accounts =
apiPrefix ++ "/accounts/"
account : Int -> String
account : String -> String
account id =
accounts ++ (toString id)
accounts ++ id
follow : Int -> String
follow : String -> String
follow id =
accounts ++ (toString id) ++ "/follow"
accounts ++ id ++ "/follow"
unfollow : Int -> String
unfollow : String -> String
unfollow id =
accounts ++ (toString id) ++ "/unfollow"
accounts ++ id ++ "/unfollow"
mute : Int -> String
mute : String -> String
mute id =
accounts ++ (toString id) ++ "/mute"
accounts ++ id ++ "/mute"
unmute : Int -> String
unmute : String -> String
unmute id =
accounts ++ (toString id) ++ "/unmute"
accounts ++ id ++ "/unmute"
block : Int -> String
block : String -> String
block id =
accounts ++ (toString id) ++ "/block"
accounts ++ id ++ "/block"
unblock : Int -> String
unblock : String -> String
unblock id =
accounts ++ (toString id) ++ "/unblock"
accounts ++ id ++ "/unblock"
userAccount : String
@ -116,14 +118,14 @@ relationships =
accounts ++ "relationships"
followers : Int -> String
followers : String -> String
followers id =
(account id) ++ "/followers"
account id ++ "/followers"
following : Int -> String
following : String -> String
following id =
(account id) ++ "/following"
account id ++ "/following"
homeTimeline : String
@ -136,9 +138,9 @@ publicTimeline =
apiPrefix ++ "/timelines/public"
accountTimeline : Int -> String
accountTimeline : String -> String
accountTimeline id =
(account id) ++ "/statuses"
account id ++ "/statuses"
favouriteTimeline : String
@ -171,34 +173,34 @@ statuses =
apiPrefix ++ "/statuses"
context : Int -> String
context id =
statuses ++ "/" ++ (toString id) ++ "/context"
context : StatusId -> String
context (StatusId id) =
statuses ++ "/" ++ id ++ "/context"
reblog : Int -> String
reblog id =
statuses ++ "/" ++ (toString id) ++ "/reblog"
reblog : StatusId -> String
reblog (StatusId id) =
statuses ++ "/" ++ id ++ "/reblog"
status : Int -> String
status id =
statuses ++ "/" ++ (toString id)
status : StatusId -> String
status (StatusId id) =
statuses ++ "/" ++ id
unreblog : Int -> String
unreblog id =
statuses ++ "/" ++ (toString id) ++ "/unreblog"
unreblog : StatusId -> String
unreblog (StatusId id) =
statuses ++ "/" ++ id ++ "/unreblog"
favourite : Int -> String
favourite id =
statuses ++ "/" ++ (toString id) ++ "/favourite"
favourite : StatusId -> String
favourite (StatusId id) =
statuses ++ "/" ++ id ++ "/favourite"
unfavourite : Int -> String
unfavourite id =
statuses ++ "/" ++ (toString id) ++ "/unfavourite"
unfavourite : StatusId -> String
unfavourite (StatusId id) =
statuses ++ "/" ++ id ++ "/unfavourite"
streaming : String

91
src/Mastodon/Decoder.elm

@ -6,6 +6,7 @@ module Mastodon.Decoder
, attachmentDecoder
, contextDecoder
, decodeWebSocketMessage
, decodeClients
, mastodonErrorDecoder
, mentionDecoder
, notificationDecoder
@ -14,7 +15,6 @@ module Mastodon.Decoder
, relationshipDecoder
, searchResultsDecoder
, statusDecoder
, webSocketPayloadDecoder
, webSocketEventDecoder
)
@ -31,7 +31,7 @@ appRegistrationDecoder server scope =
|> Pipe.hardcoded scope
|> Pipe.required "client_id" Decode.string
|> Pipe.required "client_secret" Decode.string
|> Pipe.required "id" Decode.int
|> Pipe.required "id" idDecoder
|> Pipe.required "redirect_uri" Decode.string
@ -52,7 +52,7 @@ accountDecoder =
|> Pipe.required "followers_count" Decode.int
|> Pipe.required "following_count" Decode.int
|> Pipe.required "header" Decode.string
|> Pipe.required "id" Decode.int
|> Pipe.required "id" idDecoder
|> Pipe.required "locked" Decode.bool
|> Pipe.required "note" Decode.string
|> Pipe.required "statuses_count" Decode.int
@ -70,7 +70,7 @@ applicationDecoder =
attachmentDecoder : Decode.Decoder Attachment
attachmentDecoder =
Pipe.decode Attachment
|> Pipe.required "id" Decode.int
|> Pipe.required "id" idDecoder
|> Pipe.required "type" Decode.string
|> Pipe.required "url" Decode.string
|> Pipe.optional "remote_url" Decode.string ""
@ -85,6 +85,19 @@ contextDecoder =
|> Pipe.required "descendants" (Decode.list statusDecoder)
clientDecoder : Decode.Decoder Client
clientDecoder =
Pipe.decode Client
|> Pipe.required "server" Decode.string
|> Pipe.required "token" Decode.string
|> Pipe.required "account" (Decode.maybe accountDecoder)
decodeClients : String -> Result String (List Client)
decodeClients json =
Decode.decodeString (Decode.list clientDecoder) json
mastodonErrorDecoder : Decode.Decoder String
mastodonErrorDecoder =
Decode.field "error" Decode.string
@ -93,7 +106,7 @@ mastodonErrorDecoder =
mentionDecoder : Decode.Decoder Mention
mentionDecoder =
Pipe.decode Mention
|> Pipe.required "id" Decode.int
|> Pipe.required "id" idDecoder
|> Pipe.required "url" Decode.string
|> Pipe.required "username" Decode.string
|> Pipe.required "acct" Decode.string
@ -102,7 +115,7 @@ mentionDecoder =
notificationDecoder : Decode.Decoder Notification
notificationDecoder =
Pipe.decode Notification
|> Pipe.required "id" Decode.int
|> Pipe.required "id" idDecoder
|> Pipe.required "type" Decode.string
|> Pipe.required "created_at" Decode.string
|> Pipe.required "account" accountDecoder
@ -112,7 +125,7 @@ notificationDecoder =
relationshipDecoder : Decode.Decoder Relationship
relationshipDecoder =
Pipe.decode Relationship
|> Pipe.required "id" Decode.int
|> Pipe.required "id" idDecoder
|> Pipe.required "blocking" Decode.bool
|> Pipe.required "followed_by" Decode.bool
|> Pipe.required "following" Decode.bool
@ -140,6 +153,21 @@ searchResultsDecoder =
|> Pipe.required "hashtags" (Decode.list Decode.string)
idDecoder : Decode.Decoder String
idDecoder =
-- Note: since v2.0.0 of the Mastodon API, ids are treated as strings, so we
-- treat all ids as strings.
Decode.oneOf
[ Decode.string
, Decode.int |> Decode.map toString
]
statusIdDecoder : Decode.Decoder StatusId
statusIdDecoder =
idDecoder |> Decode.map StatusId
statusDecoder : Decode.Decoder Status
statusDecoder =
Pipe.decode Status
@ -149,9 +177,9 @@ statusDecoder =
|> Pipe.required "created_at" Decode.string
|> Pipe.optional "favourited" (Decode.nullable Decode.bool) Nothing
|> Pipe.required "favourites_count" Decode.int
|> Pipe.required "id" Decode.int
|> Pipe.required "in_reply_to_account_id" (Decode.nullable Decode.int)
|> Pipe.required "in_reply_to_id" (Decode.nullable Decode.int)
|> Pipe.required "id" statusIdDecoder
|> Pipe.required "in_reply_to_account_id" (Decode.nullable idDecoder)
|> Pipe.required "in_reply_to_id" (Decode.nullable statusIdDecoder)
|> Pipe.required "media_attachments" (Decode.list attachmentDecoder)
|> Pipe.required "mentions" (Decode.list mentionDecoder)
|> Pipe.optional "reblog" (Decode.lazy (\_ -> Decode.nullable reblogDecoder)) Nothing
@ -165,49 +193,34 @@ statusDecoder =
|> Pipe.required "visibility" Decode.string
webSocketPayloadDecoder : Decode.Decoder WebSocketPayload
webSocketPayloadDecoder =
Decode.oneOf
[ Decode.map StringPayload Decode.string
, Decode.map IntPayload Decode.int
]
webSocketEventDecoder : Decode.Decoder WebSocketMessage
webSocketEventDecoder =
Pipe.decode WebSocketMessage
|> Pipe.required "event" Decode.string
|> Pipe.required "payload" webSocketPayloadDecoder
|> Pipe.required "payload"
-- NOTE: as of the Mastodon API v2.0.0, ids may be either ints or
-- strings. If we receive an int (most likely for the delete event),
-- we cast it to a string.
(Decode.oneOf
[ Decode.string
, Decode.int |> Decode.map toString
]
)
decodeWebSocketMessage : String -> WebSocketEvent
decodeWebSocketMessage message =
case (Decode.decodeString webSocketEventDecoder message) of
Ok message ->
case message.event of
Ok { event, payload } ->
case event of
"update" ->
case message.payload of
StringPayload payload ->
StatusUpdateEvent (Decode.decodeString statusDecoder payload)
_ ->
ErrorEvent "WS status update event payload must be a string"
StatusUpdateEvent (Decode.decodeString statusDecoder payload)
"delete" ->
case message.payload of
IntPayload payload ->
StatusDeleteEvent <| Ok payload
_ ->
ErrorEvent "WS status delete event payload must be an int"
StatusDeleteEvent (StatusId payload)
"notification" ->
case message.payload of
StringPayload payload ->
NotificationEvent (Decode.decodeString notificationDecoder payload)
_ ->
ErrorEvent "WS notification event payload must be an string"
NotificationEvent (Decode.decodeString notificationDecoder payload)
event ->
ErrorEvent <| "Unknown WS event " ++ event

13
src/Mastodon/Encoder.elm

@ -61,7 +61,7 @@ accountEncoder account =
, ( "followers_count", Encode.int account.followers_count )
, ( "following_count", Encode.int account.following_count )
, ( "header", Encode.string account.header )
, ( "id", Encode.int account.id )
, ( "id", Encode.string account.id )
, ( "locked", Encode.bool account.locked )
, ( "note", Encode.string account.note )
, ( "statuses_count", Encode.int account.statuses_count )
@ -86,18 +86,23 @@ registrationEncoder registration =
, ( "scope", Encode.string registration.scope )
, ( "client_id", Encode.string registration.client_id )
, ( "client_secret", Encode.string registration.client_secret )
, ( "id", Encode.int registration.id )
, ( "id", Encode.string registration.id )
, ( "redirect_uri", Encode.string registration.redirect_uri )
]
encodeStatusId : StatusId -> Encode.Value
encodeStatusId (StatusId id) =
Encode.string id
statusRequestBodyEncoder : StatusRequestBody -> Encode.Value
statusRequestBodyEncoder statusData =
Encode.object
[ ( "status", Encode.string statusData.status )
, ( "in_reply_to_id", encodeMaybe Encode.int statusData.in_reply_to_id )
, ( "in_reply_to_id", encodeMaybe encodeStatusId statusData.in_reply_to_id )
, ( "spoiler_text", encodeMaybe Encode.string statusData.spoiler_text )
, ( "sensitive", Encode.bool statusData.sensitive )
, ( "visibility", Encode.string statusData.visibility )
, ( "media_ids", Encode.list (List.map Encode.int statusData.media_ids) )
, ( "media_ids", Encode.list (List.map Encode.string statusData.media_ids) )
]

8
src/Mastodon/Helper.elm

@ -3,6 +3,7 @@ module Mastodon.Helper
( extractReblog
, aggregateNotifications
, addNotificationToAggregates
, extractStatusId
, getReplyPrefix
, notificationToAggregate
, sameAccount
@ -187,6 +188,11 @@ sameAccount { id, acct, username } account =
id == account.id && acct == account.acct && username == account.username
statusReferenced : Int -> Status -> Bool
statusReferenced : StatusId -> Status -> Bool
statusReferenced id status =
status.id == id || (extractReblog status).id == id
extractStatusId : StatusId -> String
extractStatusId (StatusId id) =
id

27
src/Mastodon/Model.elm

@ -17,12 +17,13 @@ module Mastodon.Model
, Tag
, SearchResults
, Status
, StatusId(..)
, StatusRequestBody
)
type alias AccountId =
Int
String
type alias AuthCode =
@ -41,6 +42,10 @@ type alias Server =
String
type StatusId
= StatusId String
type alias StatusCode =
Int
@ -71,7 +76,7 @@ type alias AppRegistration =
, scope : String
, client_id : ClientId
, client_secret : ClientSecret
, id : Int
, id : String
, redirect_uri : String
}
@ -101,7 +106,7 @@ type alias Application =
type alias Attachment =
-- type_: -- "image", "video", "gifv"
{ id : Int
{ id : String
, type_ : String
, url : String
, remote_url : String
@ -139,7 +144,7 @@ type alias Notification =
- account: The Account sending the notification to the user
- status: The Status associated with the notification, if applicable
-}
{ id : Int
{ id : String
, type_ : String
, created_at : String
, account : Account
@ -154,7 +159,7 @@ type alias AccountNotificationDate =
type alias NotificationAggregate =
{ id : Int
{ id : String
, type_ : String
, status : Maybe Status
, accounts : List AccountNotificationDate
@ -167,7 +172,7 @@ type Reblog
type alias Relationship =
{ id : Int
{ id : String
, blocking : Bool
, followed_by : Bool
, following : Bool
@ -190,9 +195,9 @@ type alias Status =
, created_at : String
, favourited : Maybe Bool
, favourites_count : Int
, id : Int
, in_reply_to_account_id : Maybe Int
, in_reply_to_id : Maybe Int
, id : StatusId
, in_reply_to_account_id : Maybe String
, in_reply_to_id : Maybe StatusId
, media_attachments : List Attachment
, mentions : List Mention
, reblog : Maybe Reblog
@ -214,11 +219,11 @@ type alias StatusRequestBody =
-- spoiler_text: text to be shown as a warning before the actual content
-- visibility: either "direct", "private", "unlisted" or "public"
{ status : String
, in_reply_to_id : Maybe Int
, in_reply_to_id : Maybe StatusId
, spoiler_text : Maybe String
, sensitive : Bool
, visibility : String
, media_ids : List Int
, media_ids : List String
}

10
src/Mastodon/WebSocket.elm

@ -3,7 +3,6 @@ module Mastodon.WebSocket
( StreamType(..)
, WebSocketEvent(..)
, WebSocketMessage
, WebSocketPayload(..)
, subscribeToWebSockets
)
@ -23,18 +22,13 @@ type StreamType
type WebSocketEvent
= StatusUpdateEvent (Result String Status)
| NotificationEvent (Result String Notification)
| StatusDeleteEvent (Result String Int)
| StatusDeleteEvent StatusId
| ErrorEvent String
type WebSocketPayload
= StringPayload String
| IntPayload Int
type alias WebSocketMessage =
{ event : String
, payload : WebSocketPayload
, payload : String
}

12
src/Types.elm

@ -9,7 +9,7 @@ import Time exposing (Time)
type alias Flags =
{ clients : List Client
{ clients : String
, registration : Maybe AppRegistration
}
@ -17,7 +17,7 @@ type alias Flags =
type DraftMsg
= ClearDraft
| CloseAutocomplete
| RemoveMedia Int
| RemoveMedia String
| ResetAutocomplete Bool
| SelectAccount String
| SetAutoState Autocomplete.Msg
@ -72,10 +72,10 @@ type MastodonMsg
| Notifications Bool (MastodonResult (List Notification))
| Reblogged (MastodonResult Status)
| SearchResultsReceived (MastodonResult SearchResults)
| StatusDeleted (MastodonResult Int)
| StatusDeleted (MastodonResult StatusId)
| StatusPosted (MastodonResult Status)
| ThreadStatusLoaded Int (MastodonResult Status)
| ThreadContextLoaded Int (MastodonResult Context)
| ThreadStatusLoaded StatusId (MastodonResult Status)
| ThreadContextLoaded StatusId (MastodonResult Context)
| Unreblogged (MastodonResult Status)
@ -103,7 +103,7 @@ type Msg
| ClearError Int
| ConfirmCancelled Msg
| Confirmed Msg
| DeleteStatus Int
| DeleteStatus StatusId
| DraftEvent DraftMsg
| FilterNotifications NotificationFilter
| FollowAccount Account

4
src/Update/Draft.elm

@ -20,7 +20,7 @@ import Util
autocompleteUpdateConfig : Autocomplete.UpdateConfig Msg Account
autocompleteUpdateConfig =
Autocomplete.updateConfig
{ toId = .id >> toString
{ toId = .id
, onKeyDown =
\code maybeId ->
if code == 38 || code == 40 then
@ -173,7 +173,7 @@ update draftMsg currentUser ({ draft } as model) =
SelectAccount id ->
let
account =
List.filter (\account -> toString account.id == id) draft.autoAccounts
List.filter (\account -> account.id == id) draft.autoAccounts
|> List.head
stringToAtPos =

3
src/Update/Main.elm

@ -2,6 +2,7 @@ module Update.Main exposing (update)
import Command
import List.Extra exposing (removeAt)
import Mastodon.Helper exposing (extractStatusId)
import Mastodon.Model exposing (..)
import Navigation
import Types exposing (..)
@ -155,7 +156,7 @@ update msg model =
OpenThread status ->
{ model | currentView = ThreadView (Thread Nothing Nothing) }
! [ Navigation.newUrl <| "#thread/" ++ (toString status.id) ]
! [ Navigation.newUrl <| "#thread/" ++ extractStatusId status.id ]
FollowAccount account ->
model ! [ Command.follow (List.head model.clients) account ]

6
src/Update/Mastodon.elm

@ -2,7 +2,7 @@ module Update.Mastodon exposing (update)
import Command
import Navigation
import Mastodon.Helper
import Mastodon.Helper exposing (extractStatusId)
import Mastodon.Model exposing (..)
import Task
import Types exposing (..)
@ -116,7 +116,7 @@ update msg ({ accountInfo, search } as model) =
_ ->
model.currentView
}
! [ Command.scrollToThreadStatus <| toString id ]
! [ Command.scrollToThreadStatus <| extractStatusId id ]
Err error ->
{ model
@ -137,7 +137,7 @@ update msg ({ accountInfo, search } as model) =
_ ->
model.currentView
}
! [ Command.scrollToThreadStatus <| toString id ]
! [ Command.scrollToThreadStatus <| extractStatusId id ]
Err error ->
{ model

22
src/Update/Route.elm

@ -1,6 +1,7 @@
module Update.Route exposing (update)
import Command
import Mastodon.Model exposing (StatusId(..))
import Types exposing (..)
import Update.AccountInfo
import Update.Timeline
@ -8,9 +9,9 @@ import UrlParser exposing (..)
type Route
= AccountFollowersRoute Int
| AccountFollowingRoute Int
| AccountRoute Int
= AccountFollowersRoute String
| AccountFollowingRoute String
| AccountRoute String
| AccountSelectorRoute
| BlocksRoute
| FavoriteTimelineRoute
@ -19,7 +20,12 @@ type Route
| LocalTimelineRoute
| MutesRoute
| SearchRoute
| ThreadRoute Int
| ThreadRoute StatusId
statusIdParser : Parser (StatusId -> a) a
statusIdParser =
custom "id" (Ok << StatusId)
route : Parser (Route -> a) a
@ -29,12 +35,12 @@ route =
, map GlobalTimelineRoute (s "global" </> top)
, map FavoriteTimelineRoute (s "favorites" </> top)
, map HashtagRoute (s "hashtag" </> string)
, map ThreadRoute (s "thread" </> int)
, map ThreadRoute (s "thread" </> statusIdParser)
, map BlocksRoute (s "blocks" </> top)
, map MutesRoute (s "mutes" </> top)
, map AccountFollowersRoute (s "account" </> int </> s "followers")
, map AccountFollowingRoute (s "account" </> int </> s "following")
, map AccountRoute (s "account" </> int)
, map AccountFollowersRoute (s "account" </> string </> s "followers")
, map AccountFollowingRoute (s "account" </> string </> s "following")
, map AccountRoute (s "account" </> string)
, map AccountSelectorRoute (s "accounts")
, map SearchRoute (s "search" </> top)
]

10
src/Update/Timeline.elm

@ -47,7 +47,7 @@ cleanUnfollow account currentUser timeline =
{ timeline | entries = List.filter keep timeline.entries }
deleteStatusFromCurrentView : Int -> Model -> CurrentView
deleteStatusFromCurrentView : StatusId -> Model -> CurrentView
deleteStatusFromCurrentView id model =
-- Note: account timeline is already cleaned in deleteStatusFromAllTimelines
case model.currentView of
@ -78,7 +78,7 @@ deleteStatusFromCurrentView id model =
currentView
deleteStatusFromAllTimelines : Int -> Model -> Model
deleteStatusFromAllTimelines : StatusId -> Model -> Model
deleteStatusFromAllTimelines id ({ accountInfo } as model) =
let
accountTimeline =
@ -95,7 +95,7 @@ deleteStatusFromAllTimelines id ({ accountInfo } as model) =
}
deleteStatusFromNotifications : Int -> Timeline NotificationAggregate -> Timeline NotificationAggregate
deleteStatusFromNotifications : StatusId -> Timeline NotificationAggregate -> Timeline NotificationAggregate
deleteStatusFromNotifications statusId notifications =
let
update notification =
@ -109,7 +109,7 @@ deleteStatusFromNotifications statusId notifications =
{ notifications | entries = List.filter update notifications.entries }
deleteStatus : Int -> Timeline Status -> Timeline Status
deleteStatus : StatusId -> Timeline Status -> Timeline Status
deleteStatus statusId ({ entries } as timeline) =
{ timeline
| entries = List.filter (not << Mastodon.Helper.statusReferenced statusId) entries
@ -283,7 +283,7 @@ update append entries links timeline =
}
updateWithBoolFlag : Int -> Bool -> (Status -> Status) -> Model -> Model
updateWithBoolFlag : StatusId -> Bool -> (Status -> Status) -> Model -> Model
updateWithBoolFlag statusId flag statusUpdater ({ accountInfo } as model) =
let
updateStatus status =

27
src/Update/WebSocket.elm

@ -30,13 +30,8 @@ update msg model =
Err error ->
{ model | errors = addErrorNotification error model } ! []
Mastodon.WebSocket.StatusDeleteEvent result ->
case result of
Ok id ->
Update.Timeline.deleteStatusFromAllTimelines id model ! []
Err error ->
{ model | errors = addErrorNotification error model } ! []
Mastodon.WebSocket.StatusDeleteEvent id ->
Update.Timeline.deleteStatusFromAllTimelines id model ! []
Mastodon.WebSocket.NotificationEvent result ->
case result of
@ -76,13 +71,8 @@ update msg model =
Err error ->
{ model | errors = addErrorNotification error model } ! []
Mastodon.WebSocket.StatusDeleteEvent result ->
case result of
Ok id ->
Update.Timeline.deleteStatusFromAllTimelines id model ! []
Err error ->
{ model | errors = addErrorNotification error model } ! []
Mastodon.WebSocket.StatusDeleteEvent id ->
Update.Timeline.deleteStatusFromAllTimelines id model ! []
_ ->
model ! []
@ -104,13 +94,8 @@ update msg model =
Err error ->
{ model | errors = addErrorNotification error model } ! []
Mastodon.WebSocket.StatusDeleteEvent result ->
case result of
Ok id ->
Update.Timeline.deleteStatusFromAllTimelines id model ! []
Err error ->
{ model | errors = addErrorNotification error model } ! []
Mastodon.WebSocket.StatusDeleteEvent id ->
Update.Timeline.deleteStatusFromAllTimelines id model ! []
_ ->
model ! []

14
src/View/Account.elm

@ -6,7 +6,7 @@ import Html.Events exposing (..)
import Html.Keyed as Keyed
import Html.Lazy as Lazy
import List.Extra exposing (find)
import Mastodon.Helper
import Mastodon.Helper exposing (extractStatusId)
import Mastodon.Model exposing (..)
import Types exposing (..)
import View.Common as Common
@ -62,7 +62,7 @@ followView currentUser relationship account =
, div [ class "userinfo" ]
[ strong []
[ a
[ href <| "#account/" ++ (toString account.id) ]
[ href <| "#account/" ++ account.id ]
[ text <|
if account.display_name /= "" then
account.display_name
@ -148,7 +148,7 @@ accountFollowView : CurrentAccountView -> CurrentUser -> AccountInfo -> Html Msg
accountFollowView view currentUser accountInfo =
let
keyedEntry account =
( toString account.id
( account.id
, li [ class "list-group-item status" ]
[ followView
currentUser
@ -179,7 +179,7 @@ accountTimelineView : CurrentUser -> AccountInfo -> Html Msg
accountTimelineView currentUser accountInfo =
let
keyedEntry status =
( toString status.id
( extractStatusId status.id
, Lazy.lazy (statusEntryView "account" "status" currentUser) status
)
@ -221,17 +221,17 @@ counterLinks subView account =
in
div [ class "row account-infos" ]
[ counterLink
("#account/" ++ (toString account.id))
("#account/" ++ account.id)
"Statuses"
statuses_count
(subView == AccountStatusesView)
, counterLink
("#account/" ++ (toString account.id) ++ "/following")
("#account/" ++ account.id ++ "/following")
"Following"
following_count
(subView == AccountFollowingView)
, counterLink
("#account/" ++ (toString account.id) ++ "/followers")
("#account/" ++ account.id ++ "/followers")
"Followers"
followers_count
(subView == AccountFollowersView)

4
src/View/Blocks.elm

@ -35,7 +35,7 @@ blockView currentUser account =
, div [ class "userinfo" ]
[ strong []
[ a
[ href <| "#account/" ++ (toString account.id) ]
[ href <| "#account/" ++ account.id ]
[ text <|
if account.display_name /= "" then
account.display_name
@ -60,7 +60,7 @@ blocksView : Model -> Html Msg
blocksView { currentUser, currentView, blocks, location } =
let
keyedEntry account =
( toString account.id
( account.id
, blockView currentUser account
)

4
src/View/Common.elm

@ -36,7 +36,7 @@ accountLink external account =
if external then
target "_blank"
else
href <| "#account/" ++ (toString account.id)
href <| "#account/" ++ account.id
in
a
[ href account.url
@ -52,7 +52,7 @@ accountAvatarLink external account =
if external then
target "_blank"
else
href <| "#account/" ++ (toString account.id)
href <| "#account/" ++ account.id
avatarClass =
if external then

2
src/View/Draft.elm

@ -65,7 +65,7 @@ viewConfig =
}
in
Autocomplete.viewConfig
{ toId = .id >> toString
{ toId = .id
, ul = [ class "list-group autocomplete-list" ]
, li = customizedLi
}

2
src/View/Formatter.elm

@ -47,7 +47,7 @@ createLinkNode attrs children mentions =
case (getMentionForLink attrs mentions) of
Just mention ->
Html.node "a"
(replaceHref ("#account/" ++ (toString mention.id)) attrs)
(replaceHref ("#account/" ++ mention.id) attrs)
(toVirtualDom mentions children)
Nothing ->

4
src/View/Mutes.elm

@ -35,7 +35,7 @@ muteView currentUser account =
, div [ class "userinfo" ]
[ strong []
[ a
[ href <| "#account/" ++ (toString account.id) ]
[ href <| "#account/" ++ account.id ]
[ text <|
if account.display_name /= "" then
account.display_name
@ -60,7 +60,7 @@ mutesView : Model -> Html Msg
mutesView { currentUser, currentView, mutes, location } =
let
keyedEntry account =
( toString account.id
( account.id
, muteView currentUser account
)

4
src/View/Notification.elm

@ -119,7 +119,7 @@ notificationFollowView currentUser { accounts } =
, formatContent account.note []
|> div
[ class "status-text"
, onClick <| Navigate ("#account/" ++ (toString account.id))
, onClick <| Navigate ("#account/" ++ account.id)
]
]
in
@ -170,7 +170,7 @@ notificationListView : CurrentUser -> NotificationFilter -> Timeline Notificatio
notificationListView currentUser filter notifications =
let
keyedEntry notification =
( toString notification.id
( notification.id
, Lazy.lazy2 notificationEntryView currentUser notification
)

2
src/View/Search.elm

@ -20,7 +20,7 @@ accountListView accounts =
, formatContent account.note []
|> div
[ class "status-text"
, onClick <| Navigate ("#account/" ++ (toString account.id))
, onClick <| Navigate ("#account/" ++ account.id)
]
]
in

12
src/View/Status.elm

@ -9,7 +9,7 @@ import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Keyed as Keyed
import Html.Lazy as Lazy
import Mastodon.Helper
import Mastodon.Helper exposing (extractStatusId)
import Mastodon.Model exposing (..)
import Types exposing (..)
import View.Common as Common
@ -33,7 +33,7 @@ attachmentPreview context sensitive attachments ({ url, preview_url } as attachm
False
attId =
"att" ++ (toString attachment.id) ++ context
"att" ++ attachment.id ++ context
media =
a
@ -68,7 +68,7 @@ attachmentListView : String -> Status -> Html Msg
attachmentListView context { media_attachments, sensitive } =
let
keyedEntry attachments attachment =
( toString attachment.id
( attachment.id
, attachmentPreview context sensitive attachments attachment
)
in
@ -158,7 +158,7 @@ statusContentView context status =
-- Note: Spoilers are dealt with using pure CSS.
let
statusId =
"spoiler" ++ (toString status.id) ++ context
"spoiler" ++ extractStatusId status.id ++ context
in
div [ class "status-text spoiled" ]
[ div
@ -189,7 +189,7 @@ statusEntryView context className currentUser status =
liAttributes =
[ class <| "list-group-item " ++ className ++ " " ++ nsfwClass ]
++ if context == "thread" then
[ id <| "thread-status-" ++ (toString status.id) ]
[ id <| "thread-status-" ++ extractStatusId status.id ]
else
[]
in
@ -203,7 +203,7 @@ statusView : String -> Status -> Html Msg
statusView context ({ account, content, media_attachments, reblog, mentions } as status) =
let
accountLinkAttributes =
[ href <| "#account/" ++ (toString account.id) ]
[ href <| "#account/" ++ account.id ]
in
case reblog of
Just (Reblog reblog) ->

3
src/View/Thread.elm

@ -4,6 +4,7 @@ import View.Common as Common
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Keyed as Keyed
import Mastodon.Helper exposing (extractStatusId)
import Mastodon.Model exposing (..)
import Types exposing (..)
import View.Status exposing (statusEntryView)