* Update model to store multiple clients. * Delete tmp registration data after client creation. * Add minimal account selector view * Update clients so they can have an account attached. * List clients in the account selector. * List accounts in the account selector view. * It works™. * Minor CSS fix. * Reset server value when switching account. * Fix empty black screen on reauth with new client format. * Fix typo. [skip-ci]
This commit is contained in:
parent
51a802f096
commit
7a053b9fa0
@ -17,20 +17,35 @@
|
|||||||
<body>
|
<body>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
<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({
|
const app = Elm.Main.fullscreen({
|
||||||
client: JSON.parse(localStorage.getItem("tooty.client")),
|
clients: clients,
|
||||||
registration: JSON.parse(localStorage.getItem("tooty.registration"))
|
registration: JSON.parse(localStorage.getItem("tooty.registration"))
|
||||||
});
|
});
|
||||||
|
|
||||||
app.ports.saveClient.subscribe(json => {
|
app.ports.saveClients.subscribe(json => {
|
||||||
localStorage.setItem("tooty.client", json);
|
localStorage.setItem("tooty.clients", json);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.ports.saveRegistration.subscribe(json => {
|
app.ports.saveRegistration.subscribe(json => {
|
||||||
localStorage.setItem("tooty.registration", json);
|
localStorage.setItem("tooty.registration", json);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.ports.deleteRegistration.subscribe(json => {
|
||||||
|
localStorage.removeItem("tooty.registration");
|
||||||
|
});
|
||||||
|
|
||||||
app.ports.setStatus.subscribe(function(data) {
|
app.ports.setStatus.subscribe(function(data) {
|
||||||
var element = document.getElementById(data.id);
|
var element = document.getElementById(data.id);
|
||||||
if (element) {
|
if (element) {
|
||||||
|
120
public/style.css
120
public/style.css
@ -157,6 +157,10 @@ li.load-more {
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.current-user .username > span {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.follow-entry {
|
.follow-entry {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -467,6 +471,10 @@ li.load-more {
|
|||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.account-infos.row {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.account-infos a {
|
.account-infos a {
|
||||||
color: #c8c8c8;
|
color: #c8c8c8;
|
||||||
}
|
}
|
||||||
@ -561,71 +569,71 @@ li.load-more {
|
|||||||
/* Scrollbars */
|
/* Scrollbars */
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 8px
|
height: 8px
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #444;
|
background: #444;
|
||||||
border: 0px none #ffffff;
|
border: 0px none #ffffff;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #777;
|
background: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:active {
|
::-webkit-scrollbar-thumb:active {
|
||||||
background: #777;
|
background: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
border: 0px none #ffffff;
|
border: 0px none #ffffff;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: rgba(0,0,0,0.1);
|
background: rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track:hover {
|
::-webkit-scrollbar-track:hover {
|
||||||
background: #2a2e31;
|
background: #2a2e31;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track:active {
|
::-webkit-scrollbar-track:active {
|
||||||
background: #2a2e31;
|
background: #2a2e31;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-corner {
|
::-webkit-scrollbar-corner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Autocomplete */
|
/* Autocomplete */
|
||||||
|
|
||||||
.autocomplete-menu {
|
.autocomplete-menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: -10px;
|
margin-top: -10px;
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-list {
|
.autocomplete-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-item {
|
.autocomplete-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-content: stretch;
|
align-content: stretch;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #777;
|
color: #777;
|
||||||
border-color: #bbb;
|
border-color: #bbb;
|
||||||
border-left-color: #272b30;
|
border-left-color: #272b30;
|
||||||
border-right-color: #272b30;
|
border-right-color: #272b30;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-item:hover,
|
.autocomplete-item:hover,
|
||||||
@ -644,11 +652,11 @@ li.load-more {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-item > img {
|
.autocomplete-item > img {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-item > strong {
|
.autocomplete-item > strong {
|
||||||
@ -667,3 +675,39 @@ li.load-more {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Account selector */
|
||||||
|
|
||||||
|
.account-selector-item {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-content: stretch;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-selector-item > img {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 42px;
|
||||||
|
width: 42px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-selector-item > span {
|
||||||
|
flex: 100 1 auto;
|
||||||
|
align-self: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-selector-item > button {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: right;
|
||||||
|
align-self: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ module Command
|
|||||||
( initCommands
|
( initCommands
|
||||||
, navigateToAuthUrl
|
, navigateToAuthUrl
|
||||||
, registerApp
|
, registerApp
|
||||||
, saveClient
|
, saveClients
|
||||||
, saveRegistration
|
, saveRegistration
|
||||||
, loadNotifications
|
, loadNotifications
|
||||||
, loadUserAccount
|
, loadUserAccount
|
||||||
@ -57,7 +57,9 @@ initCommands registration client authCode =
|
|||||||
Just authCode ->
|
Just authCode ->
|
||||||
case registration of
|
case registration of
|
||||||
Just registration ->
|
Just registration ->
|
||||||
[ getAccessToken registration authCode ]
|
[ getAccessToken registration authCode
|
||||||
|
, Ports.deleteRegistration ""
|
||||||
|
]
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
[]
|
[]
|
||||||
@ -107,11 +109,13 @@ registerApp { server, location } =
|
|||||||
|> send (MastodonEvent << AppRegistered)
|
|> send (MastodonEvent << AppRegistered)
|
||||||
|
|
||||||
|
|
||||||
saveClient : Client -> Cmd Msg
|
saveClients : List Client -> Cmd Msg
|
||||||
saveClient client =
|
saveClients clients =
|
||||||
clientEncoder client
|
clients
|
||||||
|
|> List.map clientEncoder
|
||||||
|
|> Encode.list
|
||||||
|> Encode.encode 0
|
|> Encode.encode 0
|
||||||
|> Ports.saveClient
|
|> Ports.saveClients
|
||||||
|
|
||||||
|
|
||||||
saveRegistration : AppRegistration -> Cmd Msg
|
saveRegistration : AppRegistration -> Cmd Msg
|
||||||
|
@ -9,11 +9,11 @@ import Util
|
|||||||
|
|
||||||
|
|
||||||
init : Flags -> Navigation.Location -> ( Model, Cmd Msg )
|
init : Flags -> Navigation.Location -> ( Model, Cmd Msg )
|
||||||
init { registration, client } location =
|
init { registration, clients } location =
|
||||||
{ server = ""
|
{ server = ""
|
||||||
, currentTime = 0
|
, currentTime = 0
|
||||||
, registration = registration
|
, registration = registration
|
||||||
, client = client
|
, clients = clients
|
||||||
, homeTimeline = Update.Timeline.empty "home-timeline"
|
, homeTimeline = Update.Timeline.empty "home-timeline"
|
||||||
, localTimeline = Update.Timeline.empty "local-timeline"
|
, localTimeline = Update.Timeline.empty "local-timeline"
|
||||||
, globalTimeline = Update.Timeline.empty "global-timeline"
|
, globalTimeline = Update.Timeline.empty "global-timeline"
|
||||||
@ -32,4 +32,4 @@ init { registration, client } location =
|
|||||||
, currentUser = Nothing
|
, currentUser = Nothing
|
||||||
, notificationFilter = NotificationAll
|
, notificationFilter = NotificationAll
|
||||||
}
|
}
|
||||||
! [ Command.initCommands registration client (Util.extractAuthCode location) ]
|
! [ Command.initCommands registration (List.head clients) (Util.extractAuthCode location) ]
|
||||||
|
@ -51,11 +51,31 @@ authorizationCodeEncoder registration authCode =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
accountEncoder : Account -> Encode.Value
|
||||||
|
accountEncoder account =
|
||||||
|
Encode.object
|
||||||
|
[ ( "acct", Encode.string account.acct )
|
||||||
|
, ( "avatar", Encode.string account.avatar )
|
||||||
|
, ( "created_at", Encode.string account.created_at )
|
||||||
|
, ( "display_name", Encode.string account.display_name )
|
||||||
|
, ( "followers_count", Encode.int account.followers_count )
|
||||||
|
, ( "following_count", Encode.int account.following_count )
|
||||||
|
, ( "header", Encode.string account.header )
|
||||||
|
, ( "id", Encode.int account.id )
|
||||||
|
, ( "locked", Encode.bool account.locked )
|
||||||
|
, ( "note", Encode.string account.note )
|
||||||
|
, ( "statuses_count", Encode.int account.statuses_count )
|
||||||
|
, ( "url", Encode.string account.url )
|
||||||
|
, ( "username", Encode.string account.username )
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
clientEncoder : Client -> Encode.Value
|
clientEncoder : Client -> Encode.Value
|
||||||
clientEncoder client =
|
clientEncoder client =
|
||||||
Encode.object
|
Encode.object
|
||||||
[ ( "server", Encode.string client.server )
|
[ ( "server", Encode.string client.server )
|
||||||
, ( "token", Encode.string client.token )
|
, ( "token", Encode.string client.token )
|
||||||
|
, ( "account", encodeMaybe accountEncoder client.account )
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@ type alias Attachment =
|
|||||||
type alias Client =
|
type alias Client =
|
||||||
{ server : Server
|
{ server : Server
|
||||||
, token : Token
|
, token : Token
|
||||||
|
, account : Maybe Account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
port module Ports
|
port module Ports
|
||||||
exposing
|
exposing
|
||||||
( saveRegistration
|
( saveRegistration
|
||||||
|
, deleteRegistration
|
||||||
, scrollIntoView
|
, scrollIntoView
|
||||||
, saveClient
|
, saveClients
|
||||||
, setStatus
|
, setStatus
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,7 +11,10 @@ port module Ports
|
|||||||
port saveRegistration : String -> Cmd msg
|
port saveRegistration : String -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
port saveClient : String -> Cmd msg
|
port deleteRegistration : String -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
|
port saveClients : String -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
port setStatus : { id : String, status : String } -> Cmd msg
|
port setStatus : { id : String, status : String } -> Cmd msg
|
||||||
|
@ -7,14 +7,14 @@ import Types exposing (..)
|
|||||||
|
|
||||||
|
|
||||||
subscriptions : Model -> Sub Msg
|
subscriptions : Model -> Sub Msg
|
||||||
subscriptions { client, currentView } =
|
subscriptions { clients, currentView } =
|
||||||
let
|
let
|
||||||
timeSub =
|
timeSub =
|
||||||
Time.every Time.millisecond Tick
|
Time.every Time.millisecond Tick
|
||||||
|
|
||||||
userWsSub =
|
userWsSub =
|
||||||
Mastodon.WebSocket.subscribeToWebSockets
|
Mastodon.WebSocket.subscribeToWebSockets
|
||||||
client
|
(List.head clients)
|
||||||
Mastodon.WebSocket.UserStream
|
Mastodon.WebSocket.UserStream
|
||||||
NewWebsocketUserMessage
|
NewWebsocketUserMessage
|
||||||
|> Sub.map WebSocketEvent
|
|> Sub.map WebSocketEvent
|
||||||
@ -22,13 +22,13 @@ subscriptions { client, currentView } =
|
|||||||
otherWsSub =
|
otherWsSub =
|
||||||
if currentView == GlobalTimelineView then
|
if currentView == GlobalTimelineView then
|
||||||
Mastodon.WebSocket.subscribeToWebSockets
|
Mastodon.WebSocket.subscribeToWebSockets
|
||||||
client
|
(List.head clients)
|
||||||
Mastodon.WebSocket.GlobalPublicStream
|
Mastodon.WebSocket.GlobalPublicStream
|
||||||
NewWebsocketGlobalMessage
|
NewWebsocketGlobalMessage
|
||||||
|> Sub.map WebSocketEvent
|
|> Sub.map WebSocketEvent
|
||||||
else if currentView == LocalTimelineView then
|
else if currentView == LocalTimelineView then
|
||||||
Mastodon.WebSocket.subscribeToWebSockets
|
Mastodon.WebSocket.subscribeToWebSockets
|
||||||
client
|
(List.head clients)
|
||||||
Mastodon.WebSocket.LocalPublicStream
|
Mastodon.WebSocket.LocalPublicStream
|
||||||
NewWebsocketLocalMessage
|
NewWebsocketLocalMessage
|
||||||
|> Sub.map WebSocketEvent
|
|> Sub.map WebSocketEvent
|
||||||
|
@ -8,7 +8,7 @@ import Time exposing (Time)
|
|||||||
|
|
||||||
|
|
||||||
type alias Flags =
|
type alias Flags =
|
||||||
{ client : Maybe Client
|
{ clients : List Client
|
||||||
, registration : Maybe AppRegistration
|
, registration : Maybe AppRegistration
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +72,7 @@ type Msg
|
|||||||
= AddFavorite Int
|
= AddFavorite Int
|
||||||
| ClearError Int
|
| ClearError Int
|
||||||
| CloseAccount
|
| CloseAccount
|
||||||
|
| CloseAccountSelector
|
||||||
| CloseThread
|
| CloseThread
|
||||||
| DeleteStatus Int
|
| DeleteStatus Int
|
||||||
| DraftEvent DraftMsg
|
| DraftEvent DraftMsg
|
||||||
@ -82,12 +83,14 @@ type Msg
|
|||||||
| MastodonEvent MastodonMsg
|
| MastodonEvent MastodonMsg
|
||||||
| NoOp
|
| NoOp
|
||||||
| OpenThread Status
|
| OpenThread Status
|
||||||
|
| OpenAccountSelector
|
||||||
| ReblogStatus Int
|
| ReblogStatus Int
|
||||||
| Register
|
| Register
|
||||||
| RemoveFavorite Int
|
| RemoveFavorite Int
|
||||||
| ScrollColumn ScrollDirection String
|
| ScrollColumn ScrollDirection String
|
||||||
| ServerChange String
|
| ServerChange String
|
||||||
| SubmitDraft
|
| SubmitDraft
|
||||||
|
| SwitchClient Client
|
||||||
| Tick Time
|
| Tick Time
|
||||||
| UnfollowAccount Int
|
| UnfollowAccount Int
|
||||||
| UrlChange Navigation.Location
|
| UrlChange Navigation.Location
|
||||||
@ -105,6 +108,7 @@ type CurrentView
|
|||||||
AccountFollowersView Account (Timeline Account)
|
AccountFollowersView Account (Timeline Account)
|
||||||
| AccountFollowingView Account (Timeline Account)
|
| AccountFollowingView Account (Timeline Account)
|
||||||
| AccountView Account
|
| AccountView Account
|
||||||
|
| AccountSelectorView
|
||||||
| GlobalTimelineView
|
| GlobalTimelineView
|
||||||
| LocalTimelineView
|
| LocalTimelineView
|
||||||
| ThreadView Thread
|
| ThreadView Thread
|
||||||
@ -172,7 +176,7 @@ type alias Model =
|
|||||||
{ server : String
|
{ server : String
|
||||||
, currentTime : Time
|
, currentTime : Time
|
||||||
, registration : Maybe AppRegistration
|
, registration : Maybe AppRegistration
|
||||||
, client : Maybe Client
|
, clients : List Client
|
||||||
, homeTimeline : Timeline Status
|
, homeTimeline : Timeline Status
|
||||||
, localTimeline : Timeline Status
|
, localTimeline : Timeline Status
|
||||||
, globalTimeline : Timeline Status
|
, globalTimeline : Timeline Status
|
||||||
|
@ -165,7 +165,7 @@ update draftMsg currentUser model =
|
|||||||
in
|
in
|
||||||
{ model | draft = newDraft }
|
{ model | draft = newDraft }
|
||||||
! if query /= "" && atPosition /= Nothing then
|
! if query /= "" && atPosition /= Nothing then
|
||||||
[ Command.searchAccounts model.client query model.draft.autoMaxResults False ]
|
[ Command.searchAccounts (List.head model.clients) query model.draft.autoMaxResults False ]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
|
|
||||||
|
@ -43,6 +43,19 @@ update msg model =
|
|||||||
ClearError index ->
|
ClearError index ->
|
||||||
{ model | errors = removeAt index model.errors } ! []
|
{ model | errors = removeAt index model.errors } ! []
|
||||||
|
|
||||||
|
SwitchClient client ->
|
||||||
|
let
|
||||||
|
newClients =
|
||||||
|
client :: (List.filter (\c -> c.token /= client.token) model.clients)
|
||||||
|
in
|
||||||
|
{ model
|
||||||
|
| clients = newClients
|
||||||
|
, currentView = Update.Timeline.preferred model
|
||||||
|
}
|
||||||
|
! [ Command.loadUserAccount <| Just client
|
||||||
|
, Command.loadTimelines <| Just client
|
||||||
|
]
|
||||||
|
|
||||||
MastodonEvent msg ->
|
MastodonEvent msg ->
|
||||||
let
|
let
|
||||||
( newModel, commands ) =
|
( newModel, commands ) =
|
||||||
@ -67,35 +80,38 @@ update msg model =
|
|||||||
model ! [ Command.registerApp model ]
|
model ! [ Command.registerApp model ]
|
||||||
|
|
||||||
OpenThread status ->
|
OpenThread status ->
|
||||||
model ! [ Command.loadThread model.client status ]
|
model ! [ Command.loadThread (List.head model.clients) status ]
|
||||||
|
|
||||||
|
OpenAccountSelector ->
|
||||||
|
{ model | currentView = AccountSelectorView, server = "" } ! []
|
||||||
|
|
||||||
CloseThread ->
|
CloseThread ->
|
||||||
{ model | currentView = Update.Timeline.preferred model } ! []
|
{ model | currentView = Update.Timeline.preferred model } ! []
|
||||||
|
|
||||||
FollowAccount id ->
|
FollowAccount id ->
|
||||||
model ! [ Command.follow model.client id ]
|
model ! [ Command.follow (List.head model.clients) id ]
|
||||||
|
|
||||||
UnfollowAccount id ->
|
UnfollowAccount id ->
|
||||||
model ! [ Command.unfollow model.client id ]
|
model ! [ Command.unfollow (List.head model.clients) id ]
|
||||||
|
|
||||||
DeleteStatus id ->
|
DeleteStatus id ->
|
||||||
model ! [ Command.deleteStatus model.client id ]
|
model ! [ Command.deleteStatus (List.head model.clients) id ]
|
||||||
|
|
||||||
ReblogStatus id ->
|
ReblogStatus id ->
|
||||||
Update.Timeline.processReblog id True model
|
Update.Timeline.processReblog id True model
|
||||||
! [ Command.reblogStatus model.client id ]
|
! [ Command.reblogStatus (List.head model.clients) id ]
|
||||||
|
|
||||||
UnreblogStatus id ->
|
UnreblogStatus id ->
|
||||||
Update.Timeline.processReblog id False model
|
Update.Timeline.processReblog id False model
|
||||||
! [ Command.unreblogStatus model.client id ]
|
! [ Command.unreblogStatus (List.head model.clients) id ]
|
||||||
|
|
||||||
AddFavorite id ->
|
AddFavorite id ->
|
||||||
Update.Timeline.processFavourite id True model
|
Update.Timeline.processFavourite id True model
|
||||||
! [ Command.favouriteStatus model.client id ]
|
! [ Command.favouriteStatus (List.head model.clients) id ]
|
||||||
|
|
||||||
RemoveFavorite id ->
|
RemoveFavorite id ->
|
||||||
Update.Timeline.processFavourite id False model
|
Update.Timeline.processFavourite id False model
|
||||||
! [ Command.unfavouriteStatus model.client id ]
|
! [ Command.unfavouriteStatus (List.head model.clients) id ]
|
||||||
|
|
||||||
DraftEvent draftMsg ->
|
DraftEvent draftMsg ->
|
||||||
case model.currentUser of
|
case model.currentUser of
|
||||||
@ -113,7 +129,7 @@ update msg model =
|
|||||||
{ model | viewer = viewer } ! [ commands ]
|
{ model | viewer = viewer } ! [ commands ]
|
||||||
|
|
||||||
SubmitDraft ->
|
SubmitDraft ->
|
||||||
model ! [ Command.postStatus model.client <| toStatusRequestBody model.draft ]
|
model ! [ Command.postStatus (List.head model.clients) <| toStatusRequestBody model.draft ]
|
||||||
|
|
||||||
LoadAccount accountId ->
|
LoadAccount accountId ->
|
||||||
{ model
|
{ model
|
||||||
@ -123,25 +139,25 @@ update msg model =
|
|||||||
, accountRelationships = []
|
, accountRelationships = []
|
||||||
, accountRelationship = Nothing
|
, accountRelationship = Nothing
|
||||||
}
|
}
|
||||||
! [ Command.loadAccount model.client accountId ]
|
! [ Command.loadAccount (List.head model.clients) accountId ]
|
||||||
|
|
||||||
TimelineLoadNext id next ->
|
TimelineLoadNext id next ->
|
||||||
Update.Timeline.markAsLoading True id model
|
Update.Timeline.markAsLoading True id model
|
||||||
! [ Command.loadNextTimeline model.client model.currentView id next ]
|
! [ Command.loadNextTimeline (List.head model.clients) model.currentView id next ]
|
||||||
|
|
||||||
ViewAccountFollowers account ->
|
ViewAccountFollowers account ->
|
||||||
{ model
|
{ model
|
||||||
| currentView = AccountFollowersView account model.accountFollowers
|
| currentView = AccountFollowersView account model.accountFollowers
|
||||||
, accountRelationships = []
|
, accountRelationships = []
|
||||||
}
|
}
|
||||||
! [ Command.loadAccountFollowers model.client account.id Nothing ]
|
! [ Command.loadAccountFollowers (List.head model.clients) account.id Nothing ]
|
||||||
|
|
||||||
ViewAccountFollowing account ->
|
ViewAccountFollowing account ->
|
||||||
{ model
|
{ model
|
||||||
| currentView = AccountFollowingView account model.accountFollowing
|
| currentView = AccountFollowingView account model.accountFollowing
|
||||||
, accountRelationships = []
|
, accountRelationships = []
|
||||||
}
|
}
|
||||||
! [ Command.loadAccountFollowing model.client account.id Nothing ]
|
! [ Command.loadAccountFollowing (List.head model.clients) account.id Nothing ]
|
||||||
|
|
||||||
ViewAccountStatuses account ->
|
ViewAccountStatuses account ->
|
||||||
{ model | currentView = AccountView account } ! []
|
{ model | currentView = AccountView account } ! []
|
||||||
@ -162,6 +178,9 @@ update msg model =
|
|||||||
}
|
}
|
||||||
! []
|
! []
|
||||||
|
|
||||||
|
CloseAccountSelector ->
|
||||||
|
{ model | currentView = Update.Timeline.preferred model } ! []
|
||||||
|
|
||||||
FilterNotifications filter ->
|
FilterNotifications filter ->
|
||||||
{ model | notificationFilter = filter } ! []
|
{ model | notificationFilter = filter } ! []
|
||||||
|
|
||||||
|
@ -35,11 +35,11 @@ update msg model =
|
|||||||
Ok { decoded } ->
|
Ok { decoded } ->
|
||||||
let
|
let
|
||||||
client =
|
client =
|
||||||
Client decoded.server decoded.accessToken
|
Client decoded.server decoded.accessToken Nothing
|
||||||
in
|
in
|
||||||
{ model | client = Just client }
|
{ model | clients = client :: model.clients }
|
||||||
! [ Command.loadTimelines <| Just client
|
! [ Command.loadTimelines <| Just client
|
||||||
, Command.saveClient client
|
, Command.saveClients <| client :: model.clients
|
||||||
, Navigation.modifyUrl model.location.pathname
|
, Navigation.modifyUrl model.location.pathname
|
||||||
, Navigation.reload
|
, Navigation.reload
|
||||||
]
|
]
|
||||||
@ -90,7 +90,17 @@ update msg model =
|
|||||||
CurrentUser result ->
|
CurrentUser result ->
|
||||||
case result of
|
case result of
|
||||||
Ok { decoded } ->
|
Ok { decoded } ->
|
||||||
{ model | currentUser = Just decoded } ! []
|
let
|
||||||
|
updatedClients =
|
||||||
|
case model.clients of
|
||||||
|
client :: xs ->
|
||||||
|
({ client | account = Just decoded }) :: xs
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
model.clients
|
||||||
|
in
|
||||||
|
{ model | currentUser = Just decoded, clients = updatedClients }
|
||||||
|
! [ Command.saveClients updatedClients ]
|
||||||
|
|
||||||
Err error ->
|
Err error ->
|
||||||
{ model | errors = addErrorNotification (errorText error) model } ! []
|
{ model | errors = addErrorNotification (errorText error) model } ! []
|
||||||
@ -182,7 +192,11 @@ update msg model =
|
|||||||
| currentView = AccountView decoded
|
| currentView = AccountView decoded
|
||||||
, accountRelationships = []
|
, accountRelationships = []
|
||||||
}
|
}
|
||||||
! [ Command.loadAccountTimeline model.client decoded.id model.accountTimeline.links.next ]
|
! [ Command.loadAccountTimeline
|
||||||
|
(List.head model.clients)
|
||||||
|
decoded.id
|
||||||
|
model.accountTimeline.links.next
|
||||||
|
]
|
||||||
|
|
||||||
Err error ->
|
Err error ->
|
||||||
{ model
|
{ model
|
||||||
@ -203,7 +217,7 @@ update msg model =
|
|||||||
case result of
|
case result of
|
||||||
Ok { decoded, links } ->
|
Ok { decoded, links } ->
|
||||||
{ model | accountFollowers = Update.Timeline.update append decoded links model.accountFollowers }
|
{ model | accountFollowers = Update.Timeline.update append decoded links model.accountFollowers }
|
||||||
! [ Command.loadRelationships model.client <| List.map .id decoded ]
|
! [ Command.loadRelationships (List.head model.clients) <| List.map .id decoded ]
|
||||||
|
|
||||||
Err error ->
|
Err error ->
|
||||||
{ model | errors = addErrorNotification (errorText error) model } ! []
|
{ model | errors = addErrorNotification (errorText error) model } ! []
|
||||||
@ -212,7 +226,7 @@ update msg model =
|
|||||||
case result of
|
case result of
|
||||||
Ok { decoded, links } ->
|
Ok { decoded, links } ->
|
||||||
{ model | accountFollowing = Update.Timeline.update append decoded links model.accountFollowing }
|
{ model | accountFollowing = Update.Timeline.update append decoded links model.accountFollowing }
|
||||||
! [ Command.loadRelationships model.client <| List.map .id decoded ]
|
! [ Command.loadRelationships (List.head model.clients) <| List.map .id decoded ]
|
||||||
|
|
||||||
Err error ->
|
Err error ->
|
||||||
{ model | errors = addErrorNotification (errorText error) model } ! []
|
{ model | errors = addErrorNotification (errorText error) model } ! []
|
||||||
|
76
src/View/AccountSelector.elm
Normal file
76
src/View/AccountSelector.elm
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
module View.AccountSelector exposing (accountSelectorView)
|
||||||
|
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Html.Events exposing (..)
|
||||||
|
import Mastodon.Helper exposing (..)
|
||||||
|
import Mastodon.Model exposing (..)
|
||||||
|
import String.Extra exposing (replace)
|
||||||
|
import Types exposing (..)
|
||||||
|
import View.Auth exposing (authForm)
|
||||||
|
import View.Common exposing (..)
|
||||||
|
|
||||||
|
|
||||||
|
type alias CurrentUser =
|
||||||
|
Maybe Account
|
||||||
|
|
||||||
|
|
||||||
|
accountIdentityView : CurrentUser -> Client -> Html Msg
|
||||||
|
accountIdentityView currentUser client =
|
||||||
|
case client.account of
|
||||||
|
Just account ->
|
||||||
|
let
|
||||||
|
( isCurrentUser, entryClass ) =
|
||||||
|
case currentUser of
|
||||||
|
Just currentUser ->
|
||||||
|
if sameAccount account currentUser then
|
||||||
|
( True, "active" )
|
||||||
|
else
|
||||||
|
( False, "" )
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
( False, "" )
|
||||||
|
in
|
||||||
|
li [ class <| "list-group-item account-selector-item " ++ entryClass ]
|
||||||
|
[ accountAvatar "" account
|
||||||
|
, span []
|
||||||
|
[ strong []
|
||||||
|
[ text <|
|
||||||
|
if account.display_name /= "" then
|
||||||
|
account.display_name
|
||||||
|
else
|
||||||
|
account.username
|
||||||
|
]
|
||||||
|
, br [] []
|
||||||
|
, account.url
|
||||||
|
|> replace "https://" "@"
|
||||||
|
|> replace "/@" "@"
|
||||||
|
|> text
|
||||||
|
]
|
||||||
|
, if isCurrentUser then
|
||||||
|
text ""
|
||||||
|
else
|
||||||
|
button
|
||||||
|
[ class "btn btn-default"
|
||||||
|
, onClick <| SwitchClient client
|
||||||
|
]
|
||||||
|
[ text "Use" ]
|
||||||
|
]
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
text ""
|
||||||
|
|
||||||
|
|
||||||
|
accountSelectorView : Model -> Html Msg
|
||||||
|
accountSelectorView model =
|
||||||
|
div [ class "col-md-3 column" ]
|
||||||
|
[ div [ class "panel panel-default" ]
|
||||||
|
[ closeablePanelheading "account-selector" "user" "Account selector" CloseAccountSelector
|
||||||
|
, ul [ class "list-group " ] <|
|
||||||
|
List.map (accountIdentityView model.currentUser) model.clients
|
||||||
|
, div [ class "panel-body" ]
|
||||||
|
[ h3 [] [ text "Add an account" ]
|
||||||
|
, authForm model
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
@ -7,6 +7,7 @@ import Html.Attributes exposing (..)
|
|||||||
import Mastodon.Model exposing (..)
|
import Mastodon.Model exposing (..)
|
||||||
import Types exposing (..)
|
import Types exposing (..)
|
||||||
import View.Account exposing (accountFollowView, accountTimelineView)
|
import View.Account exposing (accountFollowView, accountTimelineView)
|
||||||
|
import View.AccountSelector exposing (accountSelectorView)
|
||||||
import View.Auth exposing (authView)
|
import View.Auth exposing (authView)
|
||||||
import View.Common as Common
|
import View.Common as Common
|
||||||
import View.Draft exposing (draftView)
|
import View.Draft exposing (draftView)
|
||||||
@ -114,6 +115,9 @@ homepageView model =
|
|||||||
model.accountRelationship
|
model.accountRelationship
|
||||||
account
|
account
|
||||||
|
|
||||||
|
AccountSelectorView ->
|
||||||
|
accountSelectorView model
|
||||||
|
|
||||||
AccountFollowersView account followers ->
|
AccountFollowersView account followers ->
|
||||||
accountFollowView
|
accountFollowView
|
||||||
currentUser
|
currentUser
|
||||||
@ -139,7 +143,7 @@ view : Model -> Html Msg
|
|||||||
view model =
|
view model =
|
||||||
div [ class "container-fluid" ]
|
div [ class "container-fluid" ]
|
||||||
[ errorsListView model
|
[ errorsListView model
|
||||||
, case model.client of
|
, case (List.head model.clients) of
|
||||||
Just client ->
|
Just client ->
|
||||||
homepageView model
|
homepageView model
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module View.Auth exposing (authView)
|
module View.Auth exposing (authForm, authView)
|
||||||
|
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
@ -6,6 +6,33 @@ import Html.Events exposing (..)
|
|||||||
import Types exposing (..)
|
import Types exposing (..)
|
||||||
|
|
||||||
|
|
||||||
|
authForm : Model -> Html Msg
|
||||||
|
authForm model =
|
||||||
|
Html.form [ class "form", onSubmit Register ]
|
||||||
|
[ div [ class "form-group" ]
|
||||||
|
[ label [ for "server" ] [ text "Mastodon server root URL" ]
|
||||||
|
, input
|
||||||
|
[ type_ "url"
|
||||||
|
, class "form-control"
|
||||||
|
, id "server"
|
||||||
|
, required True
|
||||||
|
, placeholder "https://mastodon.social"
|
||||||
|
, value model.server
|
||||||
|
, pattern "https://.+"
|
||||||
|
, onInput ServerChange
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, p [ class "help-block" ]
|
||||||
|
[ text <|
|
||||||
|
"You'll be redirected to that server to authenticate yourself. "
|
||||||
|
++ "We don't have access to your password."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, button [ class "btn btn-primary", type_ "submit" ]
|
||||||
|
[ text "Sign into Tooty" ]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
authView : Model -> Html Msg
|
authView : Model -> Html Msg
|
||||||
authView model =
|
authView model =
|
||||||
div [ class "col-md-4 col-md-offset-4" ]
|
div [ class "col-md-4 col-md-offset-4" ]
|
||||||
@ -26,29 +53,6 @@ authView model =
|
|||||||
, div [ class "panel panel-default" ]
|
, div [ class "panel panel-default" ]
|
||||||
[ div [ class "panel-heading" ] [ text "Authenticate" ]
|
[ div [ class "panel-heading" ] [ text "Authenticate" ]
|
||||||
, div [ class "panel-body" ]
|
, div [ class "panel-body" ]
|
||||||
[ Html.form [ class "form", onSubmit Register ]
|
[ authForm model ]
|
||||||
[ div [ class "form-group" ]
|
|
||||||
[ label [ for "server" ] [ text "Mastodon server root URL" ]
|
|
||||||
, input
|
|
||||||
[ type_ "url"
|
|
||||||
, class "form-control"
|
|
||||||
, id "server"
|
|
||||||
, required True
|
|
||||||
, placeholder "https://mastodon.social"
|
|
||||||
, value model.server
|
|
||||||
, pattern "https://.+"
|
|
||||||
, onInput ServerChange
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, p [ class "help-block" ]
|
|
||||||
[ text <|
|
|
||||||
"You'll be redirected to that server to authenticate yourself. "
|
|
||||||
++ "We don't have access to your password."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, button [ class "btn btn-primary", type_ "submit" ]
|
|
||||||
[ text "Sign into Tooty" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
module View.Common
|
module View.Common
|
||||||
exposing
|
exposing
|
||||||
( accountAvatarLink
|
( accountAvatar
|
||||||
|
, accountAvatarLink
|
||||||
, accountLink
|
, accountLink
|
||||||
, closeablePanelheading
|
, closeablePanelheading
|
||||||
, icon
|
, icon
|
||||||
@ -16,6 +17,11 @@ import Types exposing (..)
|
|||||||
import View.Events exposing (..)
|
import View.Events exposing (..)
|
||||||
|
|
||||||
|
|
||||||
|
accountAvatar : String -> Account -> Html Msg
|
||||||
|
accountAvatar avatarClass account =
|
||||||
|
img [ class avatarClass, src account.avatar ] []
|
||||||
|
|
||||||
|
|
||||||
accountLink : Bool -> Account -> Html Msg
|
accountLink : Bool -> Account -> Html Msg
|
||||||
accountLink external account =
|
accountLink external account =
|
||||||
let
|
let
|
||||||
@ -52,7 +58,7 @@ accountAvatarLink external account =
|
|||||||
, accountHref
|
, accountHref
|
||||||
, title <| "@" ++ account.username
|
, title <| "@" ++ account.username
|
||||||
]
|
]
|
||||||
[ img [ class avatarClass, src account.avatar ] [] ]
|
[ accountAvatar avatarClass account ]
|
||||||
|
|
||||||
|
|
||||||
closeablePanelheading : String -> String -> String -> Msg -> Html Msg
|
closeablePanelheading : String -> String -> String -> Msg -> Html Msg
|
||||||
|
@ -77,7 +77,15 @@ currentUserView currentUser =
|
|||||||
Just currentUser ->
|
Just currentUser ->
|
||||||
div [ class "current-user" ]
|
div [ class "current-user" ]
|
||||||
[ Common.accountAvatarLink False currentUser
|
[ Common.accountAvatarLink False currentUser
|
||||||
, div [ class "username" ] [ Common.accountLink False currentUser ]
|
, div [ class "username" ]
|
||||||
|
[ Common.accountLink False currentUser
|
||||||
|
, span []
|
||||||
|
[ text " ("
|
||||||
|
, a [ href "", onClickWithPreventAndStop <| OpenAccountSelector ]
|
||||||
|
[ text "switch account" ]
|
||||||
|
, text ")"
|
||||||
|
]
|
||||||
|
]
|
||||||
, p [ class "status-text" ] <| formatContent currentUser.note []
|
, p [ class "status-text" ] <| formatContent currentUser.note []
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user