diff --git a/package-lock.json b/package-lock.json
index f7d73faa..a48c429c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.4",
+ "@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "1.2.0",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
@@ -3288,6 +3289,311 @@
}
}
},
+ "node_modules/@radix-ui/react-radio-group": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz",
+ "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
diff --git a/package.json b/package.json
index 09e04637..da15a3e9 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.4",
+ "@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "1.2.0",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
diff --git a/src/components/NoteOptions/ReportDialog.tsx b/src/components/NoteOptions/ReportDialog.tsx
new file mode 100644
index 00000000..ffd9a742
--- /dev/null
+++ b/src/components/NoteOptions/ReportDialog.tsx
@@ -0,0 +1,129 @@
+import { Button } from '@/components/ui/button'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle
+} from '@/components/ui/dialog'
+import {
+ Drawer,
+ DrawerContent,
+ DrawerDescription,
+ DrawerHeader,
+ DrawerTitle
+} from '@/components/ui/drawer'
+import { Label } from '@/components/ui/label'
+import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
+import { createReportDraftEvent } from '@/lib/draft-event'
+import { useNostr } from '@/providers/NostrProvider'
+import { useScreenSize } from '@/providers/ScreenSizeProvider'
+import { Loader } from 'lucide-react'
+import { NostrEvent } from 'nostr-tools'
+import { useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { toast } from 'sonner'
+
+export default function ReportDialog({
+ event,
+ isOpen,
+ closeDialog
+}: {
+ event: NostrEvent
+ isOpen: boolean
+ closeDialog: () => void
+}) {
+ const { isSmallScreen } = useScreenSize()
+
+ if (isSmallScreen) {
+ return (
+ {
+ if (!open) {
+ closeDialog()
+ }
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ return (
+
+ )
+}
+
+function ReportContent({ event, closeDialog }: { event: NostrEvent; closeDialog: () => void }) {
+ const { t } = useTranslation()
+ const { pubkey, publish } = useNostr()
+ const [reason, setReason] = useState(null)
+ const [reporting, setReporting] = useState(false)
+
+ const handleReport = async () => {
+ if (!reason || !pubkey) return
+
+ try {
+ setReporting(true)
+ const draftEvent = createReportDraftEvent(event, reason)
+ await publish(draftEvent)
+ toast.success(t('Successfully report'))
+ closeDialog()
+ } catch (error) {
+ toast.error(t('Failed to report') + ': ' + (error as Error).message)
+ } finally {
+ setReporting(false)
+ }
+ }
+
+ return (
+
+
+ {['nudity', 'malware', 'profanity', 'illegal', 'spam', 'other'].map((item) => (
+
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/components/NoteOptions/index.tsx b/src/components/NoteOptions/index.tsx
index 3f5f7ea9..eb4b7a6b 100644
--- a/src/components/NoteOptions/index.tsx
+++ b/src/components/NoteOptions/index.tsx
@@ -5,11 +5,13 @@ import { useState } from 'react'
import { DesktopMenu } from './DesktopMenu'
import { MobileMenu } from './MobileMenu'
import RawEventDialog from './RawEventDialog'
+import ReportDialog from './ReportDialog'
import { SubMenuAction, useMenuActions } from './useMenuActions'
export default function NoteOptions({ event, className }: { event: Event; className?: string }) {
const { isSmallScreen } = useScreenSize()
const [isRawEventDialogOpen, setIsRawEventDialogOpen] = useState(false)
+ const [isReportDialogOpen, setIsReportDialogOpen] = useState(false)
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
const [showSubMenu, setShowSubMenu] = useState(false)
const [activeSubMenu, setActiveSubMenu] = useState([])
@@ -35,6 +37,7 @@ export default function NoteOptions({ event, className }: { event: Event; classN
closeDrawer,
showSubMenuActions,
setIsRawEventDialogOpen,
+ setIsReportDialogOpen,
isSmallScreen
})
@@ -70,6 +73,11 @@ export default function NoteOptions({ event, className }: { event: Event; classN
isOpen={isRawEventDialogOpen}
onClose={() => setIsRawEventDialogOpen(false)}
/>
+ setIsReportDialogOpen(false)}
+ />
)
}
diff --git a/src/components/NoteOptions/useMenuActions.tsx b/src/components/NoteOptions/useMenuActions.tsx
index c4b69573..82cfe583 100644
--- a/src/components/NoteOptions/useMenuActions.tsx
+++ b/src/components/NoteOptions/useMenuActions.tsx
@@ -6,7 +6,18 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service'
-import { Bell, BellOff, Code, Copy, Link, Mail, SatelliteDish, Server, Trash2 } from 'lucide-react'
+import {
+ Bell,
+ BellOff,
+ Code,
+ Copy,
+ Link,
+ Mail,
+ SatelliteDish,
+ Server,
+ Trash2,
+ TriangleAlert
+} from 'lucide-react'
import { Event } from 'nostr-tools'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
@@ -34,6 +45,7 @@ interface UseMenuActionsProps {
closeDrawer: () => void
showSubMenuActions: (subMenu: SubMenuAction[], title: string) => void
setIsRawEventDialogOpen: (open: boolean) => void
+ setIsReportDialogOpen: (open: boolean) => void
isSmallScreen: boolean
}
@@ -42,6 +54,7 @@ export function useMenuActions({
closeDrawer,
showSubMenuActions,
setIsRawEventDialogOpen,
+ setIsReportDialogOpen,
isSmallScreen
}: UseMenuActionsProps) {
const { t } = useTranslation()
@@ -198,6 +211,19 @@ export function useMenuActions({
})
}
+ if (pubkey && event.pubkey !== pubkey) {
+ actions.push({
+ icon: TriangleAlert,
+ label: t('Report'),
+ className: 'text-destructive focus:text-destructive',
+ onClick: () => {
+ closeDrawer()
+ setIsReportDialogOpen(true)
+ },
+ separator: true
+ })
+ }
+
if (pubkey && event.pubkey !== pubkey) {
if (isMuted) {
actions.push({
diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx
new file mode 100644
index 00000000..d40f9c90
--- /dev/null
+++ b/src/components/ui/radio-group.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react'
+import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
+import { Circle } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+const RadioGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+
+
+
+
+ )
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }
diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts
index 75a12e47..15abbb0c 100644
--- a/src/i18n/locales/ar.ts
+++ b/src/i18n/locales/ar.ts
@@ -383,6 +383,15 @@ export default {
'reposted your note': 'أعاد نشر ملاحظتك',
'zapped your note': 'زاب ملاحظتك',
'zapped you': 'زابك',
- 'Mark as read': 'تعليم كمقروء'
+ 'Mark as read': 'تعليم كمقروء',
+ Report: 'تبليغ',
+ 'Successfully report': 'تم التبليغ بنجاح',
+ 'Failed to report': 'فشل في التبليغ',
+ nudity: 'عُري',
+ malware: 'برامج ضارة',
+ profanity: 'ألفاظ نابية',
+ illegal: 'محتوى غير قانوني',
+ spam: 'رسائل مزعجة',
+ other: 'أخرى'
}
}
diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts
index bfaface4..3e69c55d 100644
--- a/src/i18n/locales/de.ts
+++ b/src/i18n/locales/de.ts
@@ -392,6 +392,15 @@ export default {
'reposted your note': 'hat Ihre Notiz geteilt',
'zapped your note': 'hat Ihre Notiz gezappt',
'zapped you': 'hat Sie gezappt',
- 'Mark as read': 'Als gelesen markieren'
+ 'Mark as read': 'Als gelesen markieren',
+ Report: 'Melden',
+ 'Successfully report': 'Erfolgreich gemeldet',
+ 'Failed to report': 'Meldung fehlgeschlagen',
+ nudity: 'Nacktheit',
+ malware: 'Schadsoftware',
+ profanity: 'Obszönität',
+ illegal: 'Illegaler Inhalt',
+ spam: 'Spam',
+ other: 'Sonstiges'
}
}
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index 17329316..c93b6f12 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -382,6 +382,15 @@ export default {
'reposted your note': 'reposted your note',
'zapped your note': 'zapped your note',
'zapped you': 'zapped you',
- 'Mark as read': 'Mark as read'
+ 'Mark as read': 'Mark as read',
+ Report: 'Report',
+ 'Successfully report': 'Successfully reported',
+ 'Failed to report': 'Failed to report',
+ nudity: 'Nudity',
+ malware: 'Malware',
+ profanity: 'Profanity',
+ illegal: 'Illegal content',
+ spam: 'Spam',
+ other: 'Other'
}
}
diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts
index d6b34a65..d12e7ed3 100644
--- a/src/i18n/locales/es.ts
+++ b/src/i18n/locales/es.ts
@@ -388,6 +388,15 @@ export default {
'reposted your note': 'reposteó tu nota',
'zapped your note': 'zappeó tu nota',
'zapped you': 'te zappeó',
- 'Mark as read': 'Marcar como leído'
+ 'Mark as read': 'Marcar como leído',
+ Report: 'Reportar',
+ 'Successfully report': 'Reporte exitoso',
+ 'Failed to report': 'Fallo al reportar',
+ nudity: 'Desnudez',
+ malware: 'Software malicioso',
+ profanity: 'Blasfemia',
+ illegal: 'Contenido ilegal',
+ spam: 'Spam',
+ other: 'Otro'
}
}
diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts
index 323e8adc..5dac2991 100644
--- a/src/i18n/locales/fa.ts
+++ b/src/i18n/locales/fa.ts
@@ -384,6 +384,15 @@ export default {
'reposted your note': 'یادداشت شما را بازنشر کرد',
'zapped your note': 'یادداشت شما را زپ کرد',
'zapped you': 'شما را زپ کرد',
- 'Mark as read': 'علامتگذاری به عنوان خوانده شده'
+ 'Mark as read': 'علامتگذاری به عنوان خوانده شده',
+ Report: 'گزارش',
+ 'Successfully report': 'گزارش با موفقیت ارسال شد',
+ 'Failed to report': 'ارسال گزارش ناموفق بود',
+ nudity: 'برهنگی',
+ malware: 'بدافزار',
+ profanity: 'فحاشی',
+ illegal: 'محتوای غیرقانونی',
+ spam: 'اسپم',
+ other: 'سایر'
}
}
diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts
index 21ff91b5..84e904c9 100644
--- a/src/i18n/locales/fr.ts
+++ b/src/i18n/locales/fr.ts
@@ -392,6 +392,15 @@ export default {
'reposted your note': 'a repartagé votre note',
'zapped your note': 'a zappé votre note',
'zapped you': 'vous a zappé',
- 'Mark as read': 'Marquer comme lu'
+ 'Mark as read': 'Marquer comme lu',
+ Report: 'Signaler',
+ 'Successfully report': 'Signalement réussi',
+ 'Failed to report': 'Échec du signalement',
+ nudity: 'Nudité',
+ malware: 'Logiciel malveillant',
+ profanity: 'Blasphème',
+ illegal: 'Contenu illégal',
+ spam: 'Spam',
+ other: 'Autre'
}
}
diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts
index 31bbaf86..e651c159 100644
--- a/src/i18n/locales/it.ts
+++ b/src/i18n/locales/it.ts
@@ -388,6 +388,15 @@ export default {
'reposted your note': 'ha ricondiviso la tua nota',
'zapped your note': 'ha zappato la tua nota',
'zapped you': 'ti ha zappato',
- 'Mark as read': 'Segna come letto'
+ 'Mark as read': 'Segna come letto',
+ Report: 'Segnala',
+ 'Successfully report': 'Segnalazione riuscita',
+ 'Failed to report': 'Segnalazione fallita',
+ nudity: 'Nudità',
+ malware: 'Malware',
+ profanity: 'Blasfemia',
+ illegal: 'Contenuto illegale',
+ spam: 'Spam',
+ other: 'Altro'
}
}
diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts
index bd36ec1d..a6b1df5b 100644
--- a/src/i18n/locales/ja.ts
+++ b/src/i18n/locales/ja.ts
@@ -385,6 +385,15 @@ export default {
'reposted your note': 'あなたのノートをリポストしました',
'zapped your note': 'あなたのノートにザップしました',
'zapped you': 'あなたにザップしました',
- 'Mark as read': '既読にする'
+ 'Mark as read': '既読にする',
+ Report: '報告',
+ 'Successfully report': '報告が成功しました',
+ 'Failed to report': '報告に失敗しました',
+ nudity: 'ヌード',
+ malware: 'マルウェア',
+ profanity: '冒涜的な内容',
+ illegal: '違法コンテンツ',
+ spam: 'スパム',
+ other: 'その他'
}
}
diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts
index 83f21785..66be0aa2 100644
--- a/src/i18n/locales/ko.ts
+++ b/src/i18n/locales/ko.ts
@@ -385,6 +385,15 @@ export default {
'reposted your note': '당신의 노트를 리포스트했습니다',
'zapped your note': '당신의 노트를 잽했습니다',
'zapped you': '당신을 잽했습니다',
- 'Mark as read': '읽음으로 표시'
+ 'Mark as read': '읽음으로 표시',
+ Report: '신고',
+ 'Successfully report': '신고가 성공적으로 완료되었습니다',
+ 'Failed to report': '신고에 실패했습니다',
+ nudity: '음란물',
+ malware: '악성 소프트웨어',
+ profanity: '욕설',
+ illegal: '불법 콘텐츠',
+ spam: '스팸',
+ other: '기타'
}
}
diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts
index b70e3114..1b044851 100644
--- a/src/i18n/locales/pl.ts
+++ b/src/i18n/locales/pl.ts
@@ -389,6 +389,15 @@ export default {
'reposted your note': 'przepostował twoją notatkę',
'zapped your note': 'zappował twoją notatkę',
'zapped you': 'zappował cię',
- 'Mark as read': 'Oznacz jako przeczytane'
+ 'Mark as read': 'Oznacz jako przeczytane',
+ Report: 'Zgłoś',
+ 'Successfully report': 'Pomyślnie zgłoszono',
+ 'Failed to report': 'Nie udało się zgłosić',
+ nudity: 'Nagość',
+ malware: 'Złośliwe oprogramowanie',
+ profanity: 'Wulgaryzmy',
+ illegal: 'Nielegalna treść',
+ spam: 'Spam',
+ other: 'Inne'
}
}
diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts
index 779dd836..07b84b58 100644
--- a/src/i18n/locales/pt-BR.ts
+++ b/src/i18n/locales/pt-BR.ts
@@ -385,6 +385,15 @@ export default {
'reposted your note': 'republicou sua nota',
'zapped your note': 'zappeou sua nota',
'zapped you': 'zappeou você',
- 'Mark as read': 'Marcar como lida'
+ 'Mark as read': 'Marcar como lida',
+ Report: 'Denunciar',
+ 'Successfully report': 'Denúncia enviada com sucesso',
+ 'Failed to report': 'Falha ao enviar denúncia',
+ nudity: 'Nudez',
+ malware: 'Malware',
+ profanity: 'Blasfêmia',
+ illegal: 'Conteúdo ilegal',
+ spam: 'Spam',
+ other: 'Outro'
}
}
diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts
index ecc11b60..d8835c9a 100644
--- a/src/i18n/locales/pt-PT.ts
+++ b/src/i18n/locales/pt-PT.ts
@@ -388,6 +388,15 @@ export default {
'reposted your note': 'republicou a sua nota',
'zapped your note': 'zappeou a sua nota',
'zapped you': 'zappeou-o',
- 'Mark as read': 'Marcar como lida'
+ 'Mark as read': 'Marcar como lida',
+ Report: 'Denunciar',
+ 'Successfully report': 'Denúncia enviada com sucesso',
+ 'Failed to report': 'Falha ao enviar denúncia',
+ nudity: 'Nudez',
+ malware: 'Malware',
+ profanity: 'Blasfémia',
+ illegal: 'Conteúdo ilegal',
+ spam: 'Spam',
+ other: 'Outro'
}
}
diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts
index 86ebf29f..8148036b 100644
--- a/src/i18n/locales/ru.ts
+++ b/src/i18n/locales/ru.ts
@@ -389,6 +389,15 @@ export default {
'reposted your note': 'репостнул вашу заметку',
'zapped your note': 'заппил вашу заметку',
'zapped you': 'заппил вас',
- 'Mark as read': 'Отметить как прочитанное'
+ 'Mark as read': 'Отметить как прочитанное',
+ Report: 'Пожаловаться',
+ 'Successfully report': 'Жалоба успешно отправлена',
+ 'Failed to report': 'Не удалось отправить жалобу',
+ nudity: 'Обнаженность',
+ malware: 'Вредоносное ПО',
+ profanity: 'Ненормативная лексика',
+ illegal: 'Незаконный контент',
+ spam: 'Спам',
+ other: 'Другое'
}
}
diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts
index 59b1d92f..9fb50fbb 100644
--- a/src/i18n/locales/th.ts
+++ b/src/i18n/locales/th.ts
@@ -380,6 +380,15 @@ export default {
'reposted your note': 'ได้รีโพสต์โน้ตของคุณ',
'zapped your note': 'ได้แซปโน้ตของคุณ',
'zapped you': 'ได้แซปคุณ',
- 'Mark as read': 'ทำเครื่องหมายว่าอ่านแล้ว'
+ 'Mark as read': 'ทำเครื่องหมายว่าอ่านแล้ว',
+ Report: 'รายงาน',
+ 'Successfully report': 'รายงานสำเร็จ',
+ 'Failed to report': 'การรายงานล้มเหลว',
+ nudity: 'ภาพลามก',
+ malware: 'มัลแวร์',
+ profanity: 'คำหยาบคาย',
+ illegal: 'เนื้อหาผิดกฎหมาย',
+ spam: 'สแปม',
+ other: 'อื่นๆ'
}
}
diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts
index 4371bda2..0c8d676f 100644
--- a/src/i18n/locales/zh.ts
+++ b/src/i18n/locales/zh.ts
@@ -378,6 +378,15 @@ export default {
'reposted your note': '转发了您的笔记',
'zapped your note': '打闪了您的笔记',
'zapped you': '给您打闪',
- 'Mark as read': '标记为已读'
+ 'Mark as read': '标记为已读',
+ Report: '举报',
+ 'Successfully report': '举报成功',
+ 'Failed to report': '举报失败',
+ nudity: '色情内容',
+ malware: '恶意软件',
+ profanity: '亵渎言论',
+ illegal: '违法内容',
+ spam: '垃圾信息',
+ other: '其他'
}
}
diff --git a/src/lib/draft-event.ts b/src/lib/draft-event.ts
index d9fcc6c9..d4854862 100644
--- a/src/lib/draft-event.ts
+++ b/src/lib/draft-event.ts
@@ -432,6 +432,26 @@ export function createDeletionRequestDraftEvent(event: Event): TDraftEvent {
}
}
+export function createReportDraftEvent(event: Event, reason: string): TDraftEvent {
+ const tags: string[][] = []
+ if (event.kind === kinds.Metadata) {
+ tags.push(['p', event.pubkey, reason])
+ } else {
+ tags.push(['p', event.pubkey])
+ tags.push(['e', event.id, reason])
+ if (isReplaceableEvent(event.kind)) {
+ tags.push(['a', getReplaceableCoordinateFromEvent(event), reason])
+ }
+ }
+
+ return {
+ kind: kinds.Report,
+ content: '',
+ tags,
+ created_at: dayjs().unix()
+ }
+}
+
function generateImetaTags(imageUrls: string[]) {
return imageUrls
.map((imageUrl) => {
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index e1a25a26..699ec3cd 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -7,7 +7,7 @@ import {
} from '@/lib/event'
import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
import { formatPubkey, isValidPubkey, pubkeyToNpub, userIdToPubkey } from '@/lib/pubkey'
-import { getPubkeysFromPTags, getServersFromServerTags } from '@/lib/tag'
+import { getPubkeysFromPTags, getServersFromServerTags, tagNameEquals } from '@/lib/tag'
import { isLocalNetworkUrl, isWebsocketUrl, normalizeUrl } from '@/lib/url'
import { isSafari } from '@/lib/utils'
import { ISigner, TProfile, TPublishOptions, TRelayList, TSubRequestFilter } from '@/types'
@@ -87,6 +87,13 @@ class ClientService extends EventTarget {
event: NEvent,
{ specifiedRelayUrls, additionalRelayUrls }: TPublishOptions = {}
) {
+ if (event.kind === kinds.Report) {
+ const targetEventId = event.tags.find(tagNameEquals('e'))?.[1]
+ if (targetEventId) {
+ return this.getSeenEventRelayUrls(targetEventId)
+ }
+ }
+
const _additionalRelayUrls: string[] = additionalRelayUrls ?? []
if (!specifiedRelayUrls?.length && ![kinds.Contacts, kinds.Mutelist].includes(event.kind)) {
const mentions: string[] = []