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
+
+