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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -410,6 +410,68 @@ li.load-more {
|
||||||
margin-left: 6px;
|
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 */
|
/* Status text content rules */
|
||||||
|
|
||||||
.attachment, .hashtag, .ellipsis {
|
.attachment, .hashtag, .ellipsis {
|
||||||
|
|
|
@ -27,6 +27,7 @@ module Command
|
||||||
, unfavouriteStatus
|
, unfavouriteStatus
|
||||||
, follow
|
, follow
|
||||||
, unfollow
|
, unfollow
|
||||||
|
, uploadMedia
|
||||||
, focusId
|
, focusId
|
||||||
, scrollColumnToTop
|
, scrollColumnToTop
|
||||||
, scrollColumnToBottom
|
, scrollColumnToBottom
|
||||||
|
@ -479,6 +480,20 @@ unfollow client id =
|
||||||
Cmd.none
|
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 : String -> Cmd Msg
|
||||||
focusId id =
|
focusId id =
|
||||||
Dom.focus id |> Task.attempt (always NoOp)
|
Dom.focus id |> Task.attempt (always NoOp)
|
||||||
|
|
|
@ -21,6 +21,7 @@ module Mastodon.ApiUrl
|
||||||
, unfavourite
|
, unfavourite
|
||||||
, follow
|
, follow
|
||||||
, unfollow
|
, unfollow
|
||||||
|
, uploadMedia
|
||||||
, streaming
|
, streaming
|
||||||
, searchAccount
|
, searchAccount
|
||||||
)
|
)
|
||||||
|
@ -149,3 +150,8 @@ unfavourite id =
|
||||||
streaming : String
|
streaming : String
|
||||||
streaming =
|
streaming =
|
||||||
apiPrefix ++ "/streaming/"
|
apiPrefix ++ "/streaming/"
|
||||||
|
|
||||||
|
|
||||||
|
uploadMedia : String
|
||||||
|
uploadMedia =
|
||||||
|
apiPrefix ++ "/media"
|
||||||
|
|
|
@ -65,7 +65,7 @@ attachmentDecoder =
|
||||||
|> Pipe.required "id" Decode.int
|
|> Pipe.required "id" Decode.int
|
||||||
|> Pipe.required "type" Decode.string
|
|> Pipe.required "type" Decode.string
|
||||||
|> Pipe.required "url" 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 "preview_url" Decode.string
|
||||||
|> Pipe.required "text_url" (Decode.nullable Decode.string)
|
|> Pipe.required "text_url" (Decode.nullable Decode.string)
|
||||||
|
|
||||||
|
|
|
@ -99,4 +99,5 @@ statusRequestBodyEncoder statusData =
|
||||||
, ( "spoiler_text", encodeMaybe Encode.string statusData.spoiler_text )
|
, ( "spoiler_text", encodeMaybe Encode.string statusData.spoiler_text )
|
||||||
, ( "sensitive", Encode.bool statusData.sensitive )
|
, ( "sensitive", Encode.bool statusData.sensitive )
|
||||||
, ( "visibility", Encode.string statusData.visibility )
|
, ( "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
|
-- 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
|
-- spoiler_text: text to be shown as a warning before the actual content
|
||||||
-- visibility: either "direct", "private", "unlisted" or "public"
|
-- visibility: either "direct", "private", "unlisted" or "public"
|
||||||
-- TODO: media_ids: array of media IDs to attach to the status (maximum 4)
|
|
||||||
{ status : String
|
{ status : String
|
||||||
, in_reply_to_id : Maybe Int
|
, in_reply_to_id : Maybe Int
|
||||||
, spoiler_text : Maybe String
|
, spoiler_text : Maybe String
|
||||||
, sensitive : Bool
|
, sensitive : Bool
|
||||||
, visibility : String
|
, visibility : String
|
||||||
|
, media_ids : List Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,13 @@ port module Ports
|
||||||
, scrollIntoView
|
, scrollIntoView
|
||||||
, saveClients
|
, saveClients
|
||||||
, setStatus
|
, setStatus
|
||||||
|
, uploadMedia
|
||||||
|
, uploadSuccess
|
||||||
|
, uploadError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- Outgoing ports
|
||||||
|
|
||||||
|
|
||||||
port saveRegistration : String -> Cmd msg
|
port saveRegistration : String -> Cmd msg
|
||||||
|
|
||||||
|
@ -21,3 +26,16 @@ port setStatus : { id : String, status : String } -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
port scrollIntoView : 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 Autocomplete
|
||||||
import Mastodon.WebSocket
|
import Mastodon.WebSocket
|
||||||
|
import Ports
|
||||||
import Time
|
import Time
|
||||||
import Types exposing (..)
|
import Types exposing (..)
|
||||||
|
|
||||||
|
@ -37,6 +38,18 @@ subscriptions { clients, currentView } =
|
||||||
|
|
||||||
autoCompleteSub =
|
autoCompleteSub =
|
||||||
Sub.map (DraftEvent << SetAutoState) Autocomplete.subscription
|
Sub.map (DraftEvent << SetAutoState) Autocomplete.subscription
|
||||||
|
|
||||||
|
uploadSuccessSub =
|
||||||
|
Ports.uploadSuccess (DraftEvent << UploadResult)
|
||||||
|
|
||||||
|
uploadErrorSub =
|
||||||
|
Ports.uploadError (DraftEvent << UploadError)
|
||||||
in
|
in
|
||||||
[ timeSub, userWsSub, otherWsSub, autoCompleteSub ]
|
Sub.batch
|
||||||
|> Sub.batch
|
[ timeSub
|
||||||
|
, userWsSub
|
||||||
|
, otherWsSub
|
||||||
|
, autoCompleteSub
|
||||||
|
, uploadSuccessSub
|
||||||
|
, uploadErrorSub
|
||||||
|
]
|
||||||
|
|
|
@ -15,16 +15,20 @@ type alias Flags =
|
||||||
|
|
||||||
type DraftMsg
|
type DraftMsg
|
||||||
= ClearDraft
|
= ClearDraft
|
||||||
|
| CloseAutocomplete
|
||||||
|
| RemoveMedia Int
|
||||||
|
| ResetAutocomplete Bool
|
||||||
|
| SelectAccount String
|
||||||
|
| SetAutoState Autocomplete.Msg
|
||||||
|
| ToggleSpoiler Bool
|
||||||
|
| UpdateInputInformation InputInformation
|
||||||
| UpdateSensitive Bool
|
| UpdateSensitive Bool
|
||||||
| UpdateSpoiler String
|
| UpdateSpoiler String
|
||||||
| UpdateVisibility String
|
| UpdateVisibility String
|
||||||
| UpdateReplyTo Status
|
| UpdateReplyTo Status
|
||||||
| SelectAccount String
|
| UploadError String
|
||||||
| ToggleSpoiler Bool
|
| UploadMedia String
|
||||||
| UpdateInputInformation InputInformation
|
| UploadResult String
|
||||||
| ResetAutocomplete Bool
|
|
||||||
| CloseAutocomplete
|
|
||||||
| SetAutoState Autocomplete.Msg
|
|
||||||
|
|
||||||
|
|
||||||
type ViewerMsg
|
type ViewerMsg
|
||||||
|
@ -120,6 +124,7 @@ type alias Draft =
|
||||||
, spoilerText : Maybe String
|
, spoilerText : Maybe String
|
||||||
, sensitive : Bool
|
, sensitive : Bool
|
||||||
, visibility : String
|
, visibility : String
|
||||||
|
, attachments : List Attachment
|
||||||
, statusLength : Int
|
, statusLength : Int
|
||||||
|
|
||||||
-- Autocomplete values
|
-- Autocomplete values
|
||||||
|
|
|
@ -7,9 +7,12 @@ module Update.Draft
|
||||||
|
|
||||||
import Autocomplete
|
import Autocomplete
|
||||||
import Command
|
import Command
|
||||||
|
import Json.Decode as Decode
|
||||||
|
import Mastodon.Decoder exposing (attachmentDecoder)
|
||||||
import Mastodon.Helper
|
import Mastodon.Helper
|
||||||
import Mastodon.Model exposing (..)
|
import Mastodon.Model exposing (..)
|
||||||
import String.Extra
|
import String.Extra
|
||||||
|
import Update.Error exposing (addErrorNotification)
|
||||||
import Types exposing (..)
|
import Types exposing (..)
|
||||||
import Util
|
import Util
|
||||||
|
|
||||||
|
@ -42,6 +45,7 @@ empty =
|
||||||
, spoilerText = Nothing
|
, spoilerText = Nothing
|
||||||
, sensitive = False
|
, sensitive = False
|
||||||
, visibility = "public"
|
, visibility = "public"
|
||||||
|
, attachments = []
|
||||||
, statusLength = 0
|
, statusLength = 0
|
||||||
, autoState = Autocomplete.empty
|
, autoState = Autocomplete.empty
|
||||||
, autoAtPosition = Nothing
|
, autoAtPosition = Nothing
|
||||||
|
@ -70,202 +74,233 @@ showAutoMenu accounts atPosition query =
|
||||||
|
|
||||||
|
|
||||||
update : DraftMsg -> Account -> Model -> ( Model, Cmd Msg )
|
update : DraftMsg -> Account -> Model -> ( Model, Cmd Msg )
|
||||||
update draftMsg currentUser model =
|
update draftMsg currentUser ({ draft } as model) =
|
||||||
let
|
case draftMsg of
|
||||||
draft =
|
ClearDraft ->
|
||||||
model.draft
|
{ model | draft = empty }
|
||||||
in
|
! [ Command.updateDomStatus empty.status ]
|
||||||
case draftMsg of
|
|
||||||
ClearDraft ->
|
|
||||||
{ model | draft = empty }
|
|
||||||
! [ Command.updateDomStatus empty.status ]
|
|
||||||
|
|
||||||
ToggleSpoiler enabled ->
|
ToggleSpoiler enabled ->
|
||||||
let
|
let
|
||||||
newDraft =
|
newDraft =
|
||||||
{ draft
|
{ draft
|
||||||
| spoilerText =
|
| spoilerText =
|
||||||
if enabled then
|
if enabled then
|
||||||
Just ""
|
Just ""
|
||||||
else
|
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
|
|
||||||
|
|
||||||
" " ->
|
|
||||||
Nothing
|
Nothing
|
||||||
|
}
|
||||||
|
in
|
||||||
|
{ model | draft = newDraft } ! []
|
||||||
|
|
||||||
_ ->
|
UpdateSensitive sensitive ->
|
||||||
model.draft.autoAtPosition
|
{ model | draft = { draft | sensitive = sensitive } } ! []
|
||||||
|
|
||||||
query =
|
UpdateSpoiler spoilerText ->
|
||||||
case atPosition of
|
{ model | draft = { draft | spoilerText = Just spoilerText } } ! []
|
||||||
Just position ->
|
|
||||||
String.slice position (String.length stringToPos) stringToPos
|
|
||||||
|
|
||||||
Nothing ->
|
UpdateVisibility visibility ->
|
||||||
""
|
{ model | draft = { draft | visibility = visibility } } ! []
|
||||||
|
|
||||||
newDraft =
|
UpdateReplyTo status ->
|
||||||
|
let
|
||||||
|
newStatus =
|
||||||
|
Mastodon.Helper.getReplyPrefix currentUser status
|
||||||
|
in
|
||||||
|
{ model
|
||||||
|
| draft =
|
||||||
{ draft
|
{ draft
|
||||||
| status = status
|
| inReplyTo = Just status
|
||||||
, statusLength = String.length status
|
, status = newStatus
|
||||||
, autoCursorPosition = selectionStart
|
, sensitive = Maybe.withDefault False status.sensitive
|
||||||
, autoAtPosition = atPosition
|
, spoilerText =
|
||||||
, autoQuery = query
|
if status.spoiler_text == "" then
|
||||||
, showAutoMenu =
|
Nothing
|
||||||
showAutoMenu
|
else
|
||||||
draft.autoAccounts
|
Just status.spoiler_text
|
||||||
draft.autoAtPosition
|
, visibility = status.visibility
|
||||||
draft.autoQuery
|
|
||||||
}
|
}
|
||||||
in
|
}
|
||||||
{ model | draft = newDraft }
|
! [ Command.focusId "status"
|
||||||
! if query /= "" && atPosition /= Nothing then
|
, Command.updateDomStatus newStatus
|
||||||
[ Command.searchAccounts (List.head model.clients) query model.draft.autoMaxResults False ]
|
]
|
||||||
else
|
|
||||||
[]
|
|
||||||
|
|
||||||
SelectAccount id ->
|
UpdateInputInformation { status, selectionStart } ->
|
||||||
let
|
let
|
||||||
account =
|
stringToPos =
|
||||||
List.filter (\account -> toString account.id == id) draft.autoAccounts
|
String.slice 0 selectionStart status
|
||||||
|> List.head
|
|
||||||
|
|
||||||
stringToAtPos =
|
atPosition =
|
||||||
case draft.autoAtPosition of
|
case (String.right 1 stringToPos) of
|
||||||
Just atPosition ->
|
"@" ->
|
||||||
String.slice 0 atPosition draft.status
|
Just selectionStart
|
||||||
|
|
||||||
_ ->
|
" " ->
|
||||||
""
|
Nothing
|
||||||
|
|
||||||
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 ! []
|
model.draft.autoAtPosition
|
||||||
|
|
||||||
CloseAutocomplete ->
|
query =
|
||||||
let
|
case atPosition of
|
||||||
newDraft =
|
Just position ->
|
||||||
{ draft
|
String.slice position (String.length stringToPos) stringToPos
|
||||||
| showAutoMenu = False
|
|
||||||
, autoState = Autocomplete.reset autocompleteUpdateConfig draft.autoState
|
|
||||||
}
|
|
||||||
in
|
|
||||||
{ model | draft = newDraft } ! []
|
|
||||||
|
|
||||||
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
|
let
|
||||||
newDraft =
|
decodedAttachment =
|
||||||
{ draft
|
Decode.decodeString attachmentDecoder encoded
|
||||||
| 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
|
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
|
, spoiler_text = draft.spoilerText
|
||||||
, sensitive = draft.sensitive
|
, sensitive = draft.sensitive
|
||||||
, visibility = draft.visibility
|
, visibility = draft.visibility
|
||||||
|
, media_ids = List.map .id draft.attachments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -239,7 +239,7 @@ draftView ({ draft, currentUser } as model) =
|
||||||
textarea
|
textarea
|
||||||
[ id "status"
|
[ id "status"
|
||||||
, class "form-control"
|
, class "form-control"
|
||||||
, rows 8
|
, rows 7
|
||||||
, placeholder <|
|
, placeholder <|
|
||||||
if hasSpoiler then
|
if hasSpoiler then
|
||||||
"This text will be hidden by default, as you have enabled a spoiler."
|
"This text will be hidden by default, as you have enabled a spoiler."
|
||||||
|
@ -255,6 +255,7 @@ draftView ({ draft, currentUser } as model) =
|
||||||
, autoMenu
|
, autoMenu
|
||||||
]
|
]
|
||||||
, visibilitySelector draft
|
, visibilitySelector draft
|
||||||
|
, fileUploadField draft
|
||||||
, div [ class "form-group checkbox" ]
|
, div [ class "form-group checkbox" ]
|
||||||
[ label []
|
[ label []
|
||||||
[ input
|
[ 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