feat: Add content warnings

This commit is contained in:
Ro 2024-11-05 22:19:26 -06:00
parent 2685a8dfb8
commit 0bca082286
Signed by: ro
GPG key ID: 5B5AD5A568CDABF9
5 changed files with 109 additions and 42 deletions

View file

@ -5,7 +5,7 @@
- [x] Post formatting
- [x] Deleting posts
- [x] Sending posts w/ images
- [ ] Making posts with CWs
- [x] Making posts with CWs
- [ ] Making polls
- [ ] Followers-only posts (or maybe this is handled because we only send posts to followers? but we also include public in the TO field?)
- [] Sending posts with videos
@ -39,11 +39,11 @@
- [x] Your posts show up in timeline
- [x] Posts from accounts you follow show up in timeline
- [x] Show the actor avatar and display name
- [ ] Receiving posts w/ images
- [x] Receiving posts w/ images
- [ ] Receiving posts w/ videos
- [ ] Liking posts
- [ ] Unliking posts
- [ ] Displaying CW posts behind CW
- [x] Displaying CW posts behind CW
- [ ] Displaying polls
- [ ] Voting in polls

View file

@ -9,7 +9,7 @@ defmodule Postland.Activities do
alias Postland.Repo
alias Postland.Follows
def record_markdown_post(markdown, attachments) do
def record_markdown_post(markdown, cw, attachments) do
id = Ecto.UUID.autogenerate()
html = Earmark.as_html!(markdown)
@ -21,6 +21,22 @@ defmodule Postland.Activities do
# CW posts have a summary field which is the warning itself, a content
# field (like a non-CW post) that is the body, and a "sensitive" => true field
note =
%{
"id" => url(~p"/activities/#{id}"),
"type" => "Note",
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
"attributedTo" => Postland.my_actor_id(),
"mediaType" => "text/html",
"content" => html,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [
"#{Postland.my_actor_id()}/followers"
],
"attachment" => attachments
}
|> maybe_add_cw(cw)
activity =
%{
"@context" => [
@ -38,19 +54,7 @@ defmodule Postland.Activities do
"id" => url(~p"/activities/#{id}"),
"type" => "Create",
"actor" => Postland.my_actor_id(),
"object" => %{
"id" => url(~p"/activities/#{id}"),
"type" => "Note",
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
"attributedTo" => Postland.my_actor_id(),
"mediaType" => "text/html",
"content" => html,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [
"#{Postland.my_actor_id()}/followers"
],
"attachment" => attachments
}
"object" => note
}
activity
@ -59,6 +63,10 @@ defmodule Postland.Activities do
|> broadcast_to_followers(activity)
end
defp maybe_add_cw(note, nil), do: note
defp maybe_add_cw(note, ""), do: note
defp maybe_add_cw(note, cw), do: Map.merge(note, %{"sensitive" => true, "summary" => cw})
def delete_post(post) do
activity =
%{

View file

@ -17,6 +17,12 @@ defmodule Postland.Timeline do
# TODO: Filter down to just image/* and video/* MIME types
end
def cw(activity) do
activity.data
|> Map.get("items")
|> Enum.find_value(fn item -> item["sensitive"] && item["summary"] end)
end
def attribution(activity) do
Postland.Actors.actor(get_from_html_item(activity, "attributedTo"))
end

View file

@ -87,6 +87,16 @@ defmodule PostlandWeb.CoreComponents do
<div class="min-w-0 flex-1 relative">
<div class="overflow-hidden bg-white relative rounded-lg shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-violet-600">
<.form phx-submit="create_post" phx-change="change_post" id="create-post-form">
<div :if={@cw} class="px-3 pt-2 text-orange-700">
<.icon name="hero-exclamation-triangle" />
<input
id="cw-input"
name="cw"
value={@cw}
placeholder="add content warning"
class="border-0 bg-transparent py-1.5 text-orange-700 placeholder:text-gray-400 focus:ring-0 outline-none sm:text-sm sm:leading-6"
/>
</div>
<label for="post-body" class="sr-only">post body</label>
<textarea
rows="3"
@ -94,8 +104,7 @@ defmodule PostlandWeb.CoreComponents do
id="post-body"
class="block w-full resize-none border-0 bg-transparent py-1.5 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
placeholder="post body"
value={@post_content}
></textarea>
><%= @post_content %></textarea>
</.form>
<div class="flex">
<div :for={attachment <- @attachments} class="m-2 w-1/2">
@ -117,6 +126,12 @@ defmodule PostlandWeb.CoreComponents do
<.icon name="hero-photo" />
</label>
</div>
<div
class="h-9 flex items-center text-gray-500"
phx-click={if @cw, do: "remove_cw", else: "add_cw"}
>
<.icon name="hero-exclamation-triangle" class="relative top-0.5" />
</div>
</div>
<div class="flex-shrink-0">
<button
@ -176,11 +191,14 @@ defmodule PostlandWeb.CoreComponents do
attachments =
Postland.Timeline.attachments(assigns.post)
cw = Postland.Timeline.cw(assigns.post)
assigns =
assigns
|> Map.put(:author, author)
|> Map.put(:author_host, host)
|> Map.put(:attachments, attachments)
|> Map.put(:cw, cw)
~H"""
<div class="flex items-start space-x-4 w-full">
@ -200,26 +218,30 @@ defmodule PostlandWeb.CoreComponents do
@<%= @author["preferredUsername"] %>@<%= @author_host %>
</span>
</div>
<div class="px-4 py-5 sm:p-6 prose">
<%= {:safe, Postland.Timeline.html_content(@post)} %>
</div>
<div :if={@attachments != []} class="px-4 py-5 sm:p-6">
<div :for={attachment <- @attachments} class="flex">
<div
class="w-1/2 aspect-square bg-black bg-center bg-contain bg-no-repeat rounded-lg shadow"
style={"background-image: url('#{attachment["url"]}')"}
phx-click={show_modal("modal-#{Base.url_encode64(attachment["url"], padding: false)}")}
phx-value-attachment={attachment["url"]}
alt={attachment["name"]}
title={attachment["name"]}
/>
<.modal id={"modal-#{Base.url_encode64(attachment["url"], padding: false)}"}>
<div class="w-full flex justify-center" title={attachment["name"]}>
<img src={attachment["url"]} alt={attachment["name"]} />
</div>
</.modal>
<.cw_wrapper cw={@cw}>
<div class="px-4 py-5 sm:p-6 prose">
<%= {:safe, Postland.Timeline.html_content(@post)} %>
</div>
</div>
<div :if={@attachments != []} class="px-4 py-5 sm:p-6">
<div :for={attachment <- @attachments} class="flex">
<div
class="w-1/2 aspect-square bg-black bg-center bg-contain bg-no-repeat rounded-lg shadow"
style={"background-image: url('#{attachment["url"]}')"}
phx-click={
show_modal("modal-#{Base.url_encode64(attachment["url"], padding: false)}")
}
phx-value-attachment={attachment["url"]}
alt={attachment["name"]}
title={attachment["name"]}
/>
<.modal id={"modal-#{Base.url_encode64(attachment["url"], padding: false)}"}>
<div class="w-full flex justify-center" title={attachment["name"]}>
<img src={attachment["url"]} alt={attachment["name"]} />
</div>
</.modal>
</div>
</div>
</.cw_wrapper>
<div class="px-4 py-4 sm:px-6">
<a
:if={@author["id"] == Postland.my_actor_id()}
@ -239,6 +261,21 @@ defmodule PostlandWeb.CoreComponents do
"""
end
def cw_wrapper(assigns) do
~H"""
<%= if @cw do %>
<details>
<summary class="ml-6 text-orange-700">
<.icon name="hero-exclamation-triangle" /> <%= @cw %>
</summary>
<%= render_slot(@inner_block) %>
</details>
<% else %>
<%= render_slot(@inner_block) %>
<% end %>
"""
end
@doc """
Renders a modal.

View file

@ -7,7 +7,7 @@ defmodule PostlandWeb.TimelineLive do
def render(assigns) do
~H"""
<div id="timeline">
<.post_form post_content={@post} attachments={@attachments} upload={@uploads.files} />
<.post_form post_content={@post} attachments={@attachments} upload={@uploads.files} cw={@cw} />
<div id="timeline-posts" phx-update="stream">
<div :for={{id, post} <- @streams.posts} id={id} class="mt-10">
<.post_card post={post} post_dom_id={id} />
@ -22,6 +22,7 @@ defmodule PostlandWeb.TimelineLive do
socket
|> assign(:post, "")
|> assign(:attachments, [])
|> assign(:cw, nil)
|> stream(:posts, Postland.Timeline.timeline())
|> allow_upload(:files,
accept: ["image/*"],
@ -33,7 +34,12 @@ defmodule PostlandWeb.TimelineLive do
end
def handle_event("create_post", %{"post" => post}, socket) do
{:ok, results} = Postland.Activities.record_markdown_post(post, socket.assigns.attachments)
{:ok, results} =
Postland.Activities.record_markdown_post(
post,
socket.assigns.cw,
socket.assigns.attachments
)
new_posts =
results
@ -45,6 +51,7 @@ defmodule PostlandWeb.TimelineLive do
socket
|> assign(:post, "")
|> assign(:attachments, [])
|> assign(:cw, nil)
|> then(fn socket ->
Enum.reduce(new_posts, socket, fn {_id, post}, socket ->
stream_insert(socket, :posts, post, at: 0)
@ -66,8 +73,9 @@ defmodule PostlandWeb.TimelineLive do
end
end
def handle_event("change_post", %{"post" => post}, socket) do
{:noreply, socket |> assign(:post, post)}
def handle_event("change_post", %{"post" => post} = params, socket) do
cw = Map.get(params, "cw")
{:noreply, socket |> assign(post: post, cw: cw)}
end
def handle_event("remove_attachment", %{"url" => url}, socket) do
@ -92,6 +100,14 @@ defmodule PostlandWeb.TimelineLive do
{:noreply, assign(socket, attachments: attachments)}
end
def handle_event("add_cw", _, socket) do
{:noreply, assign(socket, cw: "")}
end
def handle_event("remove_cw", _, socket) do
{:noreply, assign(socket, cw: nil)}
end
def handle_progress(:files, entry, socket) do
if entry.done? do
uploaded_file =