chore: cleanup right widget styles & NIP-89 handler

This commit is contained in:
2024-09-19 10:37:11 +01:00
parent 17e8955f54
commit 41c0047f5b
23 changed files with 773 additions and 94 deletions

View File

@ -65,7 +65,6 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam
return (
<FollowListBase
pubkeys={ids}
showAbout={true}
className={className}
title={findTag(ev, "title") ?? findTag(ev, "d")}
actions={
@ -81,6 +80,11 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam
</AsyncButton>
</>
}
profilePreviewProps={{
options: {
about: true,
},
}}
/>
);
}

View File

@ -2,6 +2,8 @@ import { mapEventToProfile, NostrLink, TaggedNostrEvent } from "@snort/system";
import { FormattedMessage } from "react-intl";
import Icon from "@/Components/Icons/Icon";
import NostrIcon from "@/Components/Icons/Nostrich";
import KindName from "@/Components/kind-name";
import Avatar from "@/Components/User/Avatar";
import DisplayName from "@/Components/User/DisplayName";
import useAppHandler from "@/Hooks/useAppHandler";
@ -11,32 +13,49 @@ export default function NoteAppHandler({ ev }: { ev: TaggedNostrEvent }) {
const link = NostrLink.fromEvent(ev);
const profiles = handlers.apps
.filter(a => a.tags.find(b => b[0] === "web" && b[2] === "nevent"))
.map(a => ({ profile: mapEventToProfile(a), event: a }))
.filter(a => a.profile)
.slice(0, 5);
return (
<div className="card flex flex-col gap-2">
<div className="card flex flex-col gap-3">
<small>
<FormattedMessage defaultMessage="Sorry, we dont understand this event kind, please try one of the following apps instead!" />
<FormattedMessage
defaultMessage="Sorry, we dont understand this event kind ({name}), please try one of the following apps instead!"
values={{
name: <KindName kind={ev.kind} />,
}}
/>
</small>
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => {
window.open(`nostr:${link.encode()}`, "_blank");
}}>
<div className="flex items-center gap-2">
<NostrIcon width={40} />
<FormattedMessage defaultMessage="Native App" />
</div>
<Icon name="link" />
</div>
{profiles.map(a => (
<div className="flex justify-between items-center" key={a.event.id}>
<div
className="flex justify-between items-center cursor-pointer"
key={a.event.id}
onClick={() => {
const webHandler = a.event.tags.find(a => a[0] === "web" && a[2] === "nevent")?.[1];
if (webHandler) {
window.open(webHandler.replace("<bech32>", link.encode()), "_blank");
}
}}>
<div className="flex items-center gap-2">
<Avatar size={40} pubkey={a.event.pubkey} user={a.profile} />
<div>
<DisplayName pubkey={a.event.pubkey} user={a.profile} />
</div>
</div>
<Icon
name="link"
onClick={() => {
const webHandler = a.event.tags.find(a => a[0] === "web" && a[2] === "nevent")?.[1];
if (webHandler) {
window.open(webHandler.replace("<bech32>", link.encode()), "_blank");
}
}}
/>
<Icon name="link" />
</div>
))}
</div>

View File

@ -4,13 +4,14 @@ import { FormattedMessage } from "react-intl";
export interface NoteTimeProps {
from: number;
fallback?: string;
className?: string;
}
const secondsInAMinute = 60;
const secondsInAnHour = secondsInAMinute * 60;
const secondsInADay = secondsInAnHour * 24;
const NoteTime: React.FC<NoteTimeProps> = ({ from, fallback }) => {
const NoteTime: React.FC<NoteTimeProps> = ({ from, fallback, className }) => {
const calcTime = useCallback((fromTime: number) => {
const currentTime = new Date();
const timeDifference = Math.floor((currentTime.getTime() - fromTime) / 1000);
@ -52,7 +53,7 @@ const NoteTime: React.FC<NoteTimeProps> = ({ from, fallback }) => {
const isoDate = useMemo(() => new Date(from).toISOString(), [from]);
return (
<time dateTime={isoDate} title={absoluteTime}>
<time dateTime={isoDate} title={absoluteTime} className={className}>
{time || fallback}
</time>
);

View File

@ -27,5 +27,14 @@ export default function UsersFeed({ keyword, sortPopular = true }: { keyword: st
if (!usersFeed) return <PageSpinner />;
return <FollowListBase pubkeys={usersFeed} showAbout={true} />;
return (
<FollowListBase
pubkeys={usersFeed}
profilePreviewProps={{
options: {
about: true,
},
}}
/>
);
}

View File

@ -32,8 +32,10 @@ export default function InviteFriendsWidget() {
<div className="flex flex-col gap-2">
<FormattedMessage defaultMessage="Share a personalized invitation with friends!" />
<div>
<AsyncButton onClick={() => copy.copy(`https://${window.location.host}?ref=${refCode?.code}`)}>
<Icon name="copy" />
<AsyncButton
className="secondary"
onClick={() => copy.copy(`https://${window.location.host}?ref=${refCode?.code}`)}>
<Icon name={copy.copied ? "check" : "copy"} />
<FormattedMessage defaultMessage="Copy link" />
</AsyncButton>
</div>

View File

@ -60,7 +60,14 @@ export default function SuggestedProfiles() {
{/*<option value={Provider.SemisolDev}>semisol.dev</option>*/}
</select>
</div>
<FollowListBase pubkeys={userList as HexKey[]} showAbout={true} />
<FollowListBase
pubkeys={userList as HexKey[]}
profilePreviewProps={{
options: {
about: true,
},
}}
/>
</>
);
}

View File

@ -5,25 +5,34 @@ import NoteTime from "@/Components/Event/Note/NoteTime";
import Text from "@/Components/Text/Text";
import ProfileImage from "@/Components/User/ProfileImage";
export default function ShortNote({ event }: { event: TaggedNostrEvent }) {
import DisplayName from "../User/DisplayName";
export default function TrendingNote({ event }: { event: TaggedNostrEvent }) {
// replace newlines with spaces, replace double spaces with single spaces
const content = event.content.slice(0, 80).replace(/\n/g, " ").replace(/ +/g, " ");
return (
<Link to={`/${NostrLink.fromEvent(event).encode(CONFIG.eventLinkPrefix)}`} className="flex flex-col">
<Link to={`/${NostrLink.fromEvent(event).encode(CONFIG.eventLinkPrefix)}`} className="flex flex-col gap-1">
<div className="flex flex-row justify-between">
<ProfileImage pubkey={event.pubkey} size={32} showProfileCard={true} />
<NoteTime from={event.created_at * 1000} />
</div>
<div className="ml-10">
<Text
id={event.id + "short"}
tags={event.tags}
creator={event.pubkey}
content={content}
truncate={75}
disableMedia={true}
<ProfileImage
pubkey={event.pubkey}
size={28}
showProfileCard={true}
overrideUsername={
<>
<DisplayName pubkey={event.pubkey} className="font-semibold" />
<NoteTime from={event.created_at * 1000} className="font-normal text-sm" />
</>
}
/>
</div>
<Text
id={event.id + "short"}
tags={event.tags}
creator={event.pubkey}
content={content}
truncate={75}
disableMedia={true}
/>
</Link>
);
}

View File

@ -10,7 +10,7 @@ import ImageGridItem from "@/Components/Feed/ImageGridItem";
import { useLocale } from "@/Components/IntlProvider/useLocale";
import PageSpinner from "@/Components/PageSpinner";
import { SpotlightThreadModal } from "@/Components/Spotlight/SpotlightThreadModal";
import ShortNote from "@/Components/Trending/ShortNote";
import TrendingNote from "@/Components/Trending/ShortNote";
import NostrBandApi from "@/External/NostrBand";
import useCachedFetch from "@/Hooks/useCachedFetch";
import useLogin from "@/Hooks/useLogin";
@ -83,7 +83,7 @@ export default function TrendingNotes({ count = Infinity, small = false }: { cou
const renderList = () => {
return filteredAndLimitedPosts.map((e, index) =>
small ? (
<ShortNote key={e.id} event={e as TaggedNostrEvent} />
<TrendingNote key={e.id} event={e as TaggedNostrEvent} />
) : (
<Note key={e.id} data={e as TaggedNostrEvent} depth={0} options={options} waitUntilInView={index > 5} />
),
@ -91,7 +91,7 @@ export default function TrendingNotes({ count = Infinity, small = false }: { cou
};
return (
<div className={classNames("flex flex-col", { "gap-6": small, "py-4": small })}>
<div className={classNames("flex flex-col", { "gap-4": small, "py-4": small })}>
{!small && <DisplayAsSelector activeSelection={displayAs} onSelect={a => setDisplayAs(a)} />}
{displayAs === "grid" ? renderGrid() : renderList()}
{modalThread && (

View File

@ -11,15 +11,11 @@ import { ErrorOrOffline } from "../ErrorOrOffline";
export default function TrendingUsers({
title,
count = Infinity,
followAll = true,
actions,
profileActions,
followListProps,
}: {
title?: ReactNode;
count?: number;
followAll?: boolean;
actions?: FollowListBaseProps["actions"];
profileActions?: FollowListBaseProps["profileActions"];
followListProps?: Omit<FollowListBaseProps, "pubkeys">;
}) {
const api = new NostrBandApi();
const trendingProfilesUrl = api.trendingProfilesUrl();
@ -42,11 +38,14 @@ export default function TrendingUsers({
return (
<FollowListBase
pubkeys={trendingUsersData.slice(0, count) as HexKey[]}
showAbout={true}
title={title}
showFollowAll={followAll}
actions={actions}
profileActions={profileActions}
showFollowAll={true}
profilePreviewProps={{
options: {
about: true,
},
}}
{...followListProps}
/>
);
}

View File

@ -10,13 +10,14 @@ import { getDisplayNameOrPlaceHolder } from "@/Utils";
interface DisplayNameProps {
pubkey: HexKey;
user?: UserMetadata | undefined;
className?: string;
}
const DisplayName = ({ pubkey, user }: DisplayNameProps) => {
const DisplayName = ({ pubkey, user, className }: DisplayNameProps) => {
const profile = useUserProfile(user ? undefined : pubkey) ?? user;
const [name, isPlaceHolder] = useMemo(() => getDisplayNameOrPlaceHolder(profile, pubkey), [profile, pubkey]);
return <span className={classNames({ placeholder: isPlaceHolder })}>{name}</span>;
return <span className={classNames(className, { placeholder: isPlaceHolder })}>{name}</span>;
};
export default DisplayName;

View File

@ -1,4 +1,5 @@
import { HexKey } from "@snort/system";
import classNames from "classnames";
import { FormattedMessage } from "react-intl";
import AsyncButton from "@/Components/Button/AsyncButton";
@ -17,11 +18,10 @@ export default function FollowButton(props: FollowButtonProps) {
const readonly = useLogin(s => s.readonly);
const control = useFollowsControls();
const isFollowing = control.isFollowing(pubkey);
const baseClassname = props.className ? `${props.className} ` : "";
return (
<AsyncButton
className={isFollowing ? `${baseClassname} secondary` : `${baseClassname} primary`}
className={classNames(props.className, "secondary")}
disabled={readonly}
onClick={async e => {
e.stopPropagation();

View File

@ -2,7 +2,7 @@ import { HexKey } from "@snort/system";
import { ReactNode, useMemo } from "react";
import { FormattedMessage } from "react-intl";
import ProfilePreview from "@/Components/User/ProfilePreview";
import ProfilePreview, { ProfilePreviewProps } from "@/Components/User/ProfilePreview";
import useFollowsControls from "@/Hooks/useFollowControls";
import useLogin from "@/Hooks/useLogin";
@ -13,26 +13,22 @@ export interface FollowListBaseProps {
pubkeys: HexKey[];
title?: ReactNode;
showFollowAll?: boolean;
showAbout?: boolean;
className?: string;
actions?: ReactNode;
profileActions?: (pk: string) => ReactNode;
profilePreviewProps?: Omit<ProfilePreviewProps, "pubkey">;
}
export default function FollowListBase({
pubkeys,
title,
showFollowAll,
showAbout,
className,
actions,
profileActions,
profilePreviewProps,
}: FollowListBaseProps) {
const control = useFollowsControls();
const login = useLogin();
const profilePreviewOptions = useMemo(() => ({ about: showAbout, profileCards: true }), [showAbout]);
async function followAll() {
await control.addFollow(pubkeys);
}
@ -50,13 +46,7 @@ export default function FollowListBase({
)}
<div className={className}>
{pubkeys?.map((a, index) => (
<ProfilePreview
pubkey={a}
key={a}
options={profilePreviewOptions}
actions={profileActions?.(a)}
waitUntilInView={index > 10}
/>
<ProfilePreview pubkey={a} key={a} waitUntilInView={index > 10} {...profilePreviewProps} />
))}
</div>
</div>

View File

@ -1,4 +1,3 @@
import { HexKey, NostrPrefix } from "@snort/system";
import { FormattedMessage } from "react-intl";
import MuteButton from "@/Components/User/MuteButton";
@ -8,7 +7,7 @@ import useModeration from "@/Hooks/useModeration";
import messages from "../messages";
export interface MutedListProps {
pubkeys: HexKey[];
pubkeys: Array<string>;
}
export default function MutedList() {
@ -22,15 +21,15 @@ export default function MutedList() {
</div>
</div>
{muteList?.map(a => {
switch (a.type) {
case NostrPrefix.Profile:
case NostrPrefix.PublicKey: {
const tag = a.toEventTag();
switch (tag?.at(0)) {
case "p": {
return (
<ProfilePreview
actions={<MuteButton pubkey={a.id} />}
pubkey={a.id}
actions={<MuteButton pubkey={tag[1]} />}
pubkey={tag[1]}
options={{ about: false }}
key={a.id}
key={tag[1]}
/>
);
}

View File

@ -22,7 +22,7 @@ export interface ProfileImageProps {
link?: string;
defaultNip?: string;
verifyNip?: boolean;
overrideUsername?: string;
overrideUsername?: ReactNode;
profile?: UserMetadata;
size?: number;
onClick?: (e: React.MouseEvent) => void;
@ -57,12 +57,12 @@ export default function ProfileImage({
const handleMouseEnter = useCallback(() => {
hoverTimeoutRef.current && clearTimeout(hoverTimeoutRef.current);
hoverTimeoutRef.current = setTimeout(() => setIsHovering(true), 100); // Adjust timeout as needed
hoverTimeoutRef.current = setTimeout(() => setIsHovering(true), 100);
}, []);
const handleMouseLeave = useCallback(() => {
hoverTimeoutRef.current && clearTimeout(hoverTimeoutRef.current);
hoverTimeoutRef.current = setTimeout(() => setIsHovering(false), 300); // Adjust timeout as needed
hoverTimeoutRef.current = setTimeout(() => setIsHovering(false), 300);
}, []);
function handleClick(e: React.MouseEvent) {

View File

@ -2,27 +2,25 @@ import "./ProfilePreview.css";
import { HexKey, UserMetadata } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { memo, ReactNode } from "react";
import { ReactNode } from "react";
import { useInView } from "react-intersection-observer";
import FollowButton from "@/Components/User/FollowButton";
import ProfileImage from "@/Components/User/ProfileImage";
import ProfileImage, { ProfileImageProps } from "@/Components/User/ProfileImage";
export interface ProfilePreviewProps {
pubkey: HexKey;
options?: {
about?: boolean;
linkToProfile?: boolean;
profileCards?: boolean;
};
subHeader?: ReactNode;
profile?: UserMetadata;
actions?: ReactNode;
className?: string;
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
waitUntilInView?: boolean;
profileImageProps?: Omit<ProfileImageProps, "pubkey" | "profile">;
}
export default memo(function ProfilePreview(props: ProfilePreviewProps) {
export default function ProfilePreview(props: ProfilePreviewProps) {
const pubkey = props.pubkey;
const { ref, inView } = useInView({ triggerOnce: true, rootMargin: "500px" });
const user = useUserProfile(inView ? pubkey : undefined);
@ -50,9 +48,8 @@ export default memo(function ProfilePreview(props: ProfilePreviewProps) {
<ProfileImage
pubkey={pubkey}
profile={props.profile}
link={options.linkToProfile ?? true ? undefined : ""}
subHeader={options.about ? <div className="about">{user?.about}</div> : props.subHeader}
showProfileCard={options.profileCards}
subHeader={options.about && <div className="about">{user?.about}</div>}
{...props.profileImageProps}
/>
{props.actions ?? (
<div className="whitespace-nowrap">
@ -64,4 +61,4 @@ export default memo(function ProfilePreview(props: ProfilePreviewProps) {
</div>
</>
);
});
}

View File

@ -0,0 +1,226 @@
import { FormattedMessage } from "react-intl";
export default function KindName({ kind }: { kind: number }) {
switch (kind) {
case 0:
return <FormattedMessage defaultMessage="User Metadata" />;
case 1:
return <FormattedMessage defaultMessage="Short Text Note" />;
case 2:
return <FormattedMessage defaultMessage="Recommend Relay" />;
case 3:
return <FormattedMessage defaultMessage="Follows" />;
case 4:
return <FormattedMessage defaultMessage="Encrypted Direct Messages" />;
case 5:
return <FormattedMessage defaultMessage="Event Deletion Request" />;
case 6:
return <FormattedMessage defaultMessage="Repost" />;
case 7:
return <FormattedMessage defaultMessage="Reaction" />;
case 8:
return <FormattedMessage defaultMessage="Badge Award" />;
case 9:
return <FormattedMessage defaultMessage="Group Chat Message" />;
case 10:
return <FormattedMessage defaultMessage="Group Chat Threaded Reply" />;
case 11:
return <FormattedMessage defaultMessage="Group Thread" />;
case 12:
return <FormattedMessage defaultMessage="Group Thread Reply" />;
case 13:
return <FormattedMessage defaultMessage="Seal" />;
case 14:
return <FormattedMessage defaultMessage="Direct Message" />;
case 16:
return <FormattedMessage defaultMessage="Generic Repost" />;
case 17:
return <FormattedMessage defaultMessage="Reaction to a website" />;
case 40:
return <FormattedMessage defaultMessage="Channel Creation" />;
case 41:
return <FormattedMessage defaultMessage="Channel Metadata" />;
case 42:
return <FormattedMessage defaultMessage="Channel Message" />;
case 43:
return <FormattedMessage defaultMessage="Channel Hide Message" />;
case 44:
return <FormattedMessage defaultMessage="Channel Mute User" />;
case 64:
return <FormattedMessage defaultMessage="Chess (PGN)" />;
case 818:
return <FormattedMessage defaultMessage="Merge Requests" />;
case 1021:
return <FormattedMessage defaultMessage="Bid" />;
case 1022:
return <FormattedMessage defaultMessage="Bid confirmation" />;
case 1040:
return <FormattedMessage defaultMessage="OpenTimestamps" />;
case 1059:
return <FormattedMessage defaultMessage="Gift Wrap" />;
case 1063:
return <FormattedMessage defaultMessage="File Metadata" />;
case 1311:
return <FormattedMessage defaultMessage="Live Chat Message" />;
case 1617:
return <FormattedMessage defaultMessage="Patches" />;
case 1621:
return <FormattedMessage defaultMessage="Issues" />;
case 1622:
return <FormattedMessage defaultMessage="Replies" />;
case 1971:
return <FormattedMessage defaultMessage="Problem Tracker" />;
case 1984:
return <FormattedMessage defaultMessage="Reporting" />;
case 1985:
return <FormattedMessage defaultMessage="Label" />;
case 1986:
return <FormattedMessage defaultMessage="Relay reviews" />;
case 1987:
return <FormattedMessage defaultMessage="AI Embeddings / Vector lists" />;
case 2003:
return <FormattedMessage defaultMessage="Torrent" />;
case 2004:
return <FormattedMessage defaultMessage="Torrent Comment" />;
case 2022:
return <FormattedMessage defaultMessage="Coinjoin Pool" />;
case 4550:
return <FormattedMessage defaultMessage="Community Post Approval" />;
case 7000:
return <FormattedMessage defaultMessage="Job Feedback" />;
case 9041:
return <FormattedMessage defaultMessage="Zap Goal" />;
case 9467:
return <FormattedMessage defaultMessage="Tidal login" />;
case 9734:
return <FormattedMessage defaultMessage="Zap Request" />;
case 9735:
return <FormattedMessage defaultMessage="Zap" />;
case 9802:
return <FormattedMessage defaultMessage="Highlights" />;
case 10000:
return <FormattedMessage defaultMessage="Mute list" />;
case 10001:
return <FormattedMessage defaultMessage="Pin list" />;
case 10002:
return <FormattedMessage defaultMessage="Relay List Metadata" />;
case 10003:
return <FormattedMessage defaultMessage="Bookmark list" />;
case 10004:
return <FormattedMessage defaultMessage="Communities list" />;
case 10005:
return <FormattedMessage defaultMessage="Public chats list" />;
case 10006:
return <FormattedMessage defaultMessage="Blocked relays list" />;
case 10007:
return <FormattedMessage defaultMessage="Search relays list" />;
case 10009:
return <FormattedMessage defaultMessage="User groups" />;
case 10015:
return <FormattedMessage defaultMessage="Interests list" />;
case 10030:
return <FormattedMessage defaultMessage="User emoji list" />;
case 10050:
return <FormattedMessage defaultMessage="Relay list to receive DMs" />;
case 10063:
return <FormattedMessage defaultMessage="User server list" />;
case 10096:
return <FormattedMessage defaultMessage="File storage server list" />;
case 13194:
return <FormattedMessage defaultMessage="Wallet Info" />;
case 21000:
return <FormattedMessage defaultMessage="Lightning Pub RPC" />;
case 22242:
return <FormattedMessage defaultMessage="Client Authentication" />;
case 23194:
return <FormattedMessage defaultMessage="Wallet Request" />;
case 23195:
return <FormattedMessage defaultMessage="Wallet Response" />;
case 24133:
return <FormattedMessage defaultMessage="Nostr Connect" />;
case 24242:
return <FormattedMessage defaultMessage="Blobs stored on mediaservers" />;
case 27235:
return <FormattedMessage defaultMessage="HTTP Auth" />;
case 30000:
return <FormattedMessage defaultMessage="Follow sets" />;
case 30001:
return <FormattedMessage defaultMessage="Generic lists" />;
case 30002:
return <FormattedMessage defaultMessage="Relay sets" />;
case 30003:
return <FormattedMessage defaultMessage="Bookmark sets" />;
case 30004:
return <FormattedMessage defaultMessage="Curation sets" />;
case 30005:
return <FormattedMessage defaultMessage="Video sets" />;
case 30007:
return <FormattedMessage defaultMessage="Kind mute sets" />;
case 30008:
return <FormattedMessage defaultMessage="Profile Badges" />;
case 30009:
return <FormattedMessage defaultMessage="Badge Definition" />;
case 30015:
return <FormattedMessage defaultMessage="Interest sets" />;
case 30017:
return <FormattedMessage defaultMessage="Create or update a stall" />;
case 30018:
return <FormattedMessage defaultMessage="Create or update a product" />;
case 30019:
return <FormattedMessage defaultMessage="Marketplace UI/UX" />;
case 30020:
return <FormattedMessage defaultMessage="Product sold as an auction" />;
case 30023:
return <FormattedMessage defaultMessage="Long-form Content" />;
case 30024:
return <FormattedMessage defaultMessage="Draft Long-form Content" />;
case 30030:
return <FormattedMessage defaultMessage="Emoji sets" />;
case 30040:
return <FormattedMessage defaultMessage="Modular Article Header" />;
case 30041:
return <FormattedMessage defaultMessage="Modular Article Content" />;
case 30063:
return <FormattedMessage defaultMessage="Release artifact sets" />;
case 30078:
return <FormattedMessage defaultMessage="Application-specific Data" />;
case 30311:
return <FormattedMessage defaultMessage="Live Event" />;
case 30315:
return <FormattedMessage defaultMessage="User Statuses" />;
case 30402:
return <FormattedMessage defaultMessage="Classified Listing" />;
case 30403:
return <FormattedMessage defaultMessage="Draft Classified Listing" />;
case 30617:
return <FormattedMessage defaultMessage="Repository announcements" />;
case 30618:
return <FormattedMessage defaultMessage="Repository state announcements" />;
case 30818:
return <FormattedMessage defaultMessage="Wiki article" />;
case 30819:
return <FormattedMessage defaultMessage="Redirects" />;
case 31890:
return <FormattedMessage defaultMessage="Feed" />;
case 31922:
return <FormattedMessage defaultMessage="Date-Based Calendar Event" />;
case 31923:
return <FormattedMessage defaultMessage="Time-Based Calendar Event" />;
case 31924:
return <FormattedMessage defaultMessage="Calendar" />;
case 31925:
return <FormattedMessage defaultMessage="Calendar Event RSVP" />;
case 31989:
return <FormattedMessage defaultMessage="Handler recommendation" />;
case 31990:
return <FormattedMessage defaultMessage="Handler information" />;
case 34235:
return <FormattedMessage defaultMessage="Video Event" />;
case 34236:
return <FormattedMessage defaultMessage="Short-form Portrait Video Event" />;
case 34237:
return <FormattedMessage defaultMessage="Video View Event" />;
case 34550:
return <FormattedMessage defaultMessage="Community Definition" />;
}
}