1
0
Fork 0

Fix #78: Handle media upload. (#156)

This commit is contained in:
Nicolas Perriault 2017-05-11 10:55:15 +02:00 committed by GitHub
parent 2fae98f452
commit b7001eb8da
13 changed files with 401 additions and 193 deletions

View File

@ -64,6 +64,19 @@
});
});
app.ports.uploadMedia.subscribe(data => {
const files = Array.from(document.getElementById(data.id).files);
const formData = new FormData();
formData.append("file", files[0]);
fetch(data.url, {
method: "POST",
headers: { Authorization: "Bearer " + data.token },
body: formData,
})
.catch(err => app.ports.uploadError.send(err.message))
.then(response => response.text())
.then(app.ports.uploadSuccess.send);
});
</script>
</body>
</html>

View File

@ -410,6 +410,68 @@ li.load-more {
margin-left: 6px;
}
.draft-attachments-field {
margin-top: 12px;
}
.draft-attachments {
display: table;
width: 100%;
padding: 0 2px;
margin-bottom: 0px;
list-style-type: none;
}
.draft-attachment-entry {
display: table-cell;
width: 25%;
height: 60px;
}
.draft-attachment-entry a {
display: block;
height: 60px;
opacity: 0;
background: transparent;
font-size: 45px;
color: #fff;
text-align: center;
}
.draft-attachment-entry a:hover {
transition: 150ms all ease;
opacity: 1;
background: rgba(0, 0, 0, 0.85);
}
.draft-attachment-input {
display: block;
position: relative;
width: 100%;
cursor: pointer;
height: 35px;
outline: 0;
}
.draft-attachment-input:after {
background-image: linear-gradient(#484e55, #3a3f44 60%, #313539);
color: #c8c8c8;
line-height: 32px;
text-align: center;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
content: 'Attach a media';
outline: 0;
}
.draft-attachment-input:hover:after {
background-image: linear-gradient(#020202, #101112 40%, #141618);
}
/* Status text content rules */
.attachment, .hashtag, .ellipsis {

View File

@ -27,6 +27,7 @@ module Command
, unfavouriteStatus
, follow
, unfollow
, uploadMedia
, focusId
, scrollColumnToTop
, scrollColumnToBottom
@ -479,6 +480,20 @@ unfollow client id =
Cmd.none
uploadMedia : Maybe Client -> String -> Cmd Msg
uploadMedia client fileInputId =
case client of
Just { server, token } ->
Ports.uploadMedia
{ id = fileInputId
, url = server ++ ApiUrl.uploadMedia
, token = token
}
Nothing ->
Cmd.none
focusId : String -> Cmd Msg
focusId id =
Dom.focus id |> Task.attempt (always NoOp)

View File

@ -21,6 +21,7 @@ module Mastodon.ApiUrl
, unfavourite
, follow
, unfollow
, uploadMedia
, streaming
, searchAccount
)
@ -149,3 +150,8 @@ unfavourite id =
streaming : String
streaming =
apiPrefix ++ "/streaming/"
uploadMedia : String
uploadMedia =
apiPrefix ++ "/media"

View File

@ -65,7 +65,7 @@ attachmentDecoder =
|> Pipe.required "id" Decode.int
|> Pipe.required "type" Decode.string
|> Pipe.required "url" Decode.string
|> Pipe.required "remote_url" Decode.string
|> Pipe.optional "remote_url" Decode.string ""
|> Pipe.required "preview_url" Decode.string
|> Pipe.required "text_url" (Decode.nullable Decode.string)

View File

@ -99,4 +99,5 @@ statusRequestBodyEncoder statusData =
, ( "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) )
]

View File

@ -190,12 +190,12 @@ type alias StatusRequestBody =
-- sensitive: set this to mark the media of the status as NSFW
-- spoiler_text: text to be shown as a warning before the actual content
-- visibility: either "direct", "private", "unlisted" or "public"
-- TODO: media_ids: array of media IDs to attach to the status (maximum 4)
{ status : String
, in_reply_to_id : Maybe Int
, spoiler_text : Maybe String
, sensitive : Bool
, visibility : String
, media_ids : List Int
}

View File

@ -5,8 +5,13 @@ port module Ports
, scrollIntoView
, saveClients
, setStatus
, uploadMedia
, uploadSuccess
, uploadError
)
-- Outgoing ports
port saveRegistration : String -> Cmd msg
@ -21,3 +26,16 @@ port setStatus : { id : String, status : String } -> Cmd msg
port scrollIntoView : String -> Cmd msg
port uploadMedia : { id : String, url : String, token : String } -> Cmd msg
-- Incoming ports
port uploadError : (String -> msg) -> Sub msg
port uploadSuccess : (String -> msg) -> Sub msg

View File

@ -2,6 +2,7 @@ module Subscription exposing (subscriptions)
import Autocomplete
import Mastodon.WebSocket
import Ports
import Time
import Types exposing (..)
@ -37,6 +38,18 @@ subscriptions { clients, currentView } =
autoCompleteSub =
Sub.map (DraftEvent << SetAutoState) Autocomplete.subscription
uploadSuccessSub =
Ports.uploadSuccess (DraftEvent << UploadResult)
uploadErrorSub =
Ports.uploadError (DraftEvent << UploadError)
in
[ timeSub, userWsSub, otherWsSub, autoCompleteSub ]
|> Sub.batch
Sub.batch
[ timeSub
, userWsSub
, otherWsSub
, autoCompleteSub
, uploadSuccessSub
, uploadErrorSub
]

View File

@ -15,16 +15,20 @@ type alias Flags =
type DraftMsg
= ClearDraft
| CloseAutocomplete
| RemoveMedia Int
| ResetAutocomplete Bool
| SelectAccount String
| SetAutoState Autocomplete.Msg
| ToggleSpoiler Bool
| UpdateInputInformation InputInformation
| UpdateSensitive Bool
| UpdateSpoiler String
| UpdateVisibility String
| UpdateReplyTo Status
| SelectAccount String
| ToggleSpoiler Bool
| UpdateInputInformation InputInformation
| ResetAutocomplete Bool
| CloseAutocomplete
| SetAutoState Autocomplete.Msg
| UploadError String
| UploadMedia String
| UploadResult String
type ViewerMsg
@ -120,6 +124,7 @@ type alias Draft =
, spoilerText : Maybe String
, sensitive : Bool
, visibility : String
, attachments : List Attachment
, statusLength : Int
-- Autocomplete values

View File

@ -7,9 +7,12 @@ module Update.Draft
import Autocomplete
import Command
import Json.Decode as Decode
import Mastodon.Decoder exposing (attachmentDecoder)
import Mastodon.Helper
import Mastodon.Model exposing (..)
import String.Extra
import Update.Error exposing (addErrorNotification)
import Types exposing (..)
import Util
@ -42,6 +45,7 @@ empty =
, spoilerText = Nothing
, sensitive = False
, visibility = "public"
, attachments = []
, statusLength = 0
, autoState = Autocomplete.empty
, autoAtPosition = Nothing
@ -70,11 +74,7 @@ showAutoMenu accounts atPosition query =
update : DraftMsg -> Account -> Model -> ( Model, Cmd Msg )
update draftMsg currentUser model =
let
draft =
model.draft
in
update draftMsg currentUser ({ draft } as model) =
case draftMsg of
ClearDraft ->
{ model | draft = empty }
@ -269,3 +269,38 @@ update draftMsg currentUser model =
}
in
{ model | draft = newDraft } ! []
RemoveMedia id ->
let
newDraft =
{ draft | attachments = List.filter (\a -> a.id /= id) draft.attachments }
in
{ model | draft = newDraft } ! []
UploadMedia id ->
model ! [ Command.uploadMedia (List.head model.clients) id ]
UploadError error ->
{ model | errors = addErrorNotification error model } ! []
UploadResult encoded ->
if encoded == "" then
-- user has likely pressed "Cancel" in the file input dialog
model ! []
else
let
decodedAttachment =
Decode.decodeString attachmentDecoder encoded
in
case decodedAttachment of
Ok attachment ->
{ model
| draft =
{ draft
| attachments = List.append draft.attachments [ attachment ]
}
}
! []
Err error ->
{ model | errors = addErrorNotification error model } ! []

