tooty/src/Mastodon/Helper.elm
Vincent Jousse 69f0cfdc54 Closes #44: Autocomplete usernames. (#107)
* Get @mention in model

* Add autocomplete logic

* Get accounts to autocomplete from the server

* Add autocomplete css

* Check if we should show menu on account search

* Add keyboard events

* Update status with completed username

* Trigger autocomplete when getting accounts back

* Highlight choices on hover

* Put focus on textarea after updating it

* Fix clear draft

* Hit the server only on non empty query

* Lazzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

* Add missing lazy

* Add keyboard subscriptions

* Add images and display name

* Better menu visibility handling

* Add lazy to notifications

* Js formatting.

* Improve styles.

* Add unique keys to costly lists.

* Fix tests.

* Coding style nits.

* Use the encodeUrl helper in ApiUrl.

* Nanonit.

* Improve autocomplete box styling.

* CamelCase draft record

* Move all autocomplete stuff to Draft

* Send status to ports with the reply prefix.

* Clear draft after posting a status.

* Move ports setStatus call to a dedicated Command helper.

* Naming.

* Fix navigation with arrow keys in textarea

* Always autoselect the first item of the menu
2017-05-01 22:10:34 +02:00

173 lines
6.0 KiB
Elm

module Mastodon.Helper
exposing
( extractReblog
, aggregateNotifications
, addNotificationToAggregates
, getReplyPrefix
, notificationToAggregate
, sameAccount
)
import List.Extra exposing (groupWhile, uniqueBy)
import Mastodon.Model exposing (..)
extractReblog : Status -> Status
extractReblog status =
case status.reblog of
Just (Reblog reblog) ->
reblog
Nothing ->
status
getReplyPrefix : Account -> Status -> String
getReplyPrefix replier status =
-- Note: the Mastodon API doesn't consistently return mentions in the order
-- they appear in the status text, we do nothing about that.
let
posters =
case status.reblog of
Just (Mastodon.Model.Reblog reblog) ->
[ reblog.account, status.account ] |> List.map toMention
Nothing ->
(toMention status.account) :: status.mentions
finalPosters =
posters
|> uniqueBy .acct
|> List.filter (\m -> m /= (toMention replier))
|> List.map (\m -> "@" ++ m.acct)
in
(String.join " " finalPosters) ++ " "
toMention : Account -> Mention
toMention { id, url, username, acct } =
Mention id url username acct
notificationToAggregate : Notification -> NotificationAggregate
notificationToAggregate notification =
NotificationAggregate
notification.id
notification.type_
notification.status
[ notification.account ]
notification.created_at
addNotificationToAggregates : Notification -> List NotificationAggregate -> List NotificationAggregate
addNotificationToAggregates notification aggregates =
let
addNewAccountToSameStatus : NotificationAggregate -> Notification -> NotificationAggregate
addNewAccountToSameStatus aggregate notification =
case ( aggregate.status, notification.status ) of
( Just aggregateStatus, Just notificationStatus ) ->
if aggregateStatus.id == notificationStatus.id then
{ aggregate | accounts = notification.account :: aggregate.accounts }
else
aggregate
( _, _ ) ->
aggregate
{-
Let's try to find an already existing aggregate, matching the notification
we are trying to add.
If we find any aggregate, we modify it inplace. If not, we return the
aggregates unmodified
-}
newAggregates =
aggregates
|> List.map
(\aggregate ->
case ( aggregate.type_, notification.type_ ) of
{-
Notification and aggregate are of the follow type.
Add the new following account.
-}
( "follow", "follow" ) ->
{ aggregate | accounts = notification.account :: aggregate.accounts }
{-
Notification is of type follow, but current aggregate
is of another type. Let's continue then.
-}
( _, "follow" ) ->
aggregate
{-
If both types are the same check if we should
add the new account.
-}
( aggregateType, notificationType ) ->
if aggregateType == notificationType then
addNewAccountToSameStatus aggregate notification
else
aggregate
)
in
{-
If we did no modification to the old aggregates it's
because we didn't found any match. So me have to create
a new aggregate
-}
if newAggregates == aggregates then
notificationToAggregate (notification) :: aggregates
else
newAggregates
aggregateNotifications : List Notification -> List NotificationAggregate
aggregateNotifications notifications =
let
only type_ notifications =
List.filter (\n -> n.type_ == type_) notifications
sameStatus n1 n2 =
case ( n1.status, n2.status ) of
( Just r1, Just r2 ) ->
r1.id == r2.id
_ ->
False
extractAggregate statusGroup =
let
accounts =
statusGroup |> List.map .account |> uniqueBy .id
in
case statusGroup of
notification :: _ ->
[ NotificationAggregate
notification.id
notification.type_
notification.status
accounts
notification.created_at
]
[] ->
[]
aggregate statusGroups =
List.map extractAggregate statusGroups |> List.concat
in
[ notifications |> only "reblog" |> groupWhile sameStatus |> aggregate
, notifications |> only "favourite" |> groupWhile sameStatus |> aggregate
, notifications |> only "mention" |> groupWhile sameStatus |> aggregate
, notifications |> only "follow" |> groupWhile (\_ _ -> True) |> aggregate
]
|> List.concat
|> List.sortBy .created_at
|> List.reverse
sameAccount : Mastodon.Model.Account -> Mastodon.Model.Account -> Bool
sameAccount { id, acct, username } account =
-- Note: different instances can share the same id for different accounts.
id == account.id && acct == account.acct && username == account.username