diff --git a/packages/app/src/Components/User/FollowButton.tsx b/packages/app/src/Components/User/FollowButton.tsx index bcb3efec..9d87f951 100644 --- a/packages/app/src/Components/User/FollowButton.tsx +++ b/packages/app/src/Components/User/FollowButton.tsx @@ -2,7 +2,7 @@ import { HexKey } from "@snort/system"; import { FormattedMessage } from "react-intl"; import AsyncButton from "@/Components/Button/AsyncButton"; -import useEventPublisher from "@/Hooks/useEventPublisher"; +import useFollowsControls from "@/Hooks/useFollowControls"; import useLogin from "@/Hooks/useLogin"; import { parseId } from "@/Utils"; @@ -14,32 +14,18 @@ export interface FollowButtonProps { } export default function FollowButton(props: FollowButtonProps) { const pubkey = parseId(props.pubkey); - const { publisher, system } = useEventPublisher(); - const { follows, readonly } = useLogin(s => ({ follows: s.follows, readonly: s.readonly })); - const isFollowing = follows.item.includes(pubkey); + const readonly = useLogin(s => s.readonly); + const control = useFollowsControls(); + const isFollowing = control.isFollowing(pubkey); const baseClassname = props.className ? `${props.className} ` : ""; - async function follow(pubkey: HexKey) { - if (publisher) { - const ev = await publisher.contactList([pubkey, ...follows.item].map(a => ["p", a])); - system.BroadcastEvent(ev); - } - } - - async function unfollow(pubkey: HexKey) { - if (publisher) { - const ev = await publisher.contactList(follows.item.filter(a => a !== pubkey).map(a => ["p", a])); - system.BroadcastEvent(ev); - } - } - return ( { e.stopPropagation(); - await (isFollowing ? unfollow(pubkey) : follow(pubkey)); + await (isFollowing ? control.removeFollow([pubkey]) : control.addFollow([pubkey])); }}> {isFollowing ? : } diff --git a/packages/app/src/Components/User/FollowListBase.tsx b/packages/app/src/Components/User/FollowListBase.tsx index 2b8663e6..7fbc49a1 100644 --- a/packages/app/src/Components/User/FollowListBase.tsx +++ b/packages/app/src/Components/User/FollowListBase.tsx @@ -1,12 +1,10 @@ -import { dedupe } from "@snort/shared"; import { HexKey } from "@snort/system"; import { ReactNode, useMemo } from "react"; import { FormattedMessage } from "react-intl"; import ProfilePreview from "@/Components/User/ProfilePreview"; -import useEventPublisher from "@/Hooks/useEventPublisher"; +import useFollowsControls from "@/Hooks/useFollowControls"; import useLogin from "@/Hooks/useLogin"; -import { setFollows } from "@/Utils/Login"; import AsyncButton from "../Button/AsyncButton"; import messages from "../messages"; @@ -30,19 +28,13 @@ export default function FollowListBase({ actions, profileActions, }: FollowListBaseProps) { - const { publisher, system } = useEventPublisher(); - const { id, follows } = useLogin(s => ({ id: s.id, follows: s.follows })); + const control = useFollowsControls(); const login = useLogin(); const profilePreviewOptions = useMemo(() => ({ about: showAbout, profileCards: true }), [showAbout]); async function followAll() { - if (publisher) { - const newFollows = dedupe([...pubkeys, ...follows.item]); - const ev = await publisher.contactList(newFollows.map(a => ["p", a])); - setFollows(id, newFollows, ev.created_at); - await system.BroadcastEvent(ev); - } + await control.addFollow(pubkeys); } return ( diff --git a/packages/app/src/Hooks/useFollowControls.ts b/packages/app/src/Hooks/useFollowControls.ts new file mode 100644 index 00000000..1adbf59f --- /dev/null +++ b/packages/app/src/Hooks/useFollowControls.ts @@ -0,0 +1,42 @@ +import { dedupe } from "@snort/shared"; +import { useMemo } from "react"; + +import useEventPublisher from "./useEventPublisher"; +import useLogin from "./useLogin"; + +/** + * Simple hook for adding / removing follows + */ +export default function useFollowsControls() { + const { publisher, system } = useEventPublisher(); + const { follows, relays } = useLogin(s => ({ follows: s.follows.item, readonly: s.readonly, relays: s.relays.item })); + + return useMemo(() => { + const publishList = async (newList: Array) => { + if (publisher) { + const ev = await publisher.contactList( + newList.map(a => ["p", a]), + relays, + ); + system.BroadcastEvent(ev); + } + }; + + return { + isFollowing: (pk: string) => { + return follows.includes(pk); + }, + addFollow: async (pk: Array) => { + const newList = dedupe([...follows, ...pk]); + await publishList(newList); + }, + removeFollow: async (pk: Array) => { + const newList = follows.filter(a => !pk.includes(a)); + await publishList(newList); + }, + setFollows: async (pk: Array) => { + await publishList(dedupe(pk)); + }, + }; + }, [follows, relays, publisher, system]); +} diff --git a/packages/app/src/Pages/settings/tools/prune-follows.tsx b/packages/app/src/Pages/settings/tools/prune-follows.tsx index fc404aa8..61483b63 100644 --- a/packages/app/src/Pages/settings/tools/prune-follows.tsx +++ b/packages/app/src/Pages/settings/tools/prune-follows.tsx @@ -6,9 +6,9 @@ import { FormattedMessage, FormattedNumber } from "react-intl"; import AsyncButton from "@/Components/Button/AsyncButton"; import ProfileImage from "@/Components/User/ProfileImage"; import useEventPublisher from "@/Hooks/useEventPublisher"; +import useFollowsControls from "@/Hooks/useFollowControls"; import useLogin from "@/Hooks/useLogin"; import { Day } from "@/Utils/Const"; -import { setFollows } from "@/Utils/Login"; import { FollowsRelayHealth } from "./follows-relay-health"; @@ -18,13 +18,14 @@ const enum PruneStage { } export function PruneFollowList() { - const { id, follows } = useLogin(s => ({ id: s.id, follows: s.follows })); - const { publisher, system } = useEventPublisher(); + const { follows } = useLogin(s => ({ id: s.id, follows: s.follows })); + const { system } = useEventPublisher(); const uniqueFollows = dedupe(follows.item); const [status, setStatus] = useState(); const [progress, setProgress] = useState(0); const [lastPost, setLastPosts] = useState>(); const [unfollow, setUnfollow] = useState>([]); + const followControls = useFollowsControls(); async function fetchLastPosts() { setStatus(PruneStage.FetchLastPostTimestamp); @@ -74,12 +75,7 @@ export function PruneFollowList() { }, [uniqueFollows, unfollow]); async function publishFollowList() { - const newFollows = newFollowList.map(a => ["p", a]) as Array<[string, string]>; - if (publisher) { - const ev = await publisher.contactList(newFollows); - await system.BroadcastEvent(ev); - setFollows(id, newFollowList, ev.created_at * 1000); - } + await followControls.setFollows(newFollowList); } function getStatus() { diff --git a/packages/app/src/Utils/Login/Functions.ts b/packages/app/src/Utils/Login/Functions.ts index 078cac50..2497b87f 100644 --- a/packages/app/src/Utils/Login/Functions.ts +++ b/packages/app/src/Utils/Login/Functions.ts @@ -122,8 +122,10 @@ export async function generateNewLogin( const publisher = EventPublisher.privateKey(privateKey); // Create new contact list following self and site account - const contactList = [publicKey, ...CONFIG.signUp.defaultFollows.map(a => bech32ToHex(a))].map(a => ["p", a]); - const ev = await publisher.contactList(contactList); + const contactList = [publicKey, ...CONFIG.signUp.defaultFollows.map(a => bech32ToHex(a))].map(a => ["p", a]) as Array< + [string, string] + >; + const ev = await publisher.contactList(contactList, newRelays); system.BroadcastEvent(ev); // Create relay metadata event diff --git a/packages/system/src/event-publisher.ts b/packages/system/src/event-publisher.ts index 5210df36..1a366abb 100644 --- a/packages/system/src/event-publisher.ts +++ b/packages/system/src/event-publisher.ts @@ -257,9 +257,12 @@ export class EventPublisher { return await this.#sign(eb); } - async contactList(tags: Array<[string, string]>) { + async contactList(tags: Array<[string, string]>, relays?: Record) { const eb = this.#eb(EventKind.ContactList); tags.forEach(a => eb.tag(a)); + if (relays) { + eb.content(JSON.stringify(relays)); + } return await this.#sign(eb); }