From fcd7526874212d09f9bc79fb67844112462e785c Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Sat, 11 Mar 2023 09:53:45 -0600 Subject: [PATCH] add basic reports tab to users view --- README.md | 1 - src/app.tsx | 2 ++ src/helpers/nostr-event.ts | 20 +++++++++-- src/views/user/index.tsx | 1 + src/views/user/reports.tsx | 70 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/views/user/reports.tsx diff --git a/README.md b/README.md index bc2978315..12d70abd1 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ I would recomend you use a browser extension like [Alby](https://getalby.com/) o - Rebuild relays view to show relay info and settings NIP-11 - filter list of followers by users the user has blocked/reported (stops bots/spammers from showing up at followers) - Add mentions in notes (https://css-tricks.com/so-you-want-to-build-an-mention-autocomplete-feature/) -- add `client` tag to published events - Save note drafts and let users manage them - make app a valid web share target https://developer.chrome.com/articles/web-share-target/ - handle image share diff --git a/src/app.tsx b/src/app.tsx index 36c489b4a..4df3a6d76 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -31,6 +31,7 @@ const UserZapsTab = React.lazy(() => import("./views/user/zaps")); const DirectMessagesView = React.lazy(() => import("./views/dm")); const DirectMessageChatView = React.lazy(() => import("./views/dm/chat")); const NostrLinkView = React.lazy(() => import("./views/link")); +const UserReportsTab = React.lazy(() => import("./views/user/reports")); const RequireCurrentAccount = ({ children }: { children: JSX.Element }) => { let location = useLocation(); @@ -96,6 +97,7 @@ const router = createBrowserRouter([ { path: "followers", element: }, { path: "following", element: }, { path: "relays", element: }, + { path: "reports", element: }, ], }, { diff --git a/src/helpers/nostr-event.ts b/src/helpers/nostr-event.ts index 34360e16b..ce994c038 100644 --- a/src/helpers/nostr-event.ts +++ b/src/helpers/nostr-event.ts @@ -1,6 +1,6 @@ import moment from "moment"; import { getEventRelays } from "../services/event-relays"; -import { DraftNostrEvent, isETag, isPTag, NostrEvent, RTag } from "../types/nostr-event"; +import { DraftNostrEvent, isETag, isPTag, NostrEvent, RTag, Tag } from "../types/nostr-event"; import { RelayConfig, RelayMode } from "../classes/relay"; import accountService from "../services/account"; import { Kind } from "nostr-tools"; @@ -17,6 +17,22 @@ export function truncatedId(id: string, keep = 6) { return id.substring(0, keep) + "..." + id.substring(id.length - keep); } +export function getContentTagRefs(content: string) { + return Array.from(content.matchAll(/#\[(\d+)\]/gi)).map((m) => parseInt(m[1])); +} + +export function filterTagsByContentRefs(content: string, tags: Tag[], referenced = true) { + const contentTagRefs = getContentTagRefs(content); + + const newTags: Tag[] = []; + for (let i = 0; i < tags.length; i++) { + if (contentTagRefs.includes(i) === referenced) { + newTags.push(tags[i]); + } + } + return newTags; +} + export type EventReferences = ReturnType; export function getReferences(event: NostrEvent | DraftNostrEvent) { const eTags = event.tags.filter(isETag); @@ -24,7 +40,7 @@ export function getReferences(event: NostrEvent | DraftNostrEvent) { const events = eTags.map((t) => t[1]); const pubkeys = pTags.map((t) => t[1]); - const contentTagRefs = Array.from(event.content.matchAll(/#\[(\d+)\]/gi)).map((m) => parseInt(m[1])); + const contentTagRefs = getContentTagRefs(event.content); let replyId = eTags.find((t) => t[3] === "reply")?.[1]; let rootId = eTags.find((t) => t[3] === "root")?.[1]; diff --git a/src/views/user/index.tsx b/src/views/user/index.tsx index d94a8177b..5af09a9fd 100644 --- a/src/views/user/index.tsx +++ b/src/views/user/index.tsx @@ -14,6 +14,7 @@ const tabs = [ { label: "Followers", path: "followers" }, { label: "Following", path: "following" }, { label: "Relays", path: "relays" }, + { label: "Reports", path: "reports" }, ]; const UserView = () => { diff --git a/src/views/user/reports.tsx b/src/views/user/reports.tsx new file mode 100644 index 000000000..15c17752e --- /dev/null +++ b/src/views/user/reports.tsx @@ -0,0 +1,70 @@ +import { Box, Button, Flex, Spinner, Text } from "@chakra-ui/react"; +import moment from "moment"; +import { useOutletContext } from "react-router-dom"; +import { RelayMode } from "../../classes/relay"; +import { NoteLink } from "../../components/note-link"; +import { UserLink } from "../../components/user-link"; +import { filterTagsByContentRefs, truncatedId } from "../../helpers/nostr-event"; +import { useReadRelayUrls } from "../../hooks/use-client-relays"; +import useFallbackUserRelays from "../../hooks/use-fallback-user-relays"; +import { useTimelineLoader } from "../../hooks/use-timeline-loader"; +import relayScoreboardService from "../../services/relay-scoreboard"; +import { isETag, isPTag, NostrEvent } from "../../types/nostr-event"; + +function ReportEvent({ report }: { report: NostrEvent }) { + const reportedEvent = report.tags.filter(isETag)[0]?.[1]; + const reportedPubkey = filterTagsByContentRefs(report.content, report.tags, false).filter(isPTag)[0]?.[1]; + if (!reportedEvent && !reportedPubkey) return null; + const reason = report.tags.find((t) => t[0] === "report")?.[1]; + + return ( + + {reportedEvent ? ( + <> + + {reportedPubkey && ( + <> + From + + + )} + + ) : ( + + )} + {reason} + + ); +} + +export default function UserReportsTab() { + const { pubkey } = useOutletContext() as { pubkey: string }; + // get user relays + const userRelays = useFallbackUserRelays(pubkey) + .filter((r) => r.mode & RelayMode.WRITE) + .map((r) => r.url); + // merge the users relays with client relays + const readRelays = useReadRelayUrls(); + // find the top 4 + const relays = relayScoreboardService.getRankedRelays(userRelays.length === 0 ? readRelays : userRelays).slice(0, 4); + + const { + events: reports, + loading, + loadMore, + } = useTimelineLoader( + `${truncatedId(pubkey)}-reports`, + relays, + { authors: [pubkey], kinds: [1984] }, + { pageSize: moment.duration(1, "week").asSeconds() } + ); + + return ( + + {reports.map((report) => ( + + ))} + {loading ? : } + + ); +}