import { useModerationDecision } from "$/components/moderation/hooks/useModerationDecision"; import { ModeratedAvatar } from "$/components/moderation/ModeratedAvatar"; import { ModerationBadgeRow } from "$/components/moderation/ModerationBadgeRow"; import { usePostNavigation } from "$/components/posts/hooks/usePostNavigation"; import { Icon } from "$/components/shared/Icon"; import { QuotedPostPreview } from "$/components/shared/QuotedPostPreview"; import type { DiagnosticBacklinkGroup, DiagnosticBacklinkItem } from "$/lib/api/diagnostics"; import { DiagnosticsController } from "$/lib/api/diagnostics"; import { collectModerationLabels } from "$/lib/moderation"; import { buildPostEngagementTabRoute, parsePostEngagementTab, type PostEngagementTab, } from "$/lib/post-engagement-routes"; import { buildProfileRoute } from "$/lib/profile"; import { asRecord } from "$/lib/type-guards"; import type { ProfileViewBasic } from "$/lib/types"; import { formatHandle, initials, normalizeError } from "$/lib/utils/text"; import { useLocation, useNavigate } from "@solidjs/router"; import * as logger from "@tauri-apps/plugin-log"; import { createEffect, createMemo, For, Match, Show, Switch } from "solid-js"; import { createStore } from "solid-js/store"; type EngagementState = { error: string | null; groups: Record; loading: boolean; uri: string | null; }; const EMPTY_GROUP: DiagnosticBacklinkGroup = { cursor: null, records: [], total: 0 }; const TABS: Array<{ key: PostEngagementTab; label: string }> = [{ key: "likes", label: "Likes" }, { key: "reposts", label: "Reposts", }, { key: "quotes", label: "Quotes" }]; function createInitialState(): EngagementState { return { error: null, groups: { likes: EMPTY_GROUP, reposts: EMPTY_GROUP, quotes: EMPTY_GROUP }, loading: false, uri: null, }; } export function PostEngagementPanel(props: { uri: string | null }) { const location = useLocation(); const navigate = useNavigate(); const postNavigation = usePostNavigation(); const [state, setState] = createStore(createInitialState()); let requestId = 0; const activeUri = createMemo(() => props.uri?.trim() || null); const activeTab = createMemo(() => parsePostEngagementTab(location.search)); const activeGroup = createMemo(() => state.groups[activeTab()]); const activeTabLabel = createMemo(() => TABS.find((tab) => tab.key === activeTab())?.label ?? "Likes"); createEffect(() => { const uri = activeUri(); if (!uri) { setState(createInitialState()); return; } const nextRequestId = ++requestId; setState({ error: null, groups: { likes: EMPTY_GROUP, reposts: EMPTY_GROUP, quotes: EMPTY_GROUP }, loading: true, uri, }); void loadEngagement(nextRequestId, uri); }); async function loadEngagement(nextRequestId: number, uri: string) { try { const response = await DiagnosticsController.getRecordBacklinks(uri); if (nextRequestId !== requestId || uri !== activeUri()) { return; } setState({ error: null, groups: { likes: response.likes ?? EMPTY_GROUP, quotes: response.quotes ?? EMPTY_GROUP, reposts: response.reposts ?? EMPTY_GROUP, }, loading: false, uri, }); } catch (error) { const message = normalizeError(error); if (nextRequestId !== requestId || uri !== activeUri()) { return; } setState({ error: message, loading: false, uri }); logger.error("failed to load post engagement", { keyValues: { error: message, uri } }); } } function selectTab(tab: PostEngagementTab) { if (tab === activeTab()) { return; } void navigate(buildPostEngagementTabRoute(location.pathname, location.search, tab)); } function openProfile(item: DiagnosticBacklinkItem) { const actor = item.profile?.handle?.trim() || item.did?.trim(); if (!actor) { return; } void navigate(buildProfileRoute(actor)); } function openQuote(item: DiagnosticBacklinkItem) { if (!item.uri) { return; } void postNavigation.openPostScreen(item.uri); } return (

Post Engagement

{activeTabLabel()}

); } function activeCount(group: DiagnosticBacklinkGroup) { return group.total ?? group.records.length; } function EngagementList( props: { items: DiagnosticBacklinkItem[]; kind: PostEngagementTab; onOpenProfile: (item: DiagnosticBacklinkItem) => void; onOpenQuote: (item: DiagnosticBacklinkItem) => void; }, ) { return (
{(item) => ( )}
); } function EngagementRow( props: { item: DiagnosticBacklinkItem; kind: PostEngagementTab; onOpenProfile: (item: DiagnosticBacklinkItem) => void; onOpenQuote: (item: DiagnosticBacklinkItem) => void; }, ) { const actorLabel = createMemo(() => props.item.profile?.displayName ?? props.item.profile?.handle ?? props.item.did ?? "Unknown account" ); const handleLabel = createMemo(() => formatHandle(props.item.profile?.handle, props.item.did)); const quoteInteractive = createMemo(() => props.kind === "quotes" && !!props.item.uri); const profileInteractive = createMemo(() => props.kind !== "quotes" && !!(props.item.profile?.handle || props.item.did) ); const interactive = createMemo(() => quoteInteractive() || profileInteractive()); const quoteText = createMemo(() => getQuoteText(props.item)); const quoteAuthor = createMemo(() => getQuoteAuthor(props.item)); const profileLabels = () => collectModerationLabels(props.item.profile); const avatarDecision = useModerationDecision(profileLabels, "avatar"); const profileDecision = useModerationDecision(profileLabels, "profileList"); return ( ); } function getQuoteRecord(item: DiagnosticBacklinkItem) { return asRecord(item.value); } function getQuoteText(item: DiagnosticBacklinkItem) { const text = getQuoteRecord(item)?.text; return typeof text === "string" && text.trim().length > 0 ? text : null; } function getQuoteAuthor(item: DiagnosticBacklinkItem): ProfileViewBasic | null { const did = item.profile?.did?.trim() || item.did?.trim(); const handle = item.profile?.handle?.trim() || did; if (!did || !handle) { return null; } return { did, handle, avatar: item.profile?.avatar ?? null, displayName: item.profile?.displayName ?? null, labels: item.profile?.labels ?? null, }; } function PanelMessage(props: { body: string; title: string }) { return (

{props.title}

{props.body}

); } function EngagementSkeleton() { return (
{() => (
)}
); }