Compare commits
6 Commits
1703c091a7
...
5f8751e142
Author | SHA1 | Date | |
---|---|---|---|
5f8751e142 | |||
5d519242be | |||
2136bf34b9 | |||
6c67e85ca5 | |||
be7ea33085 | |||
ce1580926c |
14
src/Main.elm
14
src/Main.elm
|
@ -2,6 +2,8 @@ import Browser exposing (application, UrlRequest(..))
|
||||||
import Browser.Navigation as Nav
|
import Browser.Navigation as Nav
|
||||||
import Browser.Dom exposing (Viewport, setViewportOf)
|
import Browser.Dom exposing (Viewport, setViewportOf)
|
||||||
import Scylla.Sync exposing (..)
|
import Scylla.Sync exposing (..)
|
||||||
|
import Scylla.Room exposing (..)
|
||||||
|
import Scylla.Messages exposing (..)
|
||||||
import Scylla.Login exposing (..)
|
import Scylla.Login exposing (..)
|
||||||
import Scylla.Api exposing (..)
|
import Scylla.Api exposing (..)
|
||||||
import Scylla.Model exposing (..)
|
import Scylla.Model exposing (..)
|
||||||
|
@ -78,7 +80,7 @@ update msg model = case msg of
|
||||||
ReceiveUserData s r -> updateUserData model s r
|
ReceiveUserData s r -> updateUserData model s r
|
||||||
ChangeRoomText r t -> updateChangeRoomText model r t
|
ChangeRoomText r t -> updateChangeRoomText model r t
|
||||||
SendRoomText r -> updateSendRoomText model r
|
SendRoomText r -> updateSendRoomText model r
|
||||||
SendRoomTextResponse r -> (model, Cmd.none)
|
SendRoomTextResponse t r -> ({ model | sending = Dict.remove t model.sending }, Cmd.none)
|
||||||
ReceiveCompletedReadMarker r -> (model, Cmd.none)
|
ReceiveCompletedReadMarker r -> (model, Cmd.none)
|
||||||
ReceiveCompletedTypingIndicator r -> (model, Cmd.none)
|
ReceiveCompletedTypingIndicator r -> (model, Cmd.none)
|
||||||
ReceiveStoreData d -> updateStoreData model d
|
ReceiveStoreData d -> updateStoreData model d
|
||||||
|
@ -107,8 +109,12 @@ updateMarkdown m { roomId, text, markdown } =
|
||||||
<| encodeLoginInfo
|
<| encodeLoginInfo
|
||||||
<| LoginInfo (Maybe.withDefault "" m.token) m.apiUrl m.loginUsername (m.transactionId + 1))
|
<| LoginInfo (Maybe.withDefault "" m.token) m.apiUrl m.loginUsername (m.transactionId + 1))
|
||||||
sendMessageCmd = sendMarkdownMessage m.apiUrl (Maybe.withDefault "" m.token) (m.transactionId + 1) roomId text markdown
|
sendMessageCmd = sendMarkdownMessage m.apiUrl (Maybe.withDefault "" m.token) (m.transactionId + 1) roomId text markdown
|
||||||
|
newModel =
|
||||||
|
{ m | transactionId = m.transactionId + 1
|
||||||
|
, sending = Dict.insert (m.transactionId + 1) (roomId, TextMessage text) m.sending
|
||||||
|
}
|
||||||
in
|
in
|
||||||
({ m | transactionId = m.transactionId + 1 }, Cmd.batch [ storeValueCmd, sendMessageCmd ])
|
(newModel, Cmd.batch [ storeValueCmd, sendMessageCmd ])
|
||||||
|
|
||||||
updateFileUploadComplete : Model -> RoomId -> File -> (Result Http.Error String) -> (Model, Cmd Msg)
|
updateFileUploadComplete : Model -> RoomId -> File -> (Result Http.Error String) -> (Model, Cmd Msg)
|
||||||
updateFileUploadComplete m rid mime ur =
|
updateFileUploadComplete m rid mime ur =
|
||||||
|
@ -230,7 +236,7 @@ updateViewportAfterMessage : Model -> Result Browser.Dom.Error Viewport -> (Mode
|
||||||
updateViewportAfterMessage m vr =
|
updateViewportAfterMessage m vr =
|
||||||
let
|
let
|
||||||
cmd vp = if vp.scene.height - (vp.viewport.y + vp.viewport.height ) < 100
|
cmd vp = if vp.scene.height - (vp.viewport.y + vp.viewport.height ) < 100
|
||||||
then Task.attempt ViewportChangeComplete <| setViewportOf "events-wrapper" vp.viewport.x vp.scene.height
|
then Task.attempt ViewportChangeComplete <| setViewportOf "messages-wrapper" vp.viewport.x vp.scene.height
|
||||||
else Cmd.none
|
else Cmd.none
|
||||||
in
|
in
|
||||||
(m, Result.withDefault Cmd.none <| Result.map cmd vr)
|
(m, Result.withDefault Cmd.none <| Result.map cmd vr)
|
||||||
|
@ -308,7 +314,7 @@ updateSyncResponse model r notify =
|
||||||
setScrollCmd sr = if List.isEmpty
|
setScrollCmd sr = if List.isEmpty
|
||||||
<| roomMessages sr
|
<| roomMessages sr
|
||||||
then Cmd.none
|
then Cmd.none
|
||||||
else Task.attempt ViewportAfterMessage (Browser.Dom.getViewportOf "events-wrapper")
|
else Task.attempt ViewportAfterMessage (Browser.Dom.getViewportOf "messages-wrapper")
|
||||||
setReadReceiptCmd sr = case (room, List.head <| List.reverse <| roomMessages sr) of
|
setReadReceiptCmd sr = case (room, List.head <| List.reverse <| roomMessages sr) of
|
||||||
(Just rid, Just re) -> setReadMarkers model.apiUrl token rid re.eventId <| Just re.eventId
|
(Just rid, Just re) -> setReadMarkers model.apiUrl token rid re.eventId <| Just re.eventId
|
||||||
_ -> Cmd.none
|
_ -> Cmd.none
|
||||||
|
|
|
@ -90,7 +90,7 @@ sendMessage apiUrl token transactionId room msg contents = request
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMarkdownMessage : ApiUrl -> ApiToken -> Int -> RoomId -> String -> String -> Cmd Msg
|
sendMarkdownMessage : ApiUrl -> ApiToken -> Int -> RoomId -> String -> String -> Cmd Msg
|
||||||
sendMarkdownMessage apiUrl token transactionId room message md = sendMessage apiUrl token transactionId room SendRoomTextResponse
|
sendMarkdownMessage apiUrl token transactionId room message md = sendMessage apiUrl token transactionId room (SendRoomTextResponse transactionId)
|
||||||
[ ("msgtype", string "m.text")
|
[ ("msgtype", string "m.text")
|
||||||
, ("body", string message)
|
, ("body", string message)
|
||||||
, ("formatted_body", string md)
|
, ("formatted_body", string md)
|
||||||
|
@ -98,7 +98,7 @@ sendMarkdownMessage apiUrl token transactionId room message md = sendMessage api
|
||||||
]
|
]
|
||||||
|
|
||||||
sendTextMessage : ApiUrl -> ApiToken -> Int -> RoomId -> String -> Cmd Msg
|
sendTextMessage : ApiUrl -> ApiToken -> Int -> RoomId -> String -> Cmd Msg
|
||||||
sendTextMessage apiUrl token transactionId room message = sendMessage apiUrl token transactionId room SendRoomTextResponse
|
sendTextMessage apiUrl token transactionId room message = sendMessage apiUrl token transactionId room (SendRoomTextResponse transactionId)
|
||||||
[ ("msgtype", string "m.text")
|
[ ("msgtype", string "m.text")
|
||||||
, ("body", string message)
|
, ("body", string message)
|
||||||
]
|
]
|
||||||
|
|
30
src/Scylla/Messages.elm
Normal file
30
src/Scylla/Messages.elm
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
module Scylla.Messages exposing (..)
|
||||||
|
import Scylla.Sync exposing (RoomEvent)
|
||||||
|
import Scylla.Login exposing (Username)
|
||||||
|
|
||||||
|
type SendingMessage = TextMessage String
|
||||||
|
|
||||||
|
type Message =
|
||||||
|
Sending SendingMessage
|
||||||
|
| Received RoomEvent
|
||||||
|
|
||||||
|
messageUsername : Username -> Message -> Username
|
||||||
|
messageUsername u msg = case msg of
|
||||||
|
Sending _ -> u
|
||||||
|
Received re -> re.sender
|
||||||
|
|
||||||
|
mergeMessages : Username -> List Message -> List (Username, List Message)
|
||||||
|
mergeMessages du xs =
|
||||||
|
let
|
||||||
|
initialState = (Nothing, [], [])
|
||||||
|
appendNamed mu ms msl = case mu of
|
||||||
|
Just u -> msl ++ [(u, ms)]
|
||||||
|
Nothing -> msl
|
||||||
|
foldFunction msg (pu, ms, msl) =
|
||||||
|
let
|
||||||
|
nu = Just <| messageUsername du msg
|
||||||
|
in
|
||||||
|
if pu == nu then (pu, ms ++ [msg], msl) else (nu, [msg], appendNamed pu ms msl)
|
||||||
|
(fmu, fms, fmsl) = List.foldl foldFunction initialState xs
|
||||||
|
in
|
||||||
|
appendNamed fmu fms fmsl
|
|
@ -4,6 +4,7 @@ import Scylla.Sync exposing (SyncResponse, HistoryResponse, JoinedRoom, senderNa
|
||||||
import Scylla.Login exposing (LoginResponse, Username, Password)
|
import Scylla.Login exposing (LoginResponse, Username, Password)
|
||||||
import Scylla.UserData exposing (UserData)
|
import Scylla.UserData exposing (UserData)
|
||||||
import Scylla.Route exposing (Route(..), RoomId)
|
import Scylla.Route exposing (Route(..), RoomId)
|
||||||
|
import Scylla.Messages exposing (..)
|
||||||
import Scylla.Storage exposing (..)
|
import Scylla.Storage exposing (..)
|
||||||
import Scylla.Markdown exposing (..)
|
import Scylla.Markdown exposing (..)
|
||||||
import Browser.Navigation as Nav
|
import Browser.Navigation as Nav
|
||||||
|
@ -26,7 +27,8 @@ type alias Model =
|
||||||
, apiUrl : ApiUrl
|
, apiUrl : ApiUrl
|
||||||
, sync : SyncResponse
|
, sync : SyncResponse
|
||||||
, errors : List String
|
, errors : List String
|
||||||
, roomText : Dict String String
|
, roomText : Dict RoomId String
|
||||||
|
, sending : Dict Int (RoomId, SendingMessage)
|
||||||
, transactionId : Int
|
, transactionId : Int
|
||||||
, userData : Dict Username UserData
|
, userData : Dict Username UserData
|
||||||
, connected : Bool
|
, connected : Bool
|
||||||
|
@ -42,7 +44,7 @@ type Msg =
|
||||||
| ChangeRoute Route -- URL changes
|
| ChangeRoute Route -- URL changes
|
||||||
| ChangeRoomText String String -- Change to a room's input text
|
| ChangeRoomText String String -- Change to a room's input text
|
||||||
| SendRoomText String -- Sends a message typed into a given room's input
|
| SendRoomText String -- Sends a message typed into a given room's input
|
||||||
| SendRoomTextResponse (Result Http.Error ()) -- A send message response finished
|
| SendRoomTextResponse Int (Result Http.Error ()) -- A send message response finished
|
||||||
| ViewportAfterMessage (Result Browser.Dom.Error Viewport) -- A message has been received, try scroll (maybe)
|
| ViewportAfterMessage (Result Browser.Dom.Error Viewport) -- A message has been received, try scroll (maybe)
|
||||||
| ViewportChangeComplete (Result Browser.Dom.Error ()) -- We're done changing the viewport.
|
| ViewportChangeComplete (Result Browser.Dom.Error ()) -- We're done changing the viewport.
|
||||||
| ReceiveFirstSyncResponse (Result Http.Error SyncResponse) -- HTTP, Sync has finished
|
| ReceiveFirstSyncResponse (Result Http.Error SyncResponse) -- HTTP, Sync has finished
|
||||||
|
@ -79,12 +81,12 @@ loginUrl = Url.Builder.absolute [ "login" ] []
|
||||||
newUsers : Model -> List Username -> List Username
|
newUsers : Model -> List Username -> List Username
|
||||||
newUsers m lus = List.filter (\u -> not <| Dict.member u m.userData) lus
|
newUsers m lus = List.filter (\u -> not <| Dict.member u m.userData) lus
|
||||||
|
|
||||||
|
joinedRooms : Model -> Dict RoomId JoinedRoom
|
||||||
|
joinedRooms m = Maybe.withDefault Dict.empty <| Maybe.andThen .join <| m.sync.rooms
|
||||||
|
|
||||||
currentRoom : Model -> Maybe JoinedRoom
|
currentRoom : Model -> Maybe JoinedRoom
|
||||||
currentRoom m =
|
currentRoom m =
|
||||||
let
|
Maybe.andThen (\s -> Dict.get s <| joinedRooms m) <| currentRoomId m
|
||||||
roomDict = Maybe.withDefault Dict.empty <| Maybe.andThen .join <| m.sync.rooms
|
|
||||||
in
|
|
||||||
Maybe.andThen (\s -> Dict.get s roomDict) <| currentRoomId m
|
|
||||||
|
|
||||||
currentRoomId : Model -> Maybe RoomId
|
currentRoomId : Model -> Maybe RoomId
|
||||||
currentRoomId m = case m.route of
|
currentRoomId m = case m.route of
|
||||||
|
|
37
src/Scylla/Room.elm
Normal file
37
src/Scylla/Room.elm
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
module Scylla.Room exposing (..)
|
||||||
|
import Scylla.Model exposing (..)
|
||||||
|
import Scylla.Sync exposing (..)
|
||||||
|
import Scylla.Messages exposing (..)
|
||||||
|
import Scylla.Route exposing (..)
|
||||||
|
import Dict
|
||||||
|
|
||||||
|
type alias RoomData =
|
||||||
|
{ joinedRoom : JoinedRoom
|
||||||
|
, sendingMessages : List (SendingMessage, Int)
|
||||||
|
, inputText : Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
roomData : Model -> RoomId -> Maybe RoomData
|
||||||
|
roomData m rid =
|
||||||
|
case Dict.get rid (joinedRooms m) of
|
||||||
|
Just jr -> Just
|
||||||
|
{ joinedRoom = jr
|
||||||
|
, sendingMessages = List.map (\(tid, (_, sm)) -> (sm, tid)) <| List.filter (\(_, (nrid, _)) -> nrid == rid) <| Dict.toList m.sending
|
||||||
|
, inputText = Dict.get rid m.roomText
|
||||||
|
}
|
||||||
|
Nothing -> Nothing
|
||||||
|
|
||||||
|
currentRoomData : Model -> Maybe RoomData
|
||||||
|
currentRoomData m = Maybe.andThen (roomData m) <| currentRoomId m
|
||||||
|
|
||||||
|
extractMessageEvents : List RoomEvent -> List Message
|
||||||
|
extractMessageEvents es = List.map Received
|
||||||
|
<| List.filter (\e -> e.type_ == "m.room.message") es
|
||||||
|
|
||||||
|
extractMessages : RoomData -> List Message
|
||||||
|
extractMessages rd =
|
||||||
|
let
|
||||||
|
eventMessages = extractMessageEvents <| Maybe.withDefault [] <| Maybe.andThen .events rd.joinedRoom.timeline
|
||||||
|
sendingMessages = List.map (\(sm, i) -> Sending sm) rd.sendingMessages
|
||||||
|
in
|
||||||
|
eventMessages ++ sendingMessages
|
|
@ -3,6 +3,8 @@ import Scylla.Model exposing (..)
|
||||||
import Scylla.Sync exposing (..)
|
import Scylla.Sync exposing (..)
|
||||||
import Scylla.Route exposing (..)
|
import Scylla.Route exposing (..)
|
||||||
import Scylla.Fnv as Fnv
|
import Scylla.Fnv as Fnv
|
||||||
|
import Scylla.Room exposing (..)
|
||||||
|
import Scylla.Messages exposing (..)
|
||||||
import Scylla.Login exposing (Username)
|
import Scylla.Login exposing (Username)
|
||||||
import Scylla.Http exposing (fullMediaUrl)
|
import Scylla.Http exposing (fullMediaUrl)
|
||||||
import Scylla.Api exposing (ApiUrl)
|
import Scylla.Api exposing (ApiUrl)
|
||||||
|
@ -12,7 +14,7 @@ import Svg
|
||||||
import Svg.Attributes
|
import Svg.Attributes
|
||||||
import Url.Builder
|
import Url.Builder
|
||||||
import Json.Decode as Decode
|
import Json.Decode as Decode
|
||||||
import Html exposing (Html, Attribute, div, input, text, button, div, span, a, h2, h3, table, td, tr, img, textarea, video, source)
|
import Html exposing (Html, Attribute, div, input, text, button, div, span, a, h2, h3, table, td, tr, img, textarea, video, source, p)
|
||||||
import Html.Attributes exposing (type_, value, href, class, style, src, id, rows, controls, src)
|
import Html.Attributes exposing (type_, value, href, class, style, src, id, rows, controls, src)
|
||||||
import Html.Events exposing (onInput, onClick, preventDefaultOn)
|
import Html.Events exposing (onInput, onClick, preventDefaultOn)
|
||||||
import Dict exposing (Dict)
|
import Dict exposing (Dict)
|
||||||
|
@ -41,9 +43,8 @@ stringColor s =
|
||||||
viewFull : Model -> List (Html Msg)
|
viewFull : Model -> List (Html Msg)
|
||||||
viewFull model =
|
viewFull model =
|
||||||
let
|
let
|
||||||
room r = Maybe.map (\jr -> (r, jr))
|
room r = Maybe.map (\rd -> (r, rd))
|
||||||
<| Maybe.andThen (Dict.get r)
|
<| roomData model r
|
||||||
<| Maybe.andThen .join model.sync.rooms
|
|
||||||
core = case model.route of
|
core = case model.route of
|
||||||
Login -> loginView model
|
Login -> loginView model
|
||||||
Base -> baseView model Nothing
|
Base -> baseView model Nothing
|
||||||
|
@ -59,7 +60,7 @@ errorsView = div [ class "errors-wrapper" ] << List.indexedMap errorView
|
||||||
errorView : Int -> String -> Html Msg
|
errorView : Int -> String -> Html Msg
|
||||||
errorView i s = div [ class "error-wrapper", onClick <| DismissError i ] [ iconView "alert-triangle", text s ]
|
errorView i s = div [ class "error-wrapper", onClick <| DismissError i ] [ iconView "alert-triangle", text s ]
|
||||||
|
|
||||||
baseView : Model -> Maybe (String, JoinedRoom) -> Html Msg
|
baseView : Model -> Maybe (String, RoomData) -> Html Msg
|
||||||
baseView m jr =
|
baseView m jr =
|
||||||
let
|
let
|
||||||
roomView = Maybe.map (\(id, r) -> joinedRoomView m id r) jr
|
roomView = Maybe.map (\(id, r) -> joinedRoomView m id r) jr
|
||||||
|
@ -136,13 +137,12 @@ loginView m = div [ class "login-wrapper" ]
|
||||||
, button [ onClick AttemptLogin ] [ text "Log In" ]
|
, button [ onClick AttemptLogin ] [ text "Log In" ]
|
||||||
]
|
]
|
||||||
|
|
||||||
joinedRoomView : Model -> RoomId -> JoinedRoom -> Html Msg
|
joinedRoomView : Model -> RoomId -> RoomData -> Html Msg
|
||||||
joinedRoomView m roomId jr =
|
joinedRoomView m roomId rd =
|
||||||
let
|
let
|
||||||
events = Maybe.withDefault [] <| Maybe.andThen .events jr.timeline
|
renderedMessages = List.map (userMessagesView m) <| mergeMessages m.loginUsername <| extractMessages rd
|
||||||
renderedEvents = List.filterMap (eventView m) events
|
messagesWrapper = messagesWrapperView m roomId renderedMessages
|
||||||
eventWrapper = eventWrapperView m roomId renderedEvents
|
typing = List.map (displayName m) <| roomTypingUsers rd.joinedRoom
|
||||||
typing = List.map (displayName m) <| roomTypingUsers jr
|
|
||||||
typingText = String.join ", " typing
|
typingText = String.join ", " typing
|
||||||
typingSuffix = case List.length typing of
|
typingSuffix = case List.length typing of
|
||||||
0 -> ""
|
0 -> ""
|
||||||
|
@ -162,8 +162,8 @@ joinedRoomView m roomId jr =
|
||||||
]
|
]
|
||||||
in
|
in
|
||||||
div [ class "room-wrapper" ]
|
div [ class "room-wrapper" ]
|
||||||
[ h2 [] [ text <| Maybe.withDefault "<No Name>" <| roomName jr ]
|
[ h2 [] [ text <| Maybe.withDefault "<No Name>" <| roomName rd.joinedRoom ]
|
||||||
, eventWrapper
|
, messagesWrapper
|
||||||
, typingWrapper
|
, typingWrapper
|
||||||
, messageInput
|
, messageInput
|
||||||
]
|
]
|
||||||
|
@ -187,58 +187,62 @@ iconView name =
|
||||||
[ Svg.Attributes.class "feather-icon"
|
[ Svg.Attributes.class "feather-icon"
|
||||||
] [ Svg.use [ Svg.Attributes.xlinkHref (url ++ "#" ++ name) ] [] ]
|
] [ Svg.use [ Svg.Attributes.xlinkHref (url ++ "#" ++ name) ] [] ]
|
||||||
|
|
||||||
eventWrapperView : Model -> RoomId -> List (Html Msg) -> Html Msg
|
messagesWrapperView : Model -> RoomId -> List (Html Msg) -> Html Msg
|
||||||
eventWrapperView m rid es = div [ class "events-wrapper", id "events-wrapper" ]
|
messagesWrapperView m rid es = div [ class "messages-wrapper", id "messages-wrapper" ]
|
||||||
[ a [ class "history-link", onClick <| History rid ] [ text "Load older messages" ]
|
[ a [ class "history-link", onClick <| History rid ] [ text "Load older messages" ]
|
||||||
, table [ class "events-table" ] es
|
, table [ class "messages-table" ] es
|
||||||
]
|
]
|
||||||
|
|
||||||
eventView : Model -> RoomEvent -> Maybe (Html Msg)
|
senderView : Model -> Username -> Html Msg
|
||||||
eventView m re =
|
senderView m s =
|
||||||
|
span [ style "color" <| stringColor s, class "sender-wrapper" ] [ text <| displayName m s ]
|
||||||
|
|
||||||
|
userMessagesView : Model -> (Username, List Message) -> Html Msg
|
||||||
|
userMessagesView m (u, ms) =
|
||||||
let
|
let
|
||||||
viewFunction = case re.type_ of
|
wrap h = div [ class "message" ] [ h ]
|
||||||
"m.room.message" -> Just messageView
|
|
||||||
_ -> Nothing
|
|
||||||
createRow mhtml = tr []
|
|
||||||
[ td [] [ eventSenderView m re.sender ]
|
|
||||||
, td [] [ mhtml ]
|
|
||||||
]
|
|
||||||
in
|
in
|
||||||
Maybe.map createRow
|
tr []
|
||||||
<| Maybe.andThen (\f -> f m re) viewFunction
|
[ td [] [ senderView m u ]
|
||||||
|
, td [] <| List.map wrap <| List.filterMap (messageView m) ms
|
||||||
|
]
|
||||||
|
|
||||||
eventSenderView : Model -> Username -> Html Msg
|
messageView : Model -> Message -> Maybe (Html Msg)
|
||||||
eventSenderView m s =
|
messageView m msg = case msg of
|
||||||
span [ style "background-color" <| stringColor s, class "sender-wrapper" ] [ text <| displayName m s ]
|
Sending t -> Just <| sendingMessageView m t
|
||||||
|
Received re -> roomEventView m re
|
||||||
|
|
||||||
messageView : Model -> RoomEvent -> Maybe (Html Msg)
|
sendingMessageView : Model -> SendingMessage -> Html Msg
|
||||||
messageView m re =
|
sendingMessageView m msg = case msg of
|
||||||
|
TextMessage t -> text t
|
||||||
|
|
||||||
|
roomEventView : Model -> RoomEvent -> Maybe (Html Msg)
|
||||||
|
roomEventView m re =
|
||||||
let
|
let
|
||||||
msgtype = Decode.decodeValue (Decode.field "msgtype" Decode.string) re.content
|
msgtype = Decode.decodeValue (Decode.field "msgtype" Decode.string) re.content
|
||||||
in
|
in
|
||||||
case msgtype of
|
case msgtype of
|
||||||
Ok "m.text" -> messageTextView m re
|
Ok "m.text" -> roomEventTextView m re
|
||||||
Ok "m.image" -> messageImageView m re
|
Ok "m.image" -> roomEventImageView m re
|
||||||
Ok "m.file" -> messageFileView m re
|
Ok "m.file" -> roomEventFileView m re
|
||||||
Ok "m.video" -> messageVideoView m re
|
Ok "m.video" -> roomEventVideoView m re
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
messageTextView : Model -> RoomEvent -> Maybe (Html Msg)
|
roomEventTextView : Model -> RoomEvent -> Maybe (Html Msg)
|
||||||
messageTextView m re =
|
roomEventTextView m re =
|
||||||
let
|
let
|
||||||
body = Decode.decodeValue (Decode.field "body" Decode.string) re.content
|
body = Decode.decodeValue (Decode.field "body" Decode.string) re.content
|
||||||
customHtml = Maybe.map Html.Parser.Util.toVirtualDom
|
customHtml = Maybe.map Html.Parser.Util.toVirtualDom
|
||||||
<| Maybe.andThen (Result.toMaybe << Html.Parser.run )
|
<| Maybe.andThen (Result.toMaybe << Html.Parser.run )
|
||||||
<| Result.toMaybe
|
<| Result.toMaybe
|
||||||
<| Decode.decodeValue (Decode.field "formatted_body" Decode.string) re.content
|
<| Decode.decodeValue (Decode.field "formatted_body" Decode.string) re.content
|
||||||
wrap mtext = span [] [ text mtext ]
|
|
||||||
in
|
in
|
||||||
case customHtml of
|
case customHtml of
|
||||||
Just c -> Just <| div [ class "markdown-wrapper" ] c
|
Just c -> Just <| div [] c
|
||||||
Nothing -> Maybe.map wrap <| Result.toMaybe body
|
Nothing -> Maybe.map (p [] << List.singleton << text) <| Result.toMaybe body
|
||||||
|
|
||||||
messageImageView : Model -> RoomEvent -> Maybe (Html Msg)
|
roomEventImageView : Model -> RoomEvent -> Maybe (Html Msg)
|
||||||
messageImageView m re =
|
roomEventImageView m re =
|
||||||
let
|
let
|
||||||
body = Decode.decodeValue (Decode.field "url" Decode.string) re.content
|
body = Decode.decodeValue (Decode.field "url" Decode.string) re.content
|
||||||
in
|
in
|
||||||
|
@ -246,8 +250,8 @@ messageImageView m re =
|
||||||
<| Maybe.map (contentRepositoryDownloadUrl m.apiUrl)
|
<| Maybe.map (contentRepositoryDownloadUrl m.apiUrl)
|
||||||
<| Result.toMaybe body
|
<| Result.toMaybe body
|
||||||
|
|
||||||
messageFileView : Model -> RoomEvent -> Maybe (Html Msg)
|
roomEventFileView : Model -> RoomEvent -> Maybe (Html Msg)
|
||||||
messageFileView m re =
|
roomEventFileView m re =
|
||||||
let
|
let
|
||||||
decoder = Decode.map2 (\l r -> (l, r)) (Decode.field "url" Decode.string) (Decode.field "body" Decode.string)
|
decoder = Decode.map2 (\l r -> (l, r)) (Decode.field "url" Decode.string) (Decode.field "body" Decode.string)
|
||||||
fileData = Decode.decodeValue decoder re.content
|
fileData = Decode.decodeValue decoder re.content
|
||||||
|
@ -256,8 +260,8 @@ messageFileView m re =
|
||||||
<| Maybe.map (\(url, name) -> (contentRepositoryDownloadUrl m.apiUrl url, name))
|
<| Maybe.map (\(url, name) -> (contentRepositoryDownloadUrl m.apiUrl url, name))
|
||||||
<| Result.toMaybe fileData
|
<| Result.toMaybe fileData
|
||||||
|
|
||||||
messageVideoView : Model -> RoomEvent -> Maybe (Html Msg)
|
roomEventVideoView : Model -> RoomEvent -> Maybe (Html Msg)
|
||||||
messageVideoView m re =
|
roomEventVideoView m re =
|
||||||
let
|
let
|
||||||
decoder = Decode.map2 (\l r -> (l, r))
|
decoder = Decode.map2 (\l r -> (l, r))
|
||||||
(Decode.field "url" Decode.string)
|
(Decode.field "url" Decode.string)
|
||||||
|
|
|
@ -4,6 +4,7 @@ $primary-color-highlight: #4298C7;
|
||||||
$primary-color-light: #9FDBFB;
|
$primary-color-light: #9FDBFB;
|
||||||
|
|
||||||
$background-color: #1b1e21;
|
$background-color: #1b1e21;
|
||||||
|
$background-color-light: lighten($background-color, 4%);
|
||||||
$background-color-dark: darken($background-color, 4%);
|
$background-color-dark: darken($background-color, 4%);
|
||||||
|
|
||||||
$error-color: #f01d43;
|
$error-color: #f01d43;
|
||||||
|
@ -16,6 +17,8 @@ $inactive-input-border-color: darken($inactive-input-color, 10%);
|
||||||
|
|
||||||
$transition-duration: .125s;
|
$transition-duration: .125s;
|
||||||
|
|
||||||
|
$inset-shadow: inset 0px 0px 5px rgba(0, 0, 0, .25);
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
@ -77,6 +80,7 @@ h2, h3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
a.file-wrapper {
|
a.file-wrapper {
|
||||||
|
padding: 5px 0px 5px 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
@ -141,7 +145,7 @@ div.base-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
padding: 5px;
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,6 +155,7 @@ div.base-wrapper {
|
||||||
*/
|
*/
|
||||||
div.rooms-wrapper {
|
div.rooms-wrapper {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
background-color: $background-color-light;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.room-link-wrapper {
|
div.room-link-wrapper {
|
||||||
|
@ -186,6 +191,8 @@ div.room-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
box-shadow: $inset-shadow;
|
||||||
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.typing-wrapper {
|
div.typing-wrapper {
|
||||||
|
@ -212,7 +219,7 @@ div.message-wrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.events-wrapper {
|
div.messages-wrapper {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
|
@ -225,7 +232,7 @@ div.events-wrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table.events-table {
|
table.messages-table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
|
@ -252,13 +259,9 @@ table.events-table {
|
||||||
}
|
}
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr:nth-child(2n) {
|
|
||||||
background-color: $background-color-dark;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.markdown-wrapper {
|
div.message {
|
||||||
p {
|
p {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
@ -281,7 +284,7 @@ div.markdown-wrapper {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: $background-color;
|
background-color: $background-color;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: inset 0px 0px 5px rgba(0, 0, 0, .15);
|
box-shadow: $inset-shadow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +294,8 @@ span.sender-wrapper {
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: center;
|
text-align: right;
|
||||||
|
font-weight: 800;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user