fix: full fediverse handle display + follower count includes remote
Some checks failed
lint / lint (push) Has been cancelled
test / unit (push) Has been cancelled
test / integration (push) Has been cancelled
lint / lint (pull_request) Has been cancelled
test / unit (pull_request) Has been cancelled
test / integration (pull_request) Has been cancelled

This commit is contained in:
2026-05-15 04:35:04 +02:00
parent 4342a06319
commit 2e3b81de17
6 changed files with 35 additions and 6 deletions

View File

@@ -422,9 +422,11 @@ impl ActivityPubService {
); );
} }
let domain = remote_actor.ap_id.host_str().unwrap_or("");
let full_handle = format!("{}@{}", remote_actor.username, domain);
let remote = RemoteActor { let remote = RemoteActor {
url: remote_actor.ap_id.to_string(), url: remote_actor.ap_id.to_string(),
handle: remote_actor.username.clone(), handle: full_handle,
inbox_url: remote_actor.inbox_url.to_string(), inbox_url: remote_actor.inbox_url.to_string(),
shared_inbox_url: None, shared_inbox_url: None,
display_name: Some(remote_actor.username.clone()), display_name: Some(remote_actor.username.clone()),

View File

@@ -3,6 +3,8 @@ import {
getFollowersList, getFollowersList,
getFollowingList, getFollowingList,
getMe, getMe,
getRemoteFollowers,
getRemoteFollowing,
getTopFriends, getTopFriends,
getUserProfile, getUserProfile,
getUserThoughts, getUserThoughts,
@@ -95,16 +97,27 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
thoughtsResult.status === "fulfilled" ? thoughtsResult.value.items : []; thoughtsResult.status === "fulfilled" ? thoughtsResult.value.items : [];
const thoughtThreads = buildThoughtThreads(thoughts); const thoughtThreads = buildThoughtThreads(thoughts);
const followersCount = const localFollowersCount =
followersResult.status === "fulfilled" followersResult.status === "fulfilled"
? followersResult.value.total ? followersResult.value.total
: 0; : 0;
const followingCount = const localFollowingCount =
followingResult.status === "fulfilled" followingResult.status === "fulfilled"
? followingResult.value.total ? followingResult.value.total
: 0; : 0;
const isOwnProfile = me?.username === user.username; const isOwnProfile = me?.username === user.username;
const [remoteFollowersCount, remoteFollowingCount] =
isOwnProfile && token
? await Promise.all([
getRemoteFollowers(token).then((r) => r.length).catch(() => 0),
getRemoteFollowing(token).then((r) => r.length).catch(() => 0),
])
: [0, 0];
const followersCount = localFollowersCount + remoteFollowersCount;
const followingCount = localFollowingCount + remoteFollowingCount;
const isFollowing = user.isFollowedByViewer; const isFollowing = user.isFollowedByViewer;
const apiDomain = process.env.NEXT_PUBLIC_API_URL const apiDomain = process.env.NEXT_PUBLIC_API_URL

View File

@@ -12,6 +12,7 @@ import { UserAvatar } from "@/components/user-avatar";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { toast } from "sonner"; import { toast } from "sonner";
import Link from "next/link"; import Link from "next/link";
import { fullFediverseHandle } from "@/lib/utils";
interface Props { interface Props {
compact?: boolean; compact?: boolean;
@@ -71,7 +72,7 @@ export function PendingRequests({ compact = false }: Props) {
{actor.displayName || actor.handle} {actor.displayName || actor.handle}
</p> </p>
<p className="text-xs text-muted-foreground truncate font-mono"> <p className="text-xs text-muted-foreground truncate font-mono">
@{actor.handle} @{fullFediverseHandle(actor.handle, actor.url)}
</p> </p>
</div> </div>
</Link> </Link>

View File

@@ -7,6 +7,7 @@ import { UserAvatar } from "@/components/user-avatar";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { toast } from "sonner"; import { toast } from "sonner";
import Link from "next/link"; import Link from "next/link";
import { fullFediverseHandle } from "@/lib/utils";
export function RemoteFollowers() { export function RemoteFollowers() {
const { token } = useAuth(); const { token } = useAuth();
@@ -51,7 +52,7 @@ export function RemoteFollowers() {
{actor.displayName || actor.handle} {actor.displayName || actor.handle}
</p> </p>
<p className="text-xs text-muted-foreground truncate font-mono"> <p className="text-xs text-muted-foreground truncate font-mono">
@{actor.handle} @{fullFediverseHandle(actor.handle, actor.url)}
</p> </p>
</div> </div>
</Link> </Link>

View File

@@ -7,6 +7,7 @@ import { UserAvatar } from "@/components/user-avatar";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { toast } from "sonner"; import { toast } from "sonner";
import Link from "next/link"; import Link from "next/link";
import { fullFediverseHandle } from "@/lib/utils";
export function RemoteFollowing() { export function RemoteFollowing() {
const { token } = useAuth(); const { token } = useAuth();
@@ -51,7 +52,7 @@ export function RemoteFollowing() {
{actor.displayName || actor.handle} {actor.displayName || actor.handle}
</p> </p>
<p className="text-xs text-muted-foreground truncate font-mono"> <p className="text-xs text-muted-foreground truncate font-mono">
@{actor.handle} @{fullFediverseHandle(actor.handle, actor.url)}
</p> </p>
</div> </div>
</Link> </Link>

View File

@@ -6,6 +6,17 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))
} }
/** Construct a full fediverse handle like `user@instance.social`.
* Falls back gracefully for existing DB rows that only stored the username. */
export function fullFediverseHandle(handle: string, actorUrl: string): string {
if (handle.includes("@")) return handle;
try {
return `${handle}@${new URL(actorUrl).hostname}`;
} catch {
return handle;
}
}
export function buildThoughtThreads(thoughts: Thought[]): ThoughtThreadType[] { export function buildThoughtThreads(thoughts: Thought[]): ThoughtThreadType[] {
const thoughtMap = new Map<string, Thought>(); const thoughtMap = new Map<string, Thought>();
thoughts.forEach((t) => thoughtMap.set(t.id, t)); thoughts.forEach((t) => thoughtMap.set(t.id, t));