clacks/lib/clacks_web/views/frontend_view.ex

218 lines
5.9 KiB
Elixir
Raw Permalink Normal View History

2019-10-06 23:41:18 +00:00
defmodule ClacksWeb.FrontendView do
use ClacksWeb, :view
alias Clacks.{Actor, Activity, Repo, Notification, Object}
2020-04-26 18:45:51 +00:00
alias ClacksWeb.Router.Helpers, as: Routes
alias ClacksWeb.Endpoint
require Logger
2020-04-25 16:30:47 +00:00
@spec display_username(actor :: Actor.t()) :: String.t()
def display_username(%Actor{local: true, data: %{"preferredUsername" => username}}) do
"@" <> username
2020-04-25 16:30:47 +00:00
end
def display_username(%Actor{
local: false,
ap_id: ap_id,
data: %{"preferredUsername" => username}
}) do
2020-04-25 16:30:47 +00:00
%URI{host: host} = URI.parse(ap_id)
"@" <> username <> "@" <> host
end
@spec display_name(actor :: Actor.t()) :: String.t()
def display_name(%Actor{data: %{"name" => name, "preferredUsername" => username}}) do
if is_binary(name) && String.length(name) > 0 do
name
else
username
end
2020-04-25 16:30:47 +00:00
end
2020-04-26 18:45:51 +00:00
def local_actor_link(%Actor{local: true, ap_id: ap_id}), do: ap_id
def local_actor_link(%Actor{local: false, id: id}),
do: Routes.frontend_path(Endpoint, :actor, id)
@spec display_timestamp(
datetime :: String.t() | DateTime.t() | NaiveDateTime.t() | Activity.t()
) :: String.t()
2020-04-26 18:45:51 +00:00
2020-04-25 16:30:47 +00:00
def display_timestamp(str) when is_binary(str) do
display_timestamp(Timex.parse!(str, "{ISO:Extended}"))
end
def display_timestamp(%Activity{data: data, inserted_at: inserted_at}) do
display_timestamp(Map.get(data, "published", inserted_at))
end
def display_timestamp(%{__struct__: struct} = datetime)
when struct == DateTime or struct == NaiveDateTime do
2020-04-25 16:30:47 +00:00
diff = Timex.diff(Timex.now(), datetime, :seconds)
cond do
diff < 60 ->
# less than a minute, seconds
"#{diff}sec"
diff < 60 * 60 ->
# less than an hour, minutes
"#{Integer.floor_div(diff, 60)}min"
diff < 60 * 60 * 24 ->
# less than a day, hours
"#{Integer.floor_div(diff, 60 * 60)}hr"
diff < 60 * 60 * 24 * 7 ->
# less than a week, days
"#{Integer.floor_div(diff, 60 * 60 * 24)}d"
diff < 60 * 60 * 24 * 30 ->
# less than a month(ish), weeks
"#{Integer.floor_div(diff, 60 * 60 * 24 * 7)}wk"
diff < 60 * 60 * 24 * 365 ->
# less than a year, months(ish)
# todo: figure out actually how many months
"#{Integer.floor_div(diff, 60 * 60 * 24 * 30)}mo"
2020-04-25 16:30:47 +00:00
true ->
Timex.format!(datetime, "%F", :strftime)
2020-04-25 16:30:47 +00:00
end
end
2020-04-25 19:47:22 +00:00
@spec iso_datetime(datetime :: String.t() | DateTime.t() | NaiveDateTime.t() | Activity.t()) ::
String.t()
def iso_datetime(str) when is_binary(str) do
str
end
def iso_datetime(%Activity{data: data, inserted_at: inserted_at}) do
iso_datetime(Map.get(data, "published", inserted_at))
end
def iso_datetime(%{__struct__: struct} = datetime)
when struct == DateTime or struct == NaiveDateTime do
Timex.format!(datetime, "{ISO:Extended:Z}")
end
2020-05-24 20:26:10 +00:00
@spec prev_page_path(conn :: Plug.Conn.t(), [
Activity.t() | {Activity.t(), Actor.t()} | Notification.t()
]) ::
2020-04-27 02:57:47 +00:00
String.t() | nil
2020-04-26 18:45:51 +00:00
2020-04-25 19:47:22 +00:00
def prev_page_path(conn, activities) do
if Map.has_key?(conn.query_params, "max_id") do
Phoenix.Controller.current_path(conn, %{
2020-05-24 20:26:10 +00:00
since_id: activities |> List.first() |> timeline_id()
2020-04-25 19:47:22 +00:00
})
else
nil
end
end
2020-05-24 20:26:10 +00:00
@spec next_page_path(conn :: Plug.Conn.t(), [
Activity.t() | {Activity.t(), Actor.t()} | Notification.t()
]) ::
2020-04-27 02:57:47 +00:00
String.t() | nil
2020-04-26 18:45:51 +00:00
2020-04-25 19:47:22 +00:00
def next_page_path(conn, activities) do
if length(activities) < 20 do
nil
else
Phoenix.Controller.current_path(conn, %{
2020-05-24 20:26:10 +00:00
max_id: activities |> List.last() |> timeline_id()
2020-04-25 19:47:22 +00:00
})
end
end
2020-05-24 20:26:10 +00:00
defp timeline_id(%Activity{id: id}), do: id
defp timeline_id({%Activity{id: id}, _}), do: id
defp timeline_id({%Activity{id: id}, _, _}), do: id
defp timeline_id(%Notification{id: id}), do: id
2020-05-23 03:45:48 +00:00
@spec mentions_for_replying_to(Activity.t()) :: String.t()
defp mentions_for_replying_to(conn, %Activity{
object: %Object{data: %{"actor" => actor, "tag" => tags}}
2020-05-23 03:45:48 +00:00
}) do
current_user = conn.assigns[:user] |> Repo.preload(:actor)
tag_actors =
tags
|> Enum.filter(fn
%{"type" => "Mention"} -> true
_ -> false
end)
|> Enum.map(fn %{"href" => ap_id} -> ap_id end)
actors =
[actor | tag_actors]
|> List.delete(current_user.actor.ap_id)
|> Enum.uniq()
mentions =
actors
|> Enum.map(fn ap_id ->
actor = Actor.get_cached_by_ap_id(ap_id)
"@#{actor.nickname}"
end)
case mentions do
[] ->
""
_ ->
Enum.join(mentions, " ") <> ""
end
2020-05-23 03:45:48 +00:00
end
defp mentions_for_replying_to(_), do: ""
@spec render_status_content(activity :: Activity.t()) :: String.t()
defp render_status_content(%Activity{
object: %Object{data: %{"type" => "Note", "content" => content} = note}
}) do
with %{"tag" => tags} <- note,
{:ok, tree} <- Floki.parse_fragment(content) do
tree
|> Floki.traverse_and_update(fn
{"a", attrs, _children} = orig_tree ->
{"href", href} = Enum.find(attrs, fn {name, _} -> name == "href" end)
has_matching_tag =
Enum.any?(tags, fn
%{"type" => "Mention", "href" => ^href} -> true
_ -> false
end)
with true <- has_matching_tag,
%Actor{local: false} = actor <- Actor.get_cached_by_ap_id(href) do
{
"span",
[],
[
orig_tree,
{"a", [{"href", local_actor_link(actor)}, {"class", "local-actor-link"}], ["🔗"]}
]
}
else
_ ->
orig_tree
end
tree ->
tree
end)
|> Floki.raw_html()
# remove the <html> and </html> from the floki rendered output
|> String.slice(6..-8)
|> raw()
else
_ ->
content
end
end
2019-10-06 23:41:18 +00:00
end