Show content warning for NIP-36 notes

This commit is contained in:
hzrd149 2023-05-05 08:09:20 -05:00
parent 0df1db85ae
commit 285a2dd1c7
5 changed files with 98 additions and 12 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add content warning for NIP-36 notes

View File

@ -2,7 +2,12 @@ import React, { useMemo } from "react";
import { Link as RouterLink } from "react-router-dom"; import { Link as RouterLink } from "react-router-dom";
import moment from "moment"; import moment from "moment";
import { import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Box, Box,
Button,
ButtonGroup, ButtonGroup,
Card, Card,
CardBody, CardBody,
@ -13,6 +18,7 @@ import {
Heading, Heading,
IconButton, IconButton,
Link, Link,
Spacer,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { NostrEvent } from "../../types/nostr-event"; import { NostrEvent } from "../../types/nostr-event";
import { UserAvatarLink } from "../user-avatar-link"; import { UserAvatarLink } from "../user-avatar-link";
@ -29,7 +35,7 @@ import { convertTimestampToDate } from "../../helpers/date";
import { useCurrentAccount } from "../../hooks/use-current-account"; import { useCurrentAccount } from "../../hooks/use-current-account";
import ReactionButton from "./buttons/reaction-button"; import ReactionButton from "./buttons/reaction-button";
import NoteZapButton from "./note-zap-button"; import NoteZapButton from "./note-zap-button";
import { ExpandProvider } from "./expanded"; import { ExpandProvider, useExpand } from "./expanded";
import useSubject from "../../hooks/use-subject"; import useSubject from "../../hooks/use-subject";
import appSettings from "../../services/app-settings"; import appSettings from "../../services/app-settings";
import EventVerificationIcon from "../event-verification-icon"; import EventVerificationIcon from "../event-verification-icon";
@ -38,6 +44,31 @@ import { RepostButton } from "./buttons/repost-button";
import { QuoteRepostButton } from "./buttons/quote-repost-button"; import { QuoteRepostButton } from "./buttons/quote-repost-button";
import { useReadRelayUrls } from "../../hooks/use-client-relays"; import { useReadRelayUrls } from "../../hooks/use-client-relays";
import { ExternalLinkIcon } from "../icons"; 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 ? (
<SensitiveContentWarning description={contentWarning} />
) : (
<NoteContents
event={event}
trusted={event.pubkey === account.pubkey || following.includes(event.pubkey)}
maxHeight={maxHeight}
/>
);
}
export type NoteProps = { export type NoteProps = {
event: NostrEvent; event: NostrEvent;
@ -46,13 +77,8 @@ export type NoteProps = {
}; };
export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteProps) => { export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteProps) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const account = useCurrentAccount();
const { showReactions, showSignatureVerification } = useSubject(appSettings); const { showReactions, showSignatureVerification } = useSubject(appSettings);
const readRelays = useReadRelayUrls();
const contacts = useUserContacts(account.pubkey, readRelays);
const following = contacts?.contacts || [];
// find mostr external link // find mostr external link
const externalLink = useMemo(() => event.tags.find((t) => t[0] === "mostr"), [event]); 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
</Flex> </Flex>
</CardHeader> </CardHeader>
<CardBody px="2" py="0"> <CardBody px="2" py="0">
<NoteContents <NoteContentWithWarning event={event} maxHeight={maxHeight} />
event={event}
trusted={event.pubkey === account.pubkey || following.includes(event.pubkey)}
maxHeight={maxHeight}
/>
</CardBody> </CardBody>
<CardFooter padding="2" display="flex" gap="2"> <CardFooter padding="2" display="flex" gap="2">
<ButtonGroup size="sm" variant="link"> <ButtonGroup size="sm" variant="link">

View File

@ -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 (
<Alert
status="warning"
flexDirection="column"
alignItems="center"
justifyContent="center"
textAlign="center"
height="200px"
>
<AlertIcon boxSize="40px" mr={0} />
<AlertTitle mt={4} mb={1} fontSize="lg">
Sensitive Content
</AlertTitle>
<AlertDescription maxWidth="sm">{description}</AlertDescription>
<Button mt="2" onClick={expand?.onExpand} colorScheme="red">
Show
</Button>
</Alert>
);
}
return (
<Alert status="warning">
<AlertIcon boxSize="30px" mr="4" />
<AlertTitle fontSize="lg">Sensitive Content</AlertTitle>
<AlertDescription maxWidth="sm">{description}</AlertDescription>
<Spacer />
<Button mt="2" onClick={expand?.onExpand} colorScheme="red">
Show
</Button>
</Alert>
);
}

View File

@ -26,6 +26,7 @@ export type AppSettings = {
zapAmounts: number[]; zapAmounts: number[];
primaryColor: string; primaryColor: string;
imageProxy: string; imageProxy: string;
showContentWarning: boolean;
}; };
export const defaultSettings: AppSettings = { export const defaultSettings: AppSettings = {
@ -39,6 +40,7 @@ export const defaultSettings: AppSettings = {
zapAmounts: [50, 200, 500, 1000], zapAmounts: [50, 200, 500, 1000],
primaryColor: "#8DB600", primaryColor: "#8DB600",
imageProxy: "", imageProxy: "",
showContentWarning: true,
}; };
function parseAppSettings(event: NostrEvent): AppSettings { function parseAppSettings(event: NostrEvent): AppSettings {

View File

@ -44,7 +44,7 @@ function ColorPicker({ value, onPickColor, ...props }: { onPickColor?: (color: s
} }
export default function DisplaySettings() { export default function DisplaySettings() {
const { blurImages, colorMode, primaryColor, updateSettings } = useAppSettings(); const { blurImages, colorMode, primaryColor, updateSettings, showContentWarning } = useAppSettings();
return ( return (
<AccordionItem> <AccordionItem>
@ -106,6 +106,21 @@ export default function DisplaySettings() {
<span>Enabled: blur images for people you aren't following</span> <span>Enabled: blur images for people you aren't following</span>
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
<FormControl>
<Flex alignItems="center">
<FormLabel htmlFor="show-content-warning" mb="0">
Show content warning
</FormLabel>
<Switch
id="show-content-warning"
isChecked={showContentWarning}
onChange={(v) => updateSettings({ showContentWarning: v.target.checked })}
/>
</Flex>
<FormHelperText>
<span>Enabled: shows a warning for notes with NIP-36 Content Warning</span>
</FormHelperText>
</FormControl>
<FormControl> <FormControl>
<Flex alignItems="center"> <Flex alignItems="center">
<FormLabel htmlFor="show-ads" mb="0"> <FormLabel htmlFor="show-ads" mb="0">