feat: Add content warnings
This commit is contained in:
parent
2685a8dfb8
commit
0bca082286
5 changed files with 109 additions and 42 deletions
|
|
@ -5,7 +5,7 @@
|
||||||
- [x] Post formatting
|
- [x] Post formatting
|
||||||
- [x] Deleting posts
|
- [x] Deleting posts
|
||||||
- [x] Sending posts w/ images
|
- [x] Sending posts w/ images
|
||||||
- [ ] Making posts with CWs
|
- [x] Making posts with CWs
|
||||||
- [ ] Making polls
|
- [ ] 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?)
|
- [ ] 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
|
- [] Sending posts with videos
|
||||||
|
|
@ -39,11 +39,11 @@
|
||||||
- [x] Your posts show up in timeline
|
- [x] Your posts show up in timeline
|
||||||
- [x] Posts from accounts you follow show up in timeline
|
- [x] Posts from accounts you follow show up in timeline
|
||||||
- [x] Show the actor avatar and display name
|
- [x] Show the actor avatar and display name
|
||||||
- [ ] Receiving posts w/ images
|
- [x] Receiving posts w/ images
|
||||||
- [ ] Receiving posts w/ videos
|
- [ ] Receiving posts w/ videos
|
||||||
- [ ] Liking posts
|
- [ ] Liking posts
|
||||||
- [ ] Unliking posts
|
- [ ] Unliking posts
|
||||||
- [ ] Displaying CW posts behind CW
|
- [x] Displaying CW posts behind CW
|
||||||
- [ ] Displaying polls
|
- [ ] Displaying polls
|
||||||
- [ ] Voting in polls
|
- [ ] Voting in polls
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ defmodule Postland.Activities do
|
||||||
alias Postland.Repo
|
alias Postland.Repo
|
||||||
alias Postland.Follows
|
alias Postland.Follows
|
||||||
|
|
||||||
def record_markdown_post(markdown, attachments) do
|
def record_markdown_post(markdown, cw, attachments) do
|
||||||
id = Ecto.UUID.autogenerate()
|
id = Ecto.UUID.autogenerate()
|
||||||
html = Earmark.as_html!(markdown)
|
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
|
# 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
|
# 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 =
|
activity =
|
||||||
%{
|
%{
|
||||||
"@context" => [
|
"@context" => [
|
||||||
|
|
@ -38,19 +54,7 @@ defmodule Postland.Activities do
|
||||||
"id" => url(~p"/activities/#{id}"),
|
"id" => url(~p"/activities/#{id}"),
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"actor" => Postland.my_actor_id(),
|
"actor" => Postland.my_actor_id(),
|
||||||
"object" => %{
|
"object" => 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activity
|
activity
|
||||||
|
|
@ -59,6 +63,10 @@ defmodule Postland.Activities do
|
||||||
|> broadcast_to_followers(activity)
|
|> broadcast_to_followers(activity)
|
||||||
end
|
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
|
def delete_post(post) do
|
||||||
activity =
|
activity =
|
||||||
%{
|
%{
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,12 @@ defmodule Postland.Timeline do
|
||||||
# TODO: Filter down to just image/* and video/* MIME types
|
# TODO: Filter down to just image/* and video/* MIME types
|
||||||
end
|
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
|
def attribution(activity) do
|
||||||
Postland.Actors.actor(get_from_html_item(activity, "attributedTo"))
|
Postland.Actors.actor(get_from_html_item(activity, "attributedTo"))
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,16 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
<div class="min-w-0 flex-1 relative">
|
<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">
|
<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">
|
<.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>
|
<label for="post-body" class="sr-only">post body</label>
|
||||||
<textarea
|
<textarea
|
||||||
rows="3"
|
rows="3"
|
||||||
|
|
@ -94,8 +104,7 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
id="post-body"
|
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"
|
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"
|
placeholder="post body"
|
||||||
value={@post_content}
|
><%= @post_content %></textarea>
|
||||||
></textarea>
|
|
||||||
</.form>
|
</.form>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div :for={attachment <- @attachments} class="m-2 w-1/2">
|
<div :for={attachment <- @attachments} class="m-2 w-1/2">
|
||||||
|
|
@ -117,6 +126,12 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
<.icon name="hero-photo" />
|
<.icon name="hero-photo" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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>
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<button
|
<button
|
||||||
|
|
@ -176,11 +191,14 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
attachments =
|
attachments =
|
||||||
Postland.Timeline.attachments(assigns.post)
|
Postland.Timeline.attachments(assigns.post)
|
||||||
|
|
||||||
|
cw = Postland.Timeline.cw(assigns.post)
|
||||||
|
|
||||||
assigns =
|
assigns =
|
||||||
assigns
|
assigns
|
||||||
|> Map.put(:author, author)
|
|> Map.put(:author, author)
|
||||||
|> Map.put(:author_host, host)
|
|> Map.put(:author_host, host)
|
||||||
|> Map.put(:attachments, attachments)
|
|> Map.put(:attachments, attachments)
|
||||||
|
|> Map.put(:cw, cw)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="flex items-start space-x-4 w-full">
|
<div class="flex items-start space-x-4 w-full">
|
||||||
|
|
@ -200,6 +218,7 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
@<%= @author["preferredUsername"] %>@<%= @author_host %>
|
@<%= @author["preferredUsername"] %>@<%= @author_host %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<.cw_wrapper cw={@cw}>
|
||||||
<div class="px-4 py-5 sm:p-6 prose">
|
<div class="px-4 py-5 sm:p-6 prose">
|
||||||
<%= {:safe, Postland.Timeline.html_content(@post)} %>
|
<%= {:safe, Postland.Timeline.html_content(@post)} %>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -208,7 +227,9 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
<div
|
<div
|
||||||
class="w-1/2 aspect-square bg-black bg-center bg-contain bg-no-repeat rounded-lg shadow"
|
class="w-1/2 aspect-square bg-black bg-center bg-contain bg-no-repeat rounded-lg shadow"
|
||||||
style={"background-image: url('#{attachment["url"]}')"}
|
style={"background-image: url('#{attachment["url"]}')"}
|
||||||
phx-click={show_modal("modal-#{Base.url_encode64(attachment["url"], padding: false)}")}
|
phx-click={
|
||||||
|
show_modal("modal-#{Base.url_encode64(attachment["url"], padding: false)}")
|
||||||
|
}
|
||||||
phx-value-attachment={attachment["url"]}
|
phx-value-attachment={attachment["url"]}
|
||||||
alt={attachment["name"]}
|
alt={attachment["name"]}
|
||||||
title={attachment["name"]}
|
title={attachment["name"]}
|
||||||
|
|
@ -220,6 +241,7 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
</.modal>
|
</.modal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</.cw_wrapper>
|
||||||
<div class="px-4 py-4 sm:px-6">
|
<div class="px-4 py-4 sm:px-6">
|
||||||
<a
|
<a
|
||||||
:if={@author["id"] == Postland.my_actor_id()}
|
:if={@author["id"] == Postland.my_actor_id()}
|
||||||
|
|
@ -239,6 +261,21 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
"""
|
"""
|
||||||
end
|
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 """
|
@doc """
|
||||||
Renders a modal.
|
Renders a modal.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ defmodule PostlandWeb.TimelineLive do
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div id="timeline">
|
<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 id="timeline-posts" phx-update="stream">
|
||||||
<div :for={{id, post} <- @streams.posts} id={id} class="mt-10">
|
<div :for={{id, post} <- @streams.posts} id={id} class="mt-10">
|
||||||
<.post_card post={post} post_dom_id={id} />
|
<.post_card post={post} post_dom_id={id} />
|
||||||
|
|
@ -22,6 +22,7 @@ defmodule PostlandWeb.TimelineLive do
|
||||||
socket
|
socket
|
||||||
|> assign(:post, "")
|
|> assign(:post, "")
|
||||||
|> assign(:attachments, [])
|
|> assign(:attachments, [])
|
||||||
|
|> assign(:cw, nil)
|
||||||
|> stream(:posts, Postland.Timeline.timeline())
|
|> stream(:posts, Postland.Timeline.timeline())
|
||||||
|> allow_upload(:files,
|
|> allow_upload(:files,
|
||||||
accept: ["image/*"],
|
accept: ["image/*"],
|
||||||
|
|
@ -33,7 +34,12 @@ defmodule PostlandWeb.TimelineLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("create_post", %{"post" => post}, socket) do
|
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 =
|
new_posts =
|
||||||
results
|
results
|
||||||
|
|
@ -45,6 +51,7 @@ defmodule PostlandWeb.TimelineLive do
|
||||||
socket
|
socket
|
||||||
|> assign(:post, "")
|
|> assign(:post, "")
|
||||||
|> assign(:attachments, [])
|
|> assign(:attachments, [])
|
||||||
|
|> assign(:cw, nil)
|
||||||
|> then(fn socket ->
|
|> then(fn socket ->
|
||||||
Enum.reduce(new_posts, socket, fn {_id, post}, socket ->
|
Enum.reduce(new_posts, socket, fn {_id, post}, socket ->
|
||||||
stream_insert(socket, :posts, post, at: 0)
|
stream_insert(socket, :posts, post, at: 0)
|
||||||
|
|
@ -66,8 +73,9 @@ defmodule PostlandWeb.TimelineLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("change_post", %{"post" => post}, socket) do
|
def handle_event("change_post", %{"post" => post} = params, socket) do
|
||||||
{:noreply, socket |> assign(:post, post)}
|
cw = Map.get(params, "cw")
|
||||||
|
{:noreply, socket |> assign(post: post, cw: cw)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("remove_attachment", %{"url" => url}, socket) do
|
def handle_event("remove_attachment", %{"url" => url}, socket) do
|
||||||
|
|
@ -92,6 +100,14 @@ defmodule PostlandWeb.TimelineLive do
|
||||||
{:noreply, assign(socket, attachments: attachments)}
|
{:noreply, assign(socket, attachments: attachments)}
|
||||||
end
|
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
|
def handle_progress(:files, entry, socket) do
|
||||||
if entry.done? do
|
if entry.done? do
|
||||||
uploaded_file =
|
uploaded_file =
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue