diff --git a/.changeset/rich-plants-explode.md b/.changeset/rich-plants-explode.md new file mode 100644 index 000000000..21423d91c --- /dev/null +++ b/.changeset/rich-plants-explode.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add content warning for NIP-36 notes diff --git a/src/components/note/index.tsx b/src/components/note/index.tsx index 327611f08..3ec08174b 100644 --- a/src/components/note/index.tsx +++ b/src/components/note/index.tsx @@ -2,7 +2,12 @@ import React, { useMemo } from "react"; import { Link as RouterLink } from "react-router-dom"; import moment from "moment"; import { + Alert, + AlertDescription, + AlertIcon, + AlertTitle, Box, + Button, ButtonGroup, Card, CardBody, @@ -13,6 +18,7 @@ import { Heading, IconButton, Link, + Spacer, } from "@chakra-ui/react"; import { NostrEvent } from "../../types/nostr-event"; import { UserAvatarLink } from "../user-avatar-link"; @@ -29,7 +35,7 @@ import { convertTimestampToDate } from "../../helpers/date"; import { useCurrentAccount } from "../../hooks/use-current-account"; import ReactionButton from "./buttons/reaction-button"; import NoteZapButton from "./note-zap-button"; -import { ExpandProvider } from "./expanded"; +import { ExpandProvider, useExpand } from "./expanded"; import useSubject from "../../hooks/use-subject"; import appSettings from "../../services/app-settings"; import EventVerificationIcon from "../event-verification-icon"; @@ -38,6 +44,31 @@ import { RepostButton } from "./buttons/repost-button"; import { QuoteRepostButton } from "./buttons/quote-repost-button"; import { useReadRelayUrls } from "../../hooks/use-client-relays"; import { ExternalLinkIcon } from "../icons"; +import SensitiveContentWarning from "../sensitive-content-warning"; +import useAppSettings from "../../hooks/use-app-settings"; + +function NoteContentWithWarning({ event, maxHeight }: { event: NostrEvent; maxHeight?: number }) { + const account = useCurrentAccount(); + const expand = useExpand(); + const settings = useAppSettings(); + + const readRelays = useReadRelayUrls(); + const contacts = useUserContacts(account.pubkey, readRelays); + const following = contacts?.contacts || []; + + const contentWarning = event.tags.find((t) => t[0] === "content-warning")?.[1]; + const showContentWarning = settings.showContentWarning && contentWarning && !expand?.expanded; + + return showContentWarning ? ( + + ) : ( + + ); +} export type NoteProps = { event: NostrEvent; @@ -46,13 +77,8 @@ export type NoteProps = { }; export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteProps) => { const isMobile = useIsMobile(); - const account = useCurrentAccount(); const { showReactions, showSignatureVerification } = useSubject(appSettings); - const readRelays = useReadRelayUrls(); - const contacts = useUserContacts(account.pubkey, readRelays); - const following = contacts?.contacts || []; - // find mostr external link const externalLink = useMemo(() => event.tags.find((t) => t[0] === "mostr"), [event]); @@ -75,11 +101,7 @@ export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteP - + diff --git a/src/components/sensitive-content-warning.tsx b/src/components/sensitive-content-warning.tsx new file mode 100644 index 000000000..33968cb6c --- /dev/null +++ b/src/components/sensitive-content-warning.tsx @@ -0,0 +1,42 @@ +import { Alert, AlertDescription, AlertIcon, AlertProps, AlertTitle, Button, Spacer, useModal } from "@chakra-ui/react"; +import { useIsMobile } from "../hooks/use-is-mobile"; +import { useExpand } from "./note/expanded"; + +export default function SensitiveContentWarning({ description }: { description: string } & AlertProps) { + const isMobile = useIsMobile(); + const expand = useExpand(); + + if (isMobile) { + return ( + + + + Sensitive Content + + {description} + + + ); + } + + return ( + + + Sensitive Content + {description} + + + + ); +} diff --git a/src/services/user-app-settings.ts b/src/services/user-app-settings.ts index bd6f769b4..de2eacfae 100644 --- a/src/services/user-app-settings.ts +++ b/src/services/user-app-settings.ts @@ -26,6 +26,7 @@ export type AppSettings = { zapAmounts: number[]; primaryColor: string; imageProxy: string; + showContentWarning: boolean; }; export const defaultSettings: AppSettings = { @@ -39,6 +40,7 @@ export const defaultSettings: AppSettings = { zapAmounts: [50, 200, 500, 1000], primaryColor: "#8DB600", imageProxy: "", + showContentWarning: true, }; function parseAppSettings(event: NostrEvent): AppSettings { diff --git a/src/views/settings/display-settings.tsx b/src/views/settings/display-settings.tsx index b1101d416..f0036bbf6 100644 --- a/src/views/settings/display-settings.tsx +++ b/src/views/settings/display-settings.tsx @@ -44,7 +44,7 @@ function ColorPicker({ value, onPickColor, ...props }: { onPickColor?: (color: s } export default function DisplaySettings() { - const { blurImages, colorMode, primaryColor, updateSettings } = useAppSettings(); + const { blurImages, colorMode, primaryColor, updateSettings, showContentWarning } = useAppSettings(); return ( @@ -106,6 +106,21 @@ export default function DisplaySettings() { Enabled: blur images for people you aren't following + + + + Show content warning + + updateSettings({ showContentWarning: v.target.checked })} + /> + + + Enabled: shows a warning for notes with NIP-36 Content Warning + +