mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 21:31:43 +01:00
show recent event kinds on profile
This commit is contained in:
parent
bf6d243d61
commit
c9daeb96ae
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -1368,8 +1368,8 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@kurkle/color@0.3.2':
|
||||
resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==}
|
||||
'@kurkle/color@0.3.4':
|
||||
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
||||
|
||||
'@lezer/common@1.2.3':
|
||||
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
|
||||
@ -5664,7 +5664,7 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@kurkle/color@0.3.2': {}
|
||||
'@kurkle/color@0.3.4': {}
|
||||
|
||||
'@lezer/common@1.2.3': {}
|
||||
|
||||
@ -6424,7 +6424,7 @@ snapshots:
|
||||
|
||||
chart.js@4.4.6:
|
||||
dependencies:
|
||||
'@kurkle/color': 0.3.2
|
||||
'@kurkle/color': 0.3.4
|
||||
|
||||
cheerio-select@2.1.0:
|
||||
dependencies:
|
||||
|
@ -68,6 +68,8 @@ import Recording02 from "./icons/recording-02";
|
||||
import Upload01 from "./icons/upload-01";
|
||||
import Modem02 from "./icons/modem-02";
|
||||
import BookOpen01 from "./icons/book-open-01";
|
||||
import Edit04 from "./icons/edit-04";
|
||||
import Film02 from "./icons/film-02";
|
||||
|
||||
const defaultProps: IconProps = { boxSize: 4 };
|
||||
|
||||
@ -248,3 +250,6 @@ export const InboxIcon = Download01;
|
||||
export const OutboxIcon = Upload01;
|
||||
|
||||
export const WikiIcon = BookOpen01;
|
||||
|
||||
export const ArticleIcon = Edit04;
|
||||
export const VideoIcon = Film02;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
ArticleIcon,
|
||||
BadgeIcon,
|
||||
BookmarkIcon,
|
||||
ChannelsIcon,
|
||||
@ -13,11 +14,11 @@ import {
|
||||
SearchIcon,
|
||||
TorrentIcon,
|
||||
TrackIcon,
|
||||
VideoIcon,
|
||||
WikiIcon,
|
||||
} from "../../components/icons";
|
||||
import { App } from "./component/app-card";
|
||||
import ShieldOff from "../../components/icons/shield-off";
|
||||
import Film02 from "../../components/icons/film-02";
|
||||
import MessageQuestionSquare from "../../components/icons/message-question-square";
|
||||
import UploadCloud01 from "../../components/icons/upload-cloud-01";
|
||||
import Edit04 from "../../components/icons/edit-04";
|
||||
@ -52,8 +53,8 @@ export const internalApps: App[] = [
|
||||
{ title: "Bookmarks", description: "Manage your bookmarks", icon: BookmarkIcon, id: "bookmarks", to: "/bookmarks" },
|
||||
{ title: "Lists", description: "Browse and create lists", icon: ListsIcon, id: "lists", to: "/lists" },
|
||||
{ title: "Tracks", description: "Browse stemstr tracks", icon: TrackIcon, id: "tracks", to: "/tracks" },
|
||||
{ title: "Videos", description: "Browse flare videos", icon: Film02, id: "videos", to: "/videos" },
|
||||
{ title: "Articles", description: "Browse articles", icon: Edit04, id: "articles", to: "/articles" },
|
||||
{ title: "Videos", description: "Browse videos", icon: VideoIcon, id: "videos", to: "/videos" },
|
||||
{ title: "Articles", description: "Browse articles", icon: ArticleIcon, id: "articles", to: "/articles" },
|
||||
];
|
||||
|
||||
export const internalTools: App[] = [
|
||||
|
@ -21,7 +21,7 @@ import { useLocalStorage } from "react-use";
|
||||
import { Subscription as IDBSubscription } from "nostr-idb";
|
||||
import _throttle from "lodash.throttle";
|
||||
import stringify from "json-stringify-deterministic";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { useLocation, useSearchParams } from "react-router-dom";
|
||||
|
||||
import VerticalPageLayout from "../../../components/vertical-page-layout";
|
||||
import BackButton from "../../../components/router/back-button";
|
||||
@ -52,6 +52,7 @@ const EventTimeline = memo(({ events }: { events: NostrEvent[] }) => {
|
||||
|
||||
export default function EventConsoleView() {
|
||||
const [params, setParams] = useSearchParams();
|
||||
const location = useLocation();
|
||||
const historyDrawer = useDisclosure();
|
||||
const [history, setHistory] = useLocalStorage<string[]>("console-history", []);
|
||||
const helpModal = useDisclosure();
|
||||
@ -62,11 +63,13 @@ export default function EventConsoleView() {
|
||||
const [sub, setSub] = useState<Subscription | IDBSubscription | null>(null);
|
||||
|
||||
const [query, setQuery] = useState(() => {
|
||||
if (params.has("filter")) {
|
||||
if (params.has("filter") || location.state.filter) {
|
||||
const str = params.get("filter");
|
||||
if (str) {
|
||||
const f = safeJson(str, null);
|
||||
if (f) return JSON.stringify(f, null, 2);
|
||||
} else if (typeof location.state.filter === "object") {
|
||||
return JSON.stringify(location.state.filter, null, 2);
|
||||
}
|
||||
}
|
||||
if (history?.[0]) return history?.[0];
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { nip19, NostrEvent } from "nostr-tools";
|
||||
import { ChatIcon } from "@chakra-ui/icons";
|
||||
|
||||
import { getLudEndpoint } from "../../../helpers/lnurl";
|
||||
@ -51,6 +51,9 @@ import UserName from "../../../components/user/user-name";
|
||||
import { useUserDNSIdentity } from "../../../hooks/use-user-dns-identity";
|
||||
import { renderGenericUrl } from "../../../components/content/links/common";
|
||||
import UserAboutContent from "../../../components/user/user-about";
|
||||
import { useStoreQuery } from "applesauce-react/hooks";
|
||||
import { TimelineQuery } from "applesauce-core/queries";
|
||||
import UserRecentEvents from "./user-recent-events";
|
||||
|
||||
function DNSIdentityWarning({ pubkey }: { pubkey: string }) {
|
||||
const metadata = useUserProfile(pubkey);
|
||||
@ -218,6 +221,10 @@ export default function UserAboutTab() {
|
||||
</Flex>
|
||||
|
||||
<UserProfileBadges pubkey={pubkey} px="2" />
|
||||
<Box px="2">
|
||||
<Heading size="md">Recent activity:</Heading>
|
||||
<UserRecentEvents pubkey={pubkey} />
|
||||
</Box>
|
||||
<UserStatsAccordion pubkey={pubkey} />
|
||||
|
||||
<Flex gap="2" wrap="wrap">
|
||||
|
179
src/views/user/about/user-recent-events.tsx
Normal file
179
src/views/user/about/user-recent-events.tsx
Normal file
@ -0,0 +1,179 @@
|
||||
import { Badge, Button, ButtonProps, ComponentWithAs, Flex, IconProps, useDisclosure } from "@chakra-ui/react";
|
||||
import { Filter, kinds, nip19, NostrEvent } from "nostr-tools";
|
||||
import { Link as RouteLink, To } from "react-router-dom";
|
||||
|
||||
import { ArticleIcon, DirectMessagesIcon, ListsIcon, NotesIcon, RepostIcon } from "../../../components/icons";
|
||||
import AnnotationQuestion from "../../../components/icons/annotation-question";
|
||||
import { getSharableEventAddress } from "../../../services/event-relay-hint";
|
||||
import { npubEncode } from "nostr-tools/nip19";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import { useUserOutbox } from "../../../hooks/use-user-mailboxes";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import AlertTriangle from "../../../components/icons/alert-triangle";
|
||||
|
||||
type KnownKind = {
|
||||
kind: number;
|
||||
name?: string;
|
||||
hidden?: boolean;
|
||||
icon?: ComponentWithAs<"svg", IconProps>;
|
||||
link?: (events: NostrEvent[], pubkey: string) => LinkNav | undefined;
|
||||
single?: (event: NostrEvent, pubkey: string) => LinkNav | undefined;
|
||||
multiple?: (events: NostrEvent[], pubkey: string) => LinkNav | undefined;
|
||||
};
|
||||
|
||||
type LinkNav = string | { to: To; state: any };
|
||||
|
||||
function singleLink(event: NostrEvent, _pubkey: string) {
|
||||
const address = getSharableEventAddress(event);
|
||||
return address ? `/l/${address}` : undefined;
|
||||
}
|
||||
function consoleLink(events: NostrEvent[], pubkey: string) {
|
||||
const kinds = new Set(events.map((e) => e.kind));
|
||||
return {
|
||||
to: "/tools/console",
|
||||
state: { filter: { kinds: Array.from(kinds), authors: [pubkey] } satisfies Filter },
|
||||
};
|
||||
}
|
||||
|
||||
const KnownKinds: KnownKind[] = [
|
||||
{
|
||||
kind: kinds.ShortTextNote,
|
||||
name: "Notes",
|
||||
icon: NotesIcon,
|
||||
link: (_, p) => `/u/${npubEncode(p)}/notes`,
|
||||
},
|
||||
{
|
||||
kind: kinds.Repost,
|
||||
name: "Repost",
|
||||
icon: RepostIcon,
|
||||
link: (_e, p) => `/u/${npubEncode(p)}/notes`,
|
||||
},
|
||||
{
|
||||
kind: kinds.GenericRepost,
|
||||
name: "Generic Repost",
|
||||
icon: RepostIcon,
|
||||
hidden: true,
|
||||
link: (_e, p) => `/u/${npubEncode(p)}/notes`,
|
||||
},
|
||||
|
||||
{
|
||||
kind: kinds.LongFormArticle,
|
||||
name: "Articles",
|
||||
icon: ArticleIcon,
|
||||
link: (_, p) => `/u/${npubEncode(p)}/articles`,
|
||||
},
|
||||
|
||||
{
|
||||
kind: kinds.EncryptedDirectMessage,
|
||||
name: "Legacy DMs",
|
||||
icon: DirectMessagesIcon,
|
||||
link: (_e, p) => `/u/${nip19.npubEncode(p)}/dms`,
|
||||
},
|
||||
|
||||
{ kind: kinds.Followsets, name: "Lists", icon: ListsIcon, link: (_e, p) => `/u/${npubEncode(p)}/lists` },
|
||||
|
||||
{ kind: kinds.Report, name: "Report", icon: AlertTriangle, link: (_e, p) => `/u/${npubEncode(p)}/reports` },
|
||||
|
||||
{ kind: kinds.Handlerinformation, name: "Application" },
|
||||
{ kind: kinds.Handlerrecommendation, name: "App recommendation" },
|
||||
|
||||
{ kind: kinds.BadgeAward, name: "Badge Award" },
|
||||
|
||||
// common kinds
|
||||
{ kind: kinds.Metadata, hidden: true },
|
||||
{ kind: kinds.Contacts, hidden: true },
|
||||
{ kind: kinds.EventDeletion, hidden: true },
|
||||
{ kind: kinds.Reaction, hidden: true },
|
||||
|
||||
// NIP-51 lists
|
||||
{ kind: kinds.RelayList, hidden: true },
|
||||
{ kind: kinds.BookmarkList, hidden: true },
|
||||
{ kind: kinds.InterestsList, hidden: true },
|
||||
{ kind: kinds.Pinlist, hidden: true },
|
||||
{ kind: kinds.UserEmojiList, hidden: true },
|
||||
{ kind: kinds.Mutelist, hidden: true },
|
||||
{ kind: kinds.CommunitiesList, hidden: true },
|
||||
{ kind: kinds.SearchRelaysList, hidden: true },
|
||||
{ kind: kinds.BlockedRelaysList, hidden: true },
|
||||
|
||||
{ kind: 30008, hidden: true, name: "Badges" }, // profile badges
|
||||
|
||||
{ kind: kinds.Application, name: "App data", hidden: true },
|
||||
];
|
||||
|
||||
function EventKindButton({
|
||||
kind,
|
||||
events,
|
||||
pubkey,
|
||||
known,
|
||||
}: { kind: number; events: NostrEvent[]; pubkey: string; known?: KnownKind } & Omit<ButtonProps, "icon">) {
|
||||
const Icon = known?.icon;
|
||||
const icon = Icon ? <Icon boxSize={10} mb="4" /> : <AnnotationQuestion boxSize={10} mb="4" />;
|
||||
|
||||
let nav = known?.link?.(events, pubkey);
|
||||
if (!nav) {
|
||||
if (events.length === 1) {
|
||||
nav = known?.single?.(events[0], pubkey) || singleLink(events[0], pubkey);
|
||||
} else {
|
||||
nav = known?.multiple?.(events, pubkey) || consoleLink(events, pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
const linkProps = typeof nav === "string" ? { to: nav } : nav;
|
||||
|
||||
return (
|
||||
<Button
|
||||
as={RouteLink}
|
||||
{...linkProps}
|
||||
variant="outline"
|
||||
leftIcon={icon}
|
||||
h="36"
|
||||
w="36"
|
||||
flexDirection="column"
|
||||
position="relative"
|
||||
>
|
||||
<Badge position="absolute" top="2" right="2" fontSize="md">
|
||||
{events.length}
|
||||
</Badge>
|
||||
{known?.name || kind}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UserRecentEvents({ pubkey }: { pubkey: string }) {
|
||||
const outbox = useUserOutbox(pubkey);
|
||||
const readRelays = useReadRelays();
|
||||
const { timeline: recent } = useTimelineLoader(`${pubkey}-recent-events`, outbox || readRelays, {
|
||||
authors: [pubkey],
|
||||
limit: 100,
|
||||
});
|
||||
|
||||
// const recent = useStoreQuery(TimelineQuery, [{ authors: [pubkey], limit: 100 }]);
|
||||
const all = useDisclosure();
|
||||
|
||||
const byKind = recent?.reduce(
|
||||
(dir, event) => {
|
||||
if (dir[event.kind]) dir[event.kind].events.push(event);
|
||||
else
|
||||
dir[event.kind] = {
|
||||
known: KnownKinds.find((k) => k.kind === event.kind),
|
||||
events: [event],
|
||||
};
|
||||
|
||||
return dir;
|
||||
},
|
||||
{} as Record<number, { events: NostrEvent[]; known?: KnownKind }>,
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{byKind &&
|
||||
Object.entries(byKind)
|
||||
.filter(([_, { known }]) => (known ? known.hidden !== true : true))
|
||||
.sort((a, b) => parseInt(a[0]) - parseInt(b[0]))
|
||||
.map(([kind, { events, known }]) => (
|
||||
<EventKindButton key={kind} kind={parseInt(kind)} events={events} pubkey={pubkey} known={known} />
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user