snort/packages/app/src/Pages/HashTagsPage.tsx

106 lines
3.4 KiB
TypeScript

import { useMemo } from "react";
import { Link, useParams } from "react-router-dom";
import { FormattedMessage, FormattedNumber } from "react-intl";
import { EventKind, NostrHashtagLink, NoteCollection, RequestBuilder } from "@snort/system";
import { dedupe } from "@snort/shared";
import { useRequestBuilder } from "@snort/system-react";
import Timeline from "@/Element/Feed/Timeline";
import useEventPublisher from "@/Hooks/useEventPublisher";
import useLogin from "@/Hooks/useLogin";
import { setTags } from "@/Login";
import AsyncButton from "@/Element/AsyncButton";
import ProfileImage from "@/Element/User/ProfileImage";
import classNames from "classnames";
import { formatShort } from "@/Number";
const HashTagsPage = () => {
const params = useParams();
const tag = (params.tag ?? "").toLowerCase();
return (
<>
<div className="bb p">
<HashTagHeader tag={tag} />
</div>
<Timeline
key={tag}
subject={{ type: "hashtag", items: [tag], discriminator: tag }}
postsOnly={false}
method={"TIME_RANGE"}
/>
</>
);
};
export default HashTagsPage;
export function HashTagHeader({ tag, events, className }: { tag: string; events?: number; className?: string }) {
const login = useLogin();
const isFollowing = useMemo(() => {
return login.tags.item.includes(tag);
}, [login, tag]);
const { publisher, system } = useEventPublisher();
async function followTags(ts: string[]) {
if (publisher) {
const ev = await publisher.bookmarks(
ts.map(a => new NostrHashtagLink(a)),
"follow",
);
setTags(login, ts, ev.created_at * 1000);
await system.BroadcastEvent(ev);
}
}
const sub = useMemo(() => {
const rb = new RequestBuilder(`hashtag-counts:${tag}`);
rb.withFilter().kinds([EventKind.CategorizedBookmarks]).tag("d", ["follow"]).tag("t", [tag.toLowerCase()]);
return rb;
}, [tag]);
const followsTag = useRequestBuilder(NoteCollection, sub);
const pubkeys = dedupe((followsTag.data ?? []).map(a => a.pubkey));
return (
<div className={classNames("flex flex-col", className)}>
<div className="flex items-center justify-between">
<div className="flex g8 items-center">
<b className="text-xl">
<Link to={`/t/${tag}`}>#{tag}</Link>
</b>
{events && (
<small>
<FormattedMessage
defaultMessage="{n} notes"
id="un1nGw"
values={{
n: formatShort(events),
}}
/>
</small>
)}
</div>
{isFollowing ? (
<AsyncButton className="secondary" onClick={() => followTags(login.tags.item.filter(t => t !== tag))}>
<FormattedMessage defaultMessage="Unfollow" id="izWS4J" />
</AsyncButton>
) : (
<AsyncButton onClick={() => followTags(login.tags.item.concat([tag]))}>
<FormattedMessage defaultMessage="Follow" id="ieGrWo" />
</AsyncButton>
)}
</div>
<div className="flex items-center">
{pubkeys.slice(0, 5).map(a => (
<ProfileImage pubkey={a} showUsername={false} showFollowDistance={false} size={40} />
))}
{pubkeys.length > 5 && (
<span>
+<FormattedNumber value={pubkeys.length - 5} />
</span>
)}
</div>
</div>
);
}