View File

@ -25,6 +25,7 @@ toStatusRequestBody draft =
, spoiler_text = draft.spoilerText
, sensitive = draft.sensitive
, visibility = draft.visibility
, media_ids = List.map .id draft.attachments
}

View File

@ -239,7 +239,7 @@ draftView ({ draft, currentUser } as model) =
textarea
[ id "status"
, class "form-control"
, rows 8
, rows 7
, placeholder <|
if hasSpoiler then
"This text will be hidden by default, as you have enabled a spoiler."
@ -255,6 +255,7 @@ draftView ({ draft, currentUser } as model) =
, autoMenu
]
, visibilitySelector draft
, fileUploadField draft
, div [ class "form-group checkbox" ]
[ label []
[ input
@ -291,3 +292,41 @@ draftView ({ draft, currentUser } as model) =
]
]
]
fileUploadField : Draft -> Html Msg
fileUploadField draft =
let
attachmentPreview attachment =
li
[ class "draft-attachment-entry"
, style
[ ( "background"
, "url(" ++ attachment.preview_url ++ ") center center / cover no-repeat"
)
]
]
[ a
[ href ""
, onClickWithPreventAndStop <| DraftEvent (RemoveMedia attachment.id)
]
[ text "×" ]
]
in
div [ class "draft-attachments-field form-group" ]
[ if List.length draft.attachments > 0 then
ul [ class "draft-attachments" ] <|
List.map attachmentPreview draft.attachments
else
text ""
, if List.length draft.attachments < 4 then
input
[ type_ "file"
, id "draft-attachment"
, class "form-control draft-attachment-input"
, on "change" (Decode.succeed <| DraftEvent (UploadMedia "draft-attachment"))
]
[]
else
text ""
]