parent
2fae98f452
commit
b7001eb8da
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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) )
|
||||
]
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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,202 +74,233 @@ showAutoMenu accounts atPosition query =
|
||||
|
||||
|
||||
update : DraftMsg -> Account -> Model -> ( Model, Cmd Msg )
|
||||
update draftMsg currentUser model =
|
||||
let
|
||||
draft =
|
||||
model.draft
|
||||
in
|
||||
case draftMsg of
|
||||
ClearDraft ->
|
||||
{ model | draft = empty }
|
||||
! [ Command.updateDomStatus empty.status ]
|
||||
update draftMsg currentUser ({ draft } as model) =
|
||||
case draftMsg of
|
||||
ClearDraft ->
|
||||
{ model | draft = empty }
|
||||
! [ Command.updateDomStatus empty.status ]
|
||||
|
||||
ToggleSpoiler enabled ->
|
||||
let
|
||||
newDraft =
|
||||
{ draft
|
||||
| spoilerText =
|
||||
if enabled then
|
||||
Just ""
|
||||
else
|
||||
Nothing
|
||||
}
|
||||
in
|
||||
{ model | draft = newDraft } ! []
|
||||
|
||||
UpdateSensitive sensitive ->
|
||||
{ model | draft = { draft | sensitive = sensitive } } ! []
|
||||
|
||||
UpdateSpoiler spoilerText ->
|
||||
{ model | draft = { draft | spoilerText = Just spoilerText } } ! []
|
||||
|
||||
UpdateVisibility visibility ->
|
||||
{ model | draft = { draft | visibility = visibility } } ! []
|
||||
|
||||
UpdateReplyTo status ->
|
||||
let
|
||||
newStatus =
|
||||
Mastodon.Helper.getReplyPrefix currentUser status
|
||||
in
|
||||
{ model
|
||||
| draft =
|
||||
{ draft
|
||||
| inReplyTo = Just status
|
||||
, status = newStatus
|
||||
, sensitive = Maybe.withDefault False status.sensitive
|
||||
, spoilerText =
|
||||
if status.spoiler_text == "" then
|
||||
Nothing
|
||||
else
|
||||
Just status.spoiler_text
|
||||
, visibility = status.visibility
|
||||
}
|
||||
}
|
||||
! [ Command.focusId "status"
|
||||
, Command.updateDomStatus newStatus
|
||||
]
|
||||
|
||||
UpdateInputInformation { status, selectionStart } ->
|
||||
let
|
||||
stringToPos =
|
||||
String.slice 0 selectionStart status
|
||||
|
||||
atPosition =
|
||||
case (String.right 1 stringToPos) of
|
||||
"@" ->
|
||||
Just selectionStart
|
||||
|
||||
" " ->
|
||||
ToggleSpoiler enabled ->
|
||||
let
|
||||
newDraft =
|
||||
{ draft
|
||||
| spoilerText =
|
||||
if enabled then
|
||||
Just ""
|
||||
else
|
||||
Nothing
|
||||
}
|
||||
in
|
||||
{ model | draft = newDraft } ! []
|
||||
|
||||
_ ->
|
||||
model.draft.autoAtPosition
|
||||
UpdateSensitive sensitive ->
|
||||
{ model | draft = { draft | sensitive = sensitive } } ! []
|
||||
|
||||
query =
|
||||
case atPosition of
|
||||
Just position ->
|
||||
String.slice position (String.length stringToPos) stringToPos
|
||||
UpdateSpoiler spoilerText ->
|
||||
{ model | draft = { draft | spoilerText = Just spoilerText } } ! []
|
||||
|
||||
Nothing ->
|
||||
""
|
||||
UpdateVisibility visibility ->
|
||||
{ model | draft = { draft | visibility = visibility } } ! []
|
||||
|
||||
newDraft =
|
||||
UpdateReplyTo status ->
|
||||
let
|
||||
newStatus =
|
||||
Mastodon.Helper.getReplyPrefix currentUser status
|
||||
in
|
||||
{ model
|
||||
| draft =
|
||||
{ draft
|
||||
| status = status
|
||||
, statusLength = String.length status
|
||||
, autoCursorPosition = selectionStart
|
||||
, autoAtPosition = atPosition
|
||||
, autoQuery = query
|
||||
, showAutoMenu =
|
||||
showAutoMenu
|
||||
draft.autoAccounts
|
||||
draft.autoAtPosition
|
||||
draft.autoQuery
|
||||
| inReplyTo = Just status
|
||||
, status = newStatus
|
||||
, sensitive = Maybe.withDefault False status.sensitive
|
||||
, spoilerText =
|
||||
if status.spoiler_text == "" then
|
||||
Nothing
|
||||
else
|
||||
Just status.spoiler_text
|
||||
, visibility = status.visibility
|
||||
}
|
||||
in
|
||||
{ model | draft = newDraft }
|
||||
! if query /= "" && atPosition /= Nothing then
|
||||
[ Command.searchAccounts (List.head model.clients) query model.draft.autoMaxResults False ]
|
||||
else
|
||||
[]
|
||||
}
|
||||
! [ Command.focusId "status"
|
||||
, Command.updateDomStatus newStatus
|
||||
]
|
||||
|
||||
SelectAccount id ->
|
||||
let
|
||||
account =
|
||||
List.filter (\account -> toString account.id == id) draft.autoAccounts
|
||||
|> List.head
|
||||
UpdateInputInformation { status, selectionStart } ->
|
||||
let
|
||||
stringToPos =
|
||||
String.slice 0 selectionStart status
|
||||
|
||||
stringToAtPos =
|
||||
case draft.autoAtPosition of
|
||||
Just atPosition ->
|
||||
String.slice 0 atPosition draft.status
|
||||
atPosition =
|
||||
case (String.right 1 stringToPos) of
|
||||
"@" ->
|
||||
Just selectionStart
|
||||
|
||||
_ ->
|
||||
""
|
||||
|
||||
stringToPos =
|
||||
String.slice 0 draft.autoCursorPosition draft.status
|
||||
|
||||
newStatus =
|
||||
case draft.autoAtPosition of
|
||||
Just atPosition ->
|
||||
String.Extra.replaceSlice
|
||||
(case account of
|
||||
Just a ->
|
||||
a.acct ++ " "
|
||||
|
||||
Nothing ->
|
||||
""
|
||||
)
|
||||
atPosition
|
||||
((String.length draft.autoQuery) + atPosition)
|
||||
draft.status
|
||||
|
||||
_ ->
|
||||
""
|
||||
|
||||
newDraft =
|
||||
{ draft
|
||||
| status = newStatus
|
||||
, autoAtPosition = Nothing
|
||||
, autoQuery = ""
|
||||
, autoState = Autocomplete.empty
|
||||
, autoAccounts = []
|
||||
, showAutoMenu = False
|
||||
}
|
||||
in
|
||||
{ model | draft = newDraft }
|
||||
-- As we are using defaultValue, we need to update the textarea
|
||||
-- using a port.
|
||||
! [ Command.updateDomStatus newStatus ]
|
||||
|
||||
SetAutoState autoMsg ->
|
||||
let
|
||||
( newState, maybeMsg ) =
|
||||
Autocomplete.update
|
||||
autocompleteUpdateConfig
|
||||
autoMsg
|
||||
draft.autoMaxResults
|
||||
draft.autoState
|
||||
(Util.acceptableAccounts draft.autoQuery draft.autoAccounts)
|
||||
|
||||
newModel =
|
||||
{ model | draft = { draft | autoState = newState } }
|
||||
in
|
||||
case maybeMsg of
|
||||
Just (DraftEvent updateMsg) ->
|
||||
update updateMsg currentUser newModel
|
||||
" " ->
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
newModel ! []
|
||||
model.draft.autoAtPosition
|
||||
|
||||
CloseAutocomplete ->
|
||||
let
|
||||
newDraft =
|
||||
{ draft
|
||||
| showAutoMenu = False
|
||||
, autoState = Autocomplete.reset autocompleteUpdateConfig draft.autoState
|
||||
}
|
||||
in
|
||||
{ model | draft = newDraft } ! []
|
||||
query =
|
||||
case atPosition of
|
||||
Just position ->
|
||||
String.slice position (String.length stringToPos) stringToPos
|
||||
|
||||
ResetAutocomplete toTop ->
|
||||
Nothing ->
|
||||
""
|
||||
|
||||
newDraft =
|
||||
{ draft
|
||||
| status = status
|
||||
, statusLength = String.length status
|
||||
, autoCursorPosition = selectionStart
|
||||
, autoAtPosition = atPosition
|
||||
, autoQuery = query
|
||||
, showAutoMenu =
|
||||
showAutoMenu
|
||||
draft.autoAccounts
|
||||
draft.autoAtPosition
|
||||
draft.autoQuery
|
||||
}
|
||||
in
|
||||
{ model | draft = newDraft }
|
||||
! if query /= "" && atPosition /= Nothing then
|
||||
[ Command.searchAccounts (List.head model.clients) query model.draft.autoMaxResults False ]
|
||||
else
|
||||
[]
|
||||
|
||||
SelectAccount id ->
|
||||
let
|
||||
account =
|
||||
List.filter (\account -> toString account.id == id) draft.autoAccounts
|
||||
|> List.head
|
||||
|
||||
stringToAtPos =
|
||||
case draft.autoAtPosition of
|
||||
Just atPosition ->
|
||||
String.slice 0 atPosition draft.status
|
||||
|
||||
_ ->
|
||||
""
|
||||
|
||||
stringToPos =
|
||||
String.slice 0 draft.autoCursorPosition draft.status
|
||||
|
||||
newStatus =
|
||||
case draft.autoAtPosition of
|
||||
Just atPosition ->
|
||||
String.Extra.replaceSlice
|
||||
(case account of
|
||||
Just a ->
|
||||
a.acct ++ " "
|
||||
|
||||
Nothing ->
|
||||
""
|
||||
)
|
||||
atPosition
|
||||
((String.length draft.autoQuery) + atPosition)
|
||||
draft.status
|
||||
|
||||
_ ->
|
||||
""
|
||||
|
||||
newDraft =
|
||||
{ draft
|
||||
| status = newStatus
|
||||
, autoAtPosition = Nothing
|
||||
, autoQuery = ""
|
||||
, autoState = Autocomplete.empty
|
||||
, autoAccounts = []
|
||||
, showAutoMenu = False
|
||||
}
|
||||
in
|
||||
{ model | draft = newDraft }
|
||||
-- As we are using defaultValue, we need to update the textarea
|
||||
-- using a port.
|
||||
! [ Command.updateDomStatus newStatus ]
|
||||
|
||||
SetAutoState autoMsg ->
|
||||
let
|
||||
( newState, maybeMsg ) =
|
||||
Autocomplete.update
|
||||
autocompleteUpdateConfig
|
||||
autoMsg
|
||||
draft.autoMaxResults
|
||||
draft.autoState
|
||||
(Util.acceptableAccounts draft.autoQuery draft.autoAccounts)
|
||||
|
||||
newModel =
|
||||
{ model | draft = { draft | autoState = newState } }
|
||||
in
|
||||
case maybeMsg of
|
||||
Just (DraftEvent updateMsg) ->
|
||||
update updateMsg currentUser newModel
|
||||
|
||||
_ ->
|
||||
newModel ! []
|
||||
|
||||
CloseAutocomplete ->
|
||||
let
|
||||
newDraft =
|
||||
{ draft
|
||||
| showAutoMenu = False
|
||||
, autoState = Autocomplete.reset autocompleteUpdateConfig draft.autoState
|
||||
}
|
||||
in
|
||||
{ model | draft = newDraft } ! []
|
||||
|
||||
ResetAutocomplete toTop ->
|
||||
let
|
||||
newDraft =
|
||||
{ draft
|
||||
| autoState =
|
||||
if toTop then
|
||||
Autocomplete.resetToFirstItem
|
||||
autocompleteUpdateConfig
|
||||
(Util.acceptableAccounts draft.autoQuery draft.autoAccounts)
|
||||
draft.autoMaxResults
|
||||
draft.autoState
|
||||
else
|
||||
Autocomplete.resetToLastItem
|
||||
autocompleteUpdateConfig
|
||||
(Util.acceptableAccounts draft.autoQuery draft.autoAccounts)
|
||||
draft.autoMaxResults
|
||||
draft.autoState
|
||||
}
|
||||
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
|
||||
newDraft =
|
||||
{ draft
|
||||
| autoState =
|
||||
if toTop then
|
||||
Autocomplete.resetToFirstItem
|
||||
autocompleteUpdateConfig
|
||||
(Util.acceptableAccounts draft.autoQuery draft.autoAccounts)
|
||||
draft.autoMaxResults
|
||||
draft.autoState
|
||||
else
|
||||
Autocomplete.resetToLastItem
|
||||
autocompleteUpdateConfig
|
||||
(Util.acceptableAccounts draft.autoQuery draft.autoAccounts)
|
||||
draft.autoMaxResults
|
||||
draft.autoState
|
||||
}
|
||||
decodedAttachment =
|
||||
Decode.decodeString attachmentDecoder encoded
|
||||
in
|
||||
{ model | draft = newDraft } ! []
|
||||
case decodedAttachment of
|
||||
Ok attachment ->
|
||||
{ model
|
||||
| draft =
|
||||
{ draft
|
||||
| attachments = List.append draft.attachments [ attachment ]
|
||||
}
|
||||
}
|
||||
! []
|
||||
|
||||
Err error ->
|
||||
{ model | errors = addErrorNotification error model } ! []
|
||||
|
@ -25,6 +25,7 @@ toStatusRequestBody draft =
|
||||
, spoiler_text = draft.spoilerText
|
||||
, sensitive = draft.sensitive
|
||||
, visibility = draft.visibility
|
||||
, media_ids = List.map .id draft.attachments
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 ""
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user