From f6f974adc631506f92bd2936b0c57918e687641b Mon Sep 17 00:00:00 2001 From: codytseng Date: Thu, 11 Dec 2025 23:37:05 +0800 Subject: [PATCH] feat: add NSFW display policy setting --- src/components/Note/index.tsx | 10 +++-- src/components/NoteCard/index.tsx | 10 +++-- src/constants.ts | 9 ++++- src/i18n/locales/ar.ts | 7 +++- src/i18n/locales/de.ts | 7 +++- src/i18n/locales/en.ts | 7 +++- src/i18n/locales/es.ts | 7 +++- src/i18n/locales/fa.ts | 7 +++- src/i18n/locales/fr.ts | 9 ++++- src/i18n/locales/hi.ts | 7 +++- src/i18n/locales/hu.ts | 7 +++- src/i18n/locales/it.ts | 7 +++- src/i18n/locales/ja.ts | 7 +++- src/i18n/locales/ko.ts | 7 +++- src/i18n/locales/pl.ts | 7 +++- src/i18n/locales/pt-BR.ts | 7 +++- src/i18n/locales/pt-PT.ts | 7 +++- src/i18n/locales/ru.ts | 7 +++- src/i18n/locales/th.ts | 7 +++- src/i18n/locales/zh.ts | 7 +++- .../secondary/GeneralSettingsPage/index.tsx | 28 ++++++++++---- src/providers/ContentPolicyProvider.tsx | 18 ++++----- src/services/local-storage.service.ts | 37 +++++++++++++------ src/types/index.d.ts | 5 ++- 24 files changed, 185 insertions(+), 53 deletions(-) diff --git a/src/components/Note/index.tsx b/src/components/Note/index.tsx index 314b16c6..6df9b6d6 100644 --- a/src/components/Note/index.tsx +++ b/src/components/Note/index.tsx @@ -1,5 +1,5 @@ import { useSecondaryPage } from '@/PageManager' -import { ExtendedKind, SUPPORTED_KINDS } from '@/constants' +import { ExtendedKind, NSFW_DISPLAY_POLICY, SUPPORTED_KINDS } from '@/constants' import { getParentStuff, isNsfwEvent } from '@/lib/event' import { toExternalContent, toNote } from '@/lib/link' import { useContentPolicy } from '@/providers/ContentPolicyProvider' @@ -55,10 +55,14 @@ export default function Note({ const { parentEventId, parentExternalContent } = useMemo(() => { return getParentStuff(event) }, [event]) - const { defaultShowNsfw } = useContentPolicy() + const { nsfwDisplayPolicy } = useContentPolicy() const [showNsfw, setShowNsfw] = useState(false) const { mutePubkeySet } = useMuteList() const [showMuted, setShowMuted] = useState(false) + const isNsfw = useMemo( + () => (nsfwDisplayPolicy === NSFW_DISPLAY_POLICY.SHOW ? false : isNsfwEvent(event)), + [event, nsfwDisplayPolicy] + ) let content: React.ReactNode if ( @@ -72,7 +76,7 @@ export default function Note({ content = } else if (mutePubkeySet.has(event.pubkey) && !showMuted) { content = setShowMuted(true)} /> - } else if (!defaultShowNsfw && isNsfwEvent(event) && !showNsfw) { + } else if (isNsfw && !showNsfw) { content = setShowNsfw(true)} /> } else if (event.kind === kinds.Highlights) { content = diff --git a/src/components/NoteCard/index.tsx b/src/components/NoteCard/index.tsx index 3c4adda0..e42726a3 100644 --- a/src/components/NoteCard/index.tsx +++ b/src/components/NoteCard/index.tsx @@ -1,5 +1,6 @@ import { Skeleton } from '@/components/ui/skeleton' -import { isMentioningMutedUsers } from '@/lib/event' +import { NSFW_DISPLAY_POLICY } from '@/constants' +import { isMentioningMutedUsers, isNsfwEvent } from '@/lib/event' import { cn } from '@/lib/utils' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useMuteList } from '@/providers/MuteListProvider' @@ -22,7 +23,7 @@ export default function NoteCard({ reposters?: string[] }) { const { mutePubkeySet } = useMuteList() - const { hideContentMentioningMutedUsers } = useContentPolicy() + const { hideContentMentioningMutedUsers, nsfwDisplayPolicy } = useContentPolicy() const shouldHide = useMemo(() => { if (filterMutedNotes && mutePubkeySet.has(event.pubkey)) { return true @@ -30,8 +31,11 @@ export default function NoteCard({ if (hideContentMentioningMutedUsers && isMentioningMutedUsers(event, mutePubkeySet)) { return true } + if (nsfwDisplayPolicy === NSFW_DISPLAY_POLICY.HIDE && isNsfwEvent(event)) { + return true + } return false - }, [event, filterMutedNotes, mutePubkeySet]) + }, [event, filterMutedNotes, mutePubkeySet, nsfwDisplayPolicy]) if (shouldHide) return null if (event.kind === kinds.Repost || event.kind === kinds.GenericRepost) { diff --git a/src/constants.ts b/src/constants.ts index 1b748a6b..d59e8136 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -28,7 +28,6 @@ export const StorageKey = { TRANSLATION_SERVICE_CONFIG_MAP: 'translationServiceConfigMap', MEDIA_UPLOAD_SERVICE_CONFIG_MAP: 'mediaUploadServiceConfigMap', HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes', - DEFAULT_SHOW_NSFW: 'defaultShowNsfw', DISMISSED_TOO_MANY_RELAYS_ALERT: 'dismissedTooManyRelaysAlert', SHOW_KINDS: 'showKinds', SHOW_KINDS_VERSION: 'showKindsVersion', @@ -43,6 +42,8 @@ export const StorageKey = { FILTER_OUT_ONION_RELAYS: 'filterOutOnionRelays', QUICK_REACTION: 'quickReaction', QUICK_REACTION_EMOJI: 'quickReactionEmoji', + NSFW_DISPLAY_POLICY: 'nsfwDisplayPolicy', + DEFAULT_SHOW_NSFW: 'defaultShowNsfw', // deprecated PINNED_PUBKEYS: 'pinnedPubkeys', // deprecated MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated @@ -170,6 +171,12 @@ export const MEDIA_AUTO_LOAD_POLICY = { NEVER: 'never' } as const +export const NSFW_DISPLAY_POLICY = { + HIDE: 'hide', + HIDE_CONTENT: 'hide_content', + SHOW: 'show' +} as const + export const MAX_PINNED_NOTES = 10 export const PRIMARY_COLORS = { diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index 17916b90..67bb40c1 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -571,6 +571,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'إذا تم التمكين، يمكنك التفاعل بنقرة واحدة. اضغط مع الاستمرار للمزيد من الخيارات', 'Quick reaction emoji': 'رمز تعبيري للرد السريع', - 'Select emoji': 'اختر رمز تعبيري' + 'Select emoji': 'اختر رمز تعبيري', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index 40a7f64f..a72da865 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -587,6 +587,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'Wenn aktiviert, können Sie mit einem Klick reagieren. Klicken und halten Sie für weitere Optionen', 'Quick reaction emoji': 'Schnellreaktions-Emoji', - 'Select emoji': 'Emoji auswählen' + 'Select emoji': 'Emoji auswählen', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 0ae4608e..effe6198 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -574,6 +574,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'If enabled, you can react with a single click. Click and hold for more options', 'Quick reaction emoji': 'Quick reaction emoji', - 'Select emoji': 'Select emoji' + 'Select emoji': 'Select emoji', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index 08062ae0..e2c0c43d 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -583,6 +583,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'Si está habilitado, puedes reaccionar con un solo clic. Mantén presionado para más opciones', 'Quick reaction emoji': 'Emoji de reacción rápida', - 'Select emoji': 'Seleccionar emoji' + 'Select emoji': 'Seleccionar emoji', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index f616f0b6..eae6b5c4 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -577,6 +577,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'اگر فعال باشد، می‌توانید با یک کلیک واکنش نشان دهید. برای گزینه‌های بیشتر کلیک کنید و نگه دارید', 'Quick reaction emoji': 'ایموجی واکنش سریع', - 'Select emoji': 'انتخاب ایموجی' + 'Select emoji': 'انتخاب ایموجی', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index d9e15dfe..b40aedd9 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -584,8 +584,13 @@ export default { notes: 'notes', 'Quick reaction': 'Réaction rapide', 'If enabled, you can react with a single click. Click and hold for more options': - 'Si activé, vous pouvez réagir en un seul clic. Maintenez enfoncé pour plus d\'options', + "Si activé, vous pouvez réagir en un seul clic. Maintenez enfoncé pour plus d'options", 'Quick reaction emoji': 'Emoji de réaction rapide', - 'Select emoji': 'Sélectionner un emoji' + 'Select emoji': 'Sélectionner un emoji', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index e94b1c8a..50c6f393 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -578,6 +578,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'यदि सक्षम है, तो आप एक क्लिक से प्रतिक्रिया दे सकते हैं। अधिक विकल्पों के लिए क्लिक करें और रोकें', 'Quick reaction emoji': 'त्वरित प्रतिक्रिया इमोजी', - 'Select emoji': 'इमोजी चुनें' + 'Select emoji': 'इमोजी चुनें', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/hu.ts b/src/i18n/locales/hu.ts index 61cba850..435a2663 100644 --- a/src/i18n/locales/hu.ts +++ b/src/i18n/locales/hu.ts @@ -572,6 +572,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'Ha engedélyezve van, egy kattintással reagálhat. Tartsa lenyomva további lehetőségekért', 'Quick reaction emoji': 'Gyors reakció emoji', - 'Select emoji': 'Emoji kiválasztása' + 'Select emoji': 'Emoji kiválasztása', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index 328245cc..c1161411 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -582,6 +582,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'Se abilitato, puoi reagire con un solo clic. Fai clic e tieni premuto per altre opzioni', 'Quick reaction emoji': 'Emoji reazione rapida', - 'Select emoji': 'Seleziona emoji' + 'Select emoji': 'Seleziona emoji', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index 35d414f8..9141d13e 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -577,6 +577,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': '有効にすると、ワンクリックでリアクションできます。長押しで他のオプションを表示', 'Quick reaction emoji': 'クイックリアクション絵文字', - 'Select emoji': '絵文字を選択' + 'Select emoji': '絵文字を選択', + 'NSFW content display': 'NSFWコンテンツの表示', + 'Hide completely': '完全に非表示', + 'Show but hide content': '表示するがコンテンツを非表示', + 'Show directly': '直接表示', + 'Click to view': 'クリックして表示' } } diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index c6a2de67..7684c88c 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -576,6 +576,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': '활성화하면 한 번의 클릭으로 반응할 수 있습니다. 더 많은 옵션을 보려면 길게 누르세요', 'Quick reaction emoji': '빠른 반응 이모지', - 'Select emoji': '이모지 선택' + 'Select emoji': '이모지 선택', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index 6fb26145..e4ed880c 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -583,6 +583,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'Jeśli włączone, możesz zareagować jednym kliknięciem. Kliknij i przytrzymaj, aby uzyskać więcej opcji', 'Quick reaction emoji': 'Emoji szybkiej reakcji', - 'Select emoji': 'Wybierz emoji' + 'Select emoji': 'Wybierz emoji', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index cc71d34b..7f5397c5 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -578,6 +578,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'Se ativado, você pode reagir com um único clique. Clique e segure para mais opções', 'Quick reaction emoji': 'Emoji de reação rápida', - 'Select emoji': 'Selecionar emoji' + 'Select emoji': 'Selecionar emoji', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index 98a6f155..00dcaff8 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -581,6 +581,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'Se ativado, pode reagir com um único clique. Clique e mantenha premido para mais opções', 'Quick reaction emoji': 'Emoji de reação rápida', - 'Select emoji': 'Selecionar emoji' + 'Select emoji': 'Selecionar emoji', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index db06931b..188bd82e 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -583,6 +583,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'Если включено, вы можете реагировать одним щелчком. Нажмите и удерживайте для дополнительных параметров', 'Quick reaction emoji': 'Эмодзи быстрой реакции', - 'Select emoji': 'Выбрать эмодзи' + 'Select emoji': 'Выбрать эмодзи', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index 8aed9307..620b1b6d 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -570,6 +570,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': 'หากเปิดใช้งาน คุณสามารถรีแอคได้ด้วยคลิกเดียว คลิกค้างไว้สำหรับตัวเลือกเพิ่มเติม', 'Quick reaction emoji': 'อีโมจิรีแอคชั่นด่วน', - 'Select emoji': 'เลือกอีโมจิ' + 'Select emoji': 'เลือกอีโมจิ', + 'NSFW content display': 'NSFW content display', + 'Hide completely': 'Hide completely', + 'Show but hide content': 'Show but hide content', + 'Show directly': 'Show directly', + 'Click to view': 'Click to view' } } diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 06cc817f..0130e5a0 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -563,6 +563,11 @@ export default { 'If enabled, you can react with a single click. Click and hold for more options': '启用后,您可以通过单击进行点赞。长按以获取更多选项', 'Quick reaction emoji': '快速点赞表情', - 'Select emoji': '选择表情' + 'Select emoji': '选择表情', + 'NSFW content display': 'NSFW 内容显示', + 'Hide completely': '完全隐藏', + 'Show but hide content': '显示但隐藏内容', + 'Show directly': '直接显示', + 'Click to view': '点击查看' } } diff --git a/src/pages/secondary/GeneralSettingsPage/index.tsx b/src/pages/secondary/GeneralSettingsPage/index.tsx index e8c87120..c5da59d9 100644 --- a/src/pages/secondary/GeneralSettingsPage/index.tsx +++ b/src/pages/secondary/GeneralSettingsPage/index.tsx @@ -4,14 +4,14 @@ import { Button } from '@/components/ui/button' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select' import { Switch } from '@/components/ui/switch' -import { MEDIA_AUTO_LOAD_POLICY } from '@/constants' +import { MEDIA_AUTO_LOAD_POLICY, NSFW_DISPLAY_POLICY } from '@/constants' import { LocalizedLanguageNames, TLanguage } from '@/i18n' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { cn, isSupportCheckConnectionType } from '@/lib/utils' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useUserPreferences } from '@/providers/UserPreferencesProvider' import { useUserTrust } from '@/providers/UserTrustProvider' -import { TMediaAutoLoadPolicy } from '@/types' +import { TMediaAutoLoadPolicy, TNsfwDisplayPolicy } from '@/types' import { SelectValue } from '@radix-ui/react-select' import { RotateCcw } from 'lucide-react' import { forwardRef, HTMLProps, useState } from 'react' @@ -23,8 +23,8 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { const { autoplay, setAutoplay, - defaultShowNsfw, - setDefaultShowNsfw, + nsfwDisplayPolicy, + setNsfwDisplayPolicy, hideContentMentioningMutedUsers, setHideContentMentioningMutedUsers, mediaAutoLoadPolicy, @@ -110,10 +110,24 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { /> -