feat: Add following and followers list
This commit is contained in:
parent
b49925b830
commit
f27dd6a9a7
10 changed files with 219 additions and 88 deletions
99
README.md
99
README.md
|
|
@ -1,56 +1,67 @@
|
||||||
## Backend
|
## Posting
|
||||||
|
|
||||||
- [x] Check that signature header (digest) matches digest of body contents
|
- [x] Making posts
|
||||||
- [ ] Posting
|
- [x] Broadcasting them to followers
|
||||||
- [x] Making posts locally
|
- [x] Post formatting
|
||||||
- [x] Figuring out follower list
|
|
||||||
- [x] Sending to followers
|
|
||||||
- [x] Post formatting
|
|
||||||
- [ ] Sending posts w/ images / videos
|
|
||||||
- [ ] Receiving posts w/ images / videos
|
|
||||||
- [x] Timeline
|
|
||||||
- [x] My posts
|
|
||||||
- [x] Posts from accounts you follow
|
|
||||||
- [x] Show the actor avatar and display name
|
|
||||||
- [x] Deleting posts
|
- [x] Deleting posts
|
||||||
|
- [ ] Sending posts w/ images / videos
|
||||||
|
- [ ] 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?)
|
||||||
|
|
||||||
|
## Profile
|
||||||
|
|
||||||
- [x] Profile
|
- [x] Profile
|
||||||
- [x] Name field (for display name)
|
- [x] Name field (for display name)
|
||||||
- [x] Bust actor cache when you update your profile
|
- [x] Bust actor cache when you update your profile
|
||||||
- [x] Following
|
|
||||||
- [ ] Scrape public posts from the outbox when you follow
|
## Following
|
||||||
|
|
||||||
|
- [x] Sending follow request
|
||||||
|
- [x] View following list
|
||||||
|
- [ ] Withdrawing follow request
|
||||||
- [ ] Unfollowing
|
- [ ] Unfollowing
|
||||||
- [x] Being followed
|
- [ ] Proactively check the outbox of newly-accepted follows
|
||||||
- [x] Accepting follows
|
|
||||||
|
## Being Followed
|
||||||
|
|
||||||
|
- [x] Receiving follower requests
|
||||||
|
- [ ] Viewing follower requests
|
||||||
|
- [ ] Accepting follower requests
|
||||||
|
- [ ] Rejecting follower requests
|
||||||
|
- [ ] Ignoring follower requests
|
||||||
|
- [ ] Unaccepting follower request ("soft block")
|
||||||
- [ ] Blocking
|
- [ ] Blocking
|
||||||
- [ ] Approving / declining follows
|
|
||||||
- [ ] Manage authorized instance list
|
## Timeline
|
||||||
- [ ] Liking
|
|
||||||
- [ ] Unliking
|
- [x] Your posts show up in timeline
|
||||||
- [ ] CW posts
|
- [x] Posts from accounts you follow show up in timeline
|
||||||
- [ ] Polls
|
- [x] Show the actor avatar and display name
|
||||||
- [ ] DMs
|
- [ ] Receiving posts w/ images / videos
|
||||||
- [ ] Support authenticated fetch of outbox (by allowed domains / servers)
|
- [ ] Liking posts
|
||||||
- [ ] Followers-only posts (or maybe this is handled because we only send posts to followers? but we also include public in the TO field?)
|
- [ ] Unliking posts
|
||||||
|
- [ ] Displaying CW posts behind CW
|
||||||
|
- [ ] Displaying polls
|
||||||
|
- [ ] Voting in polls
|
||||||
|
|
||||||
|
## DMs
|
||||||
|
|
||||||
|
- [ ] Receiving DMs
|
||||||
|
- [ ] Replying to DMs
|
||||||
|
- [ ] Sending new DMs
|
||||||
|
|
||||||
|
## Allowlist
|
||||||
|
|
||||||
|
- [ ] Manage approved instance list
|
||||||
|
- [ ] Only accept activities from approved instances
|
||||||
|
- [ ] Allow approved instances to see posts in outbox
|
||||||
|
|
||||||
|
## Protocol Support
|
||||||
|
|
||||||
|
- [x] Check that signature header (digest) matches digest of body contents
|
||||||
- [ ] Check the domain of the public key against the domain of the object being CRUDed
|
- [ ] Check the domain of the public key against the domain of the object being CRUDed
|
||||||
|
|
||||||
## UX
|
|
||||||
|
|
||||||
- [x] Posting
|
|
||||||
- [x] Timeline
|
|
||||||
- [x] My posts
|
|
||||||
- [x] Posts from accounts you follow
|
|
||||||
- [ ] Deleting posts
|
|
||||||
- [~] Following
|
|
||||||
- [ ] Unfollowing
|
|
||||||
- [ ] Being followed
|
|
||||||
- [ ] Accepting follows
|
|
||||||
- [ ] Approving / declining follows and authorized instance list
|
|
||||||
- [ ] Liking
|
|
||||||
- [ ] Unliking
|
|
||||||
- [ ] CW posts
|
|
||||||
- [ ] Polls
|
|
||||||
- [ ] DMs
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
- [ ] Measure test coverage
|
- [ ] Measure test coverage
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ defmodule Postland.Activities do
|
||||||
my_actor_id = Postland.my_actor_id()
|
my_actor_id = Postland.my_actor_id()
|
||||||
private_key = Postland.Accounts.solo_user().private_key
|
private_key = Postland.Accounts.solo_user().private_key
|
||||||
|
|
||||||
Postland.Follows.all_followers()
|
Postland.Follows.confirmed_followers()
|
||||||
|> Enum.map(fn %{follower: follower} ->
|
|> Enum.map(fn %{follower: follower} ->
|
||||||
inbox = Postland.Actors.inbox(follower)
|
inbox = Postland.Actors.inbox(follower)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,20 @@ defmodule Postland.Follows do
|
||||||
def all_following() do
|
def all_following() do
|
||||||
my_actor_id = Postland.my_actor_id()
|
my_actor_id = Postland.my_actor_id()
|
||||||
|
|
||||||
from(f in Follow, where: f.follower == ^my_actor_id, where: not is_nil(f.confirmed_at))
|
from(f in Follow, where: f.follower == ^my_actor_id)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def all_followers() do
|
def all_followers() do
|
||||||
my_actor_id = Postland.my_actor_id()
|
my_actor_id = Postland.my_actor_id()
|
||||||
|
|
||||||
|
from(f in Follow, where: f.followee == ^my_actor_id)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirmed_followers() do
|
||||||
|
my_actor_id = Postland.my_actor_id()
|
||||||
|
|
||||||
from(f in Follow, where: f.followee == ^my_actor_id, where: not is_nil(f.confirmed_at))
|
from(f in Follow, where: f.followee == ^my_actor_id, where: not is_nil(f.confirmed_at))
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,42 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
alias Phoenix.LiveView.JS
|
alias Phoenix.LiveView.JS
|
||||||
use Gettext, backend: PostlandWeb.Gettext
|
use Gettext, backend: PostlandWeb.Gettext
|
||||||
|
|
||||||
|
def profile_card(assigns) do
|
||||||
|
%{host: host} = URI.parse(assigns.account["id"])
|
||||||
|
|
||||||
|
assigns = Map.put(assigns, :host, host)
|
||||||
|
|
||||||
|
~H"""
|
||||||
|
<div class="flex items-start space-x-4">
|
||||||
|
<div class="flex-shrink-0 relative">
|
||||||
|
<img
|
||||||
|
class="h-16 w-16 rounded-full drop-shadow-lg"
|
||||||
|
src={@account["icon"] || url(~p"/images/avatar.png")}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex bg-white rounded-lg shadow container p-4">
|
||||||
|
<div class="py-2">
|
||||||
|
<p>
|
||||||
|
<span class="font-bold">
|
||||||
|
<%= @account["name"] %>
|
||||||
|
</span>
|
||||||
|
<span class="ml-2 text-gray-500">
|
||||||
|
@<%= @account["preferredUsername"] %>@<%= @host %>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-500 text-sm">
|
||||||
|
<%= {:safe, Earmark.as_html!(@account["summary"] || "")} %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow text-right">
|
||||||
|
<.button disabled>Pending</.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
def post_form(assigns) do
|
def post_form(assigns) do
|
||||||
user = Postland.Accounts.solo_user()
|
user = Postland.Accounts.solo_user()
|
||||||
|
|
||||||
|
|
@ -121,6 +157,7 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
phx-click="delete_post"
|
phx-click="delete_post"
|
||||||
phx-value-post-dom-id={@post_dom_id}
|
phx-value-post-dom-id={@post_dom_id}
|
||||||
phx-value-post-id={@post.id}
|
phx-value-post-id={@post.id}
|
||||||
|
class="text-gray-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" />
|
<.icon name="hero-trash" />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,24 @@ defmodule PostlandWeb.Layouts do
|
||||||
use PostlandWeb, :html
|
use PostlandWeb, :html
|
||||||
|
|
||||||
embed_templates "layouts/*"
|
embed_templates "layouts/*"
|
||||||
|
|
||||||
|
def nav_link(assigns) do
|
||||||
|
~H"""
|
||||||
|
<li>
|
||||||
|
<!-- Current: "bg-gray-50 text-violet-600", Default: "text-gray-700 hover:text-violet-600 hover:bg-gray-50" -->
|
||||||
|
<a
|
||||||
|
href={@href}
|
||||||
|
class={[
|
||||||
|
"group flex gap-x-3 rounded-lg p-2 pl-3 text-sm/6 font-semibold",
|
||||||
|
if(assigns[:active],
|
||||||
|
do: "bg-gray-50 text-violet-600",
|
||||||
|
else: "text-gray-700 hover:text-violet-600 hover:bg-gray-50"
|
||||||
|
)
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<%= render_slot(@inner_block) %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
"""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,46 @@
|
||||||
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
<main class="flex items-start px-4 py-20 sm:px-6 lg:px-8">
|
||||||
|
<div :if={@current_user} class="flex-1 bg-white rounded-lg py-2 px-3 shadow">
|
||||||
|
<nav class="flex flex-col" aria-label="Sidebar">
|
||||||
|
<ul role="list" class="-mx-2 space-y-1">
|
||||||
|
<li>
|
||||||
|
<div class="group flex gap-x-3 rounded-lg p-2 pl-3 text-sm/6 uppercase font-bold">
|
||||||
|
Postland
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<.nav_link href={~p"/"} active={@socket.view == PostlandWeb.TimelineLive}>
|
||||||
|
Timeline
|
||||||
|
</.nav_link>
|
||||||
|
<.nav_link href={~p"/about"} active={@socket.view == PostlandWeb.ProfileLive}>
|
||||||
|
Profile
|
||||||
|
</.nav_link>
|
||||||
|
<.nav_link href={~p"/following"} active={@socket.view == PostlandWeb.FollowingLive}>
|
||||||
|
Following
|
||||||
|
</.nav_link>
|
||||||
|
<.nav_link href={~p"/followers"} active={@socket.view == PostlandWeb.FollowersLive}>
|
||||||
|
Followers
|
||||||
|
</.nav_link>
|
||||||
|
<.nav_link
|
||||||
|
href={~p"/users/settings"}
|
||||||
|
active={@socket.view == PostlandWeb.UserSettingsLive}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</.nav_link>
|
||||||
|
<li>
|
||||||
|
<.link
|
||||||
|
href={~p"/users/log_out"}
|
||||||
|
method="delete"
|
||||||
|
class="group flex gap-x-3 rounded-lg p-2 pl-3 text-sm/6 font-semibold text-gray-700 hover:bg-gray-50 hover:text-violet-600"
|
||||||
|
>
|
||||||
|
Log Out
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="flex-[5]">
|
||||||
<div class="mx-auto max-w-2xl">
|
<div class="mx-auto max-w-2xl">
|
||||||
<.flash_group flash={@flash} />
|
<.flash_group flash={@flash} />
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -12,44 +12,6 @@
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-violet-50 antialiased">
|
<body class="bg-violet-50 antialiased">
|
||||||
<header class="flex w-full bg-violet-700 text-violet-100 py-4">
|
|
||||||
<div class="flex-1 px-4 uppercase font-bold ">
|
|
||||||
<a href={~p"/"}>Postland</a>
|
|
||||||
</div>
|
|
||||||
<ul class="relative z-10 flex flex-1 items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
|
|
||||||
<%= if @current_user do %>
|
|
||||||
<li class="text-[0.8125rem] leading-6 font-semibold hover:text-violet-300">
|
|
||||||
<a href={~p"/about"}>Profile</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<.link
|
|
||||||
href={~p"/users/settings"}
|
|
||||||
class="text-[0.8125rem] leading-6 font-semibold hover:text-violet-300"
|
|
||||||
>
|
|
||||||
Settings
|
|
||||||
</.link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<.link
|
|
||||||
href={~p"/users/log_out"}
|
|
||||||
method="delete"
|
|
||||||
class="text-[0.8125rem] leading-6 font-semibold hover:text-violet-300"
|
|
||||||
>
|
|
||||||
Log out
|
|
||||||
</.link>
|
|
||||||
</li>
|
|
||||||
<% else %>
|
|
||||||
<li>
|
|
||||||
<.link
|
|
||||||
href={~p"/users/log_in"}
|
|
||||||
class="text-[0.8125rem] leading-6 font-semibold hover:text-violet-300"
|
|
||||||
>
|
|
||||||
Log in
|
|
||||||
</.link>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
</header>
|
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
27
lib/postland_web/live/followers_live.ex
Normal file
27
lib/postland_web/live/followers_live.ex
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule PostlandWeb.FollowersLive do
|
||||||
|
use PostlandWeb, :live_view
|
||||||
|
|
||||||
|
alias Postland.Actors
|
||||||
|
alias Postland.Follows
|
||||||
|
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div class="py-4">
|
||||||
|
<h3 class="text-base font-semibold">Followers</h3>
|
||||||
|
<p :if={@accounts == []} class="text-gray-400">
|
||||||
|
No one follows you.
|
||||||
|
</p>
|
||||||
|
<div :for={acct <- @accounts} class="mt-2">
|
||||||
|
<.profile_card account={acct} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
followers =
|
||||||
|
Follows.all_followers() |> Enum.map(fn follow -> Actors.actor(follow.follower) end)
|
||||||
|
|
||||||
|
{:ok, assign(socket, :accounts, followers)}
|
||||||
|
end
|
||||||
|
end
|
||||||
25
lib/postland_web/live/following_live copy.ex
Normal file
25
lib/postland_web/live/following_live copy.ex
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule PostlandWeb.FollowingLive do
|
||||||
|
use PostlandWeb, :live_view
|
||||||
|
|
||||||
|
alias Postland.Actors
|
||||||
|
alias Postland.Follows
|
||||||
|
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div class="py-4">
|
||||||
|
<h3 class="text-base font-semibold">Following</h3>
|
||||||
|
<p :if={@accounts == []} class="text-gray-400">
|
||||||
|
You aren't following anyone.
|
||||||
|
</p>
|
||||||
|
<div :for={acct <- @accounts} class="mt-2">
|
||||||
|
<.profile_card account={acct} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
follows = Follows.all_following() |> Enum.map(fn follow -> Actors.actor(follow.followee) end)
|
||||||
|
{:ok, assign(socket, :accounts, follows)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -82,6 +82,8 @@ defmodule PostlandWeb.Router do
|
||||||
live_session :require_authenticated_user,
|
live_session :require_authenticated_user,
|
||||||
on_mount: [{PostlandWeb.UserAuth, :ensure_authenticated}] do
|
on_mount: [{PostlandWeb.UserAuth, :ensure_authenticated}] do
|
||||||
live "/", TimelineLive, :show
|
live "/", TimelineLive, :show
|
||||||
|
live "/following", FollowingLive, :show
|
||||||
|
live "/followers", FollowersLive, :show
|
||||||
live "/users/settings", UserSettingsLive, :edit
|
live "/users/settings", UserSettingsLive, :edit
|
||||||
live "/@:acct", OtherProfileLive, :show
|
live "/@:acct", OtherProfileLive, :show
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue