Browse Source

bug-fixes

imwald
Silberengel 1 week ago
parent
commit
54837d5ad2
  1. 16
      src/components/Note/NsfwNote.tsx
  2. 3
      src/components/Note/index.tsx
  3. 55
      src/components/PostEditor/PostContent.tsx
  4. 83
      src/components/PostEditor/PostEditorAdvancedPanel.tsx
  5. 10
      src/i18n/locales/en.ts
  6. 66
      src/lib/content-warning.ts
  7. 13
      src/lib/discussion-thread-composer.ts
  8. 81
      src/lib/draft-event.ts
  9. 1
      src/services/post-editor-cache.service.ts

16
src/components/Note/NsfwNote.tsx

@ -1,13 +1,25 @@ @@ -1,13 +1,25 @@
import { Button } from '@/components/ui/button'
import { DEFAULT_CONTENT_WARNING_LABEL } from '@/lib/content-warning'
import { Eye } from 'lucide-react'
import { useTranslation } from 'react-i18next'
export default function NsfwNote({ show }: { show: () => void }) {
export default function NsfwNote({
show,
label
}: {
show: () => void
label?: string | null
}) {
const { t } = useTranslation()
const normalized = label?.trim() || DEFAULT_CONTENT_WARNING_LABEL
const heading =
normalized.toLowerCase() === DEFAULT_CONTENT_WARNING_LABEL.toLowerCase()
? t('🔞 NSFW 🔞')
: t('Content warning label', { label: normalized })
return (
<div className="flex flex-col gap-2 items-center text-muted-foreground font-medium my-4">
<div>{t('🔞 NSFW 🔞')}</div>
<div className="text-center px-4">{heading}</div>
<Button
onClick={(e) => {
e.stopPropagation()

3
src/components/Note/index.tsx

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import { useSmartNoteNavigationOptional } from '@/PageManager'
import { getContentWarningLabel } from '@/lib/content-warning'
import { ExtendedKind, isMusicTrackKind, isNip71StyleVideoKind, publicAssetUrl } from '@/constants'
import { isRenderableNoteKind } from '@/lib/note-renderable-kinds'
import {
@ -429,7 +430,7 @@ export default function Note({ @@ -429,7 +430,7 @@ export default function Note({
} else if (muteSetHas(mutePubkeySet, event.pubkey) && !showMuted) {
content = <MutedNote show={() => setShowMuted(true)} />
} else if (!defaultShowNsfw && isNsfwEvent(event) && !showNsfw) {
content = <NsfwNote show={() => setShowNsfw(true)} />
content = <NsfwNote show={() => setShowNsfw(true)} label={getContentWarningLabel(event)} />
} else if (isNip25ReactionKind(event.kind)) {
content = null
} else if (isNip18RepostKind(displayEvent.kind)) {

55
src/components/PostEditor/PostContent.tsx

@ -40,6 +40,11 @@ import { @@ -40,6 +40,11 @@ import {
mergeUploadImetaTagsInto,
stripImwaldAttributionTags
} from '@/lib/draft-event'
import {
contentWarningDraftOptions,
DEFAULT_CONTENT_WARNING_LABEL,
normalizeContentWarningLabel
} from '@/lib/content-warning'
import {
ExtendedKind,
isNip71ShortVideoKind,
@ -296,6 +301,7 @@ export default function PostContent({ @@ -296,6 +301,7 @@ export default function PostContent({
const [addClientTag, setAddClientTag] = useState(() => storage.getAddClientTag())
const [mentions, setMentions] = useState<string[]>([])
const [isNsfw, setIsNsfw] = useState(false)
const [contentWarningLabel, setContentWarningLabel] = useState(DEFAULT_CONTENT_WARNING_LABEL)
const [isPoll, setIsPoll] = useState(false)
const [isPublicMessage, setIsPublicMessage] = useState(!!initialPublicMessageTo)
const [extractedMentions, setExtractedMentions] = useState<string[]>(
@ -478,7 +484,8 @@ export default function PostContent({ @@ -478,7 +484,8 @@ export default function PostContent({
isReadingGroup: threadIsReadingGroup,
author: threadReadingAuthor,
subject: threadReadingSubject,
isNsfw
isNsfw,
contentWarningLabel
})
}, [
isDiscussionThread,
@ -490,7 +497,8 @@ export default function PostContent({ @@ -490,7 +497,8 @@ export default function PostContent({
threadIsReadingGroup,
threadReadingAuthor,
threadReadingSubject,
isNsfw
isNsfw,
contentWarningLabel
])
const handleRelayPublishCapChange = useCallback((preview: TPrePublishRelayCapPreview) => {
@ -753,6 +761,9 @@ export default function PostContent({ @@ -753,6 +761,9 @@ export default function PostContent({
})
if (cachedSettings) {
setIsNsfw(cachedSettings.isNsfw ?? false)
setContentWarningLabel(
normalizeContentWarningLabel(cachedSettings.contentWarningLabel ?? DEFAULT_CONTENT_WARNING_LABEL)
)
setIsPoll(cachedSettings.isPoll ?? false)
setPollCreateData(
cachedSettings.pollCreateData ?? {
@ -770,12 +781,13 @@ export default function PostContent({ @@ -770,12 +781,13 @@ export default function PostContent({
{ kind: getDeterminedKind, defaultContent, parentEvent },
{
isNsfw,
contentWarningLabel,
isPoll,
pollCreateData,
addClientTag
}
)
}, [getDeterminedKind, defaultContent, parentEvent, isNsfw, isPoll, pollCreateData, addClientTag])
}, [getDeterminedKind, defaultContent, parentEvent, isNsfw, contentWarningLabel, isPoll, pollCreateData, addClientTag])
const prevComposerShellOpenRef = useRef(open)
const prevComposerPubkeyRef = useRef(pubkey)
@ -904,12 +916,13 @@ export default function PostContent({ @@ -904,12 +916,13 @@ export default function PostContent({
const addExpirationTag = storage.getDefaultExpirationEnabled()
const expirationMonths = storage.getDefaultExpirationMonths()
const contentWarningOpts = contentWarningDraftOptions(isNsfw, contentWarningLabel)
// Public messages - check BEFORE media notes to ensure PMs with media stay as PMs
if (isPublicMessage) {
return await createPublicMessageDraftEvent(cleanedText, extractedMentions, {
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -918,7 +931,7 @@ export default function PostContent({ @@ -918,7 +931,7 @@ export default function PostContent({
// For PM replies, always create PM even if there's media
return await createPublicMessageReplyDraftEvent(cleanedText, parentEvent, mentions, {
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -936,7 +949,8 @@ export default function PostContent({ @@ -936,7 +949,8 @@ export default function PostContent({
isReadingGroup: threadIsReadingGroup,
author: threadReadingAuthor,
subject: threadReadingSubject,
isNsfw
isNsfw,
contentWarningLabel
})
const draft: TDraftEvent = {
kind: ExtendedKind.DISCUSSION,
@ -961,7 +975,7 @@ export default function PostContent({ @@ -961,7 +975,7 @@ export default function PostContent({
mentions,
{
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: addExpirationTag && isChattingKind(ExtendedKind.VOICE_COMMENT),
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -981,7 +995,7 @@ export default function PostContent({ @@ -981,7 +995,7 @@ export default function PostContent({
mentions,
{
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: addExpirationTag && isChattingKind(ExtendedKind.VOICE),
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -994,7 +1008,7 @@ export default function PostContent({ @@ -994,7 +1008,7 @@ export default function PostContent({
mentions,
{
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -1008,7 +1022,7 @@ export default function PostContent({ @@ -1008,7 +1022,7 @@ export default function PostContent({
mediaNoteKind,
{
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -1049,7 +1063,7 @@ export default function PostContent({ @@ -1049,7 +1063,7 @@ export default function PostContent({
language: musicTrackLanguage.trim() || undefined,
genres,
addClientTag,
isNsfw
...contentWarningOpts
})
}
@ -1088,7 +1102,7 @@ export default function PostContent({ @@ -1088,7 +1102,7 @@ export default function PostContent({
image: articleImage.trim() || undefined,
topics: topics.length > 0 ? topics : undefined,
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths
})
@ -1100,7 +1114,7 @@ export default function PostContent({ @@ -1100,7 +1114,7 @@ export default function PostContent({
image: articleImage.trim() || undefined,
topics: topics.length > 0 ? topics : undefined,
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths
})
@ -1113,7 +1127,7 @@ export default function PostContent({ @@ -1113,7 +1127,7 @@ export default function PostContent({
affectedKinds: affectedKinds.length > 0 ? affectedKinds : undefined,
topics: topics.length > 0 ? topics : undefined,
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths
})
@ -1125,7 +1139,7 @@ export default function PostContent({ @@ -1125,7 +1139,7 @@ export default function PostContent({
image: articleImage.trim() || undefined,
topics: topics.length > 0 ? topics : undefined,
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths
})
@ -1210,7 +1224,7 @@ export default function PostContent({ @@ -1210,7 +1224,7 @@ export default function PostContent({
undefined,
{
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -1223,7 +1237,7 @@ export default function PostContent({ @@ -1223,7 +1237,7 @@ export default function PostContent({
if (parentEvent && parentEvent.kind !== kinds.ShortTextNote) {
return await createCommentDraftEvent(cleanedText, parentEvent, mentions, {
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: addExpirationTag && isChattingKind(ExtendedKind.COMMENT),
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -1234,7 +1248,7 @@ export default function PostContent({ @@ -1234,7 +1248,7 @@ export default function PostContent({
if (isPoll) {
return await createPollDraftEvent(pubkey!, cleanedText, mentions, pollCreateData, {
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: false,
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -1245,7 +1259,7 @@ export default function PostContent({ @@ -1245,7 +1259,7 @@ export default function PostContent({
return await createShortTextNoteDraftEvent(cleanedText, mentions, {
parentEvent,
addClientTag,
isNsfw,
...contentWarningOpts,
addExpirationTag: addExpirationTag && isChattingKind(kinds.ShortTextNote),
expirationMonths,
mediaImetaTags: uploadImetaTagsOpt
@ -1292,6 +1306,7 @@ export default function PostContent({ @@ -1292,6 +1306,7 @@ export default function PostContent({
pollCreateData,
addClientTag,
isNsfw,
contentWarningLabel,
articleDTag,
articleTitle,
articleImage,
@ -3850,6 +3865,8 @@ export default function PostContent({ @@ -3850,6 +3865,8 @@ export default function PostContent({
setAddClientTag={setAddClientTag}
isNsfw={isNsfw}
setIsNsfw={setIsNsfw}
contentWarningLabel={contentWarningLabel}
setContentWarningLabel={setContentWarningLabel}
minPow={minPow}
setMinPow={setMinPow}
showMentionsPicker={!isHighlight}

83
src/components/PostEditor/PostEditorAdvancedPanel.tsx

@ -1,12 +1,27 @@ @@ -1,12 +1,27 @@
import { MAX_PUBLISH_RELAYS } from '@/constants'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select'
import { Slider } from '@/components/ui/slider'
import { Switch } from '@/components/ui/switch'
import {
CONTENT_WARNING_CUSTOM_SELECT_VALUE,
CONTENT_WARNING_PRESETS,
DEFAULT_CONTENT_WARNING_LABEL,
isPresetContentWarningLabel,
normalizeContentWarningLabel
} from '@/lib/content-warning'
import type { TPrePublishRelayCapPreview } from '@/lib/pre-publish-relay-cap'
import { cn } from '@/lib/utils'
import storage from '@/services/local-storage.service'
import type { Event } from 'nostr-tools'
import { Dispatch, SetStateAction, useEffect } from 'react'
import { Dispatch, SetStateAction, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Mentions from './Mentions'
import PostRelaySelector from './PostRelaySelector'
@ -18,6 +33,8 @@ export type PostEditorAdvancedPanelProps = { @@ -18,6 +33,8 @@ export type PostEditorAdvancedPanelProps = {
setAddClientTag: Dispatch<SetStateAction<boolean>>
isNsfw: boolean
setIsNsfw: Dispatch<SetStateAction<boolean>>
contentWarningLabel: string
setContentWarningLabel: Dispatch<SetStateAction<string>>
minPow: number
setMinPow: Dispatch<SetStateAction<number>>
/** Relay picker + cap hints (hidden for modes that do not pick relays). */
@ -51,6 +68,8 @@ export default function PostEditorAdvancedPanel({ @@ -51,6 +68,8 @@ export default function PostEditorAdvancedPanel({
setAddClientTag,
isNsfw,
setIsNsfw,
contentWarningLabel,
setContentWarningLabel,
minPow,
setMinPow,
showRelayPicker = false,
@ -81,6 +100,11 @@ export default function PostEditorAdvancedPanel({ @@ -81,6 +100,11 @@ export default function PostEditorAdvancedPanel({
setAddClientTag(checked)
}
const selectValue = useMemo(() => {
if (isPresetContentWarningLabel(contentWarningLabel)) return contentWarningLabel
return CONTENT_WARNING_CUSTOM_SELECT_VALUE
}, [contentWarningLabel])
// Mentions + relay picker must stay mounted when Advanced is collapsed so auto-selection
// effects still run (especially on mobile where users often post without opening Advanced).
return (
@ -162,16 +186,65 @@ export default function PostEditorAdvancedPanel({ @@ -162,16 +186,65 @@ export default function PostEditorAdvancedPanel({
<p className="text-muted-foreground text-xs">{t('Show others this was sent via Imwald')}</p>
</div>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Label htmlFor="add-nsfw-tag" className="text-sm font-normal">
{t('NSFW')}
<Label htmlFor="add-content-warning-tag" className="text-sm font-normal">
{t('Content warning')}
</Label>
<Switch
id="add-nsfw-tag"
id="add-content-warning-tag"
checked={isNsfw}
onCheckedChange={setIsNsfw}
onCheckedChange={(checked) => {
setIsNsfw(checked)
if (checked && !contentWarningLabel.trim()) {
setContentWarningLabel(DEFAULT_CONTENT_WARNING_LABEL)
}
}}
disabled={posting}
/>
</div>
<p className="text-muted-foreground text-xs">{t('Content warning hint')}</p>
{isNsfw ? (
<div className="grid gap-2 sm:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)]">
<Select
value={selectValue}
onValueChange={(value) => {
if (value === CONTENT_WARNING_CUSTOM_SELECT_VALUE) {
if (isPresetContentWarningLabel(contentWarningLabel)) {
setContentWarningLabel('')
}
return
}
setContentWarningLabel(value)
}}
disabled={posting}
>
<SelectTrigger aria-label={t('Content warning preset')}>
<SelectValue placeholder={t('Content warning preset')} />
</SelectTrigger>
<SelectContent>
{CONTENT_WARNING_PRESETS.map((preset) => (
<SelectItem key={preset} value={preset}>
{preset}
</SelectItem>
))}
<SelectItem value={CONTENT_WARNING_CUSTOM_SELECT_VALUE}>{t('Custom label…')}</SelectItem>
</SelectContent>
</Select>
{selectValue === CONTENT_WARNING_CUSTOM_SELECT_VALUE ? (
<Input
value={contentWarningLabel}
onChange={(e) => setContentWarningLabel(e.target.value)}
onBlur={() =>
setContentWarningLabel((prev) => normalizeContentWarningLabel(prev))
}
placeholder={t('Content warning custom placeholder')}
disabled={posting}
maxLength={80}
/>
) : null}
</div>
) : null}
</div>
<div className="grid gap-2">

10
src/i18n/locales/en.ts

@ -300,7 +300,8 @@ export default { @@ -300,7 +300,8 @@ export default {
Connections: 'Connections',
Calls: 'Calls',
Advanced: 'Advanced',
'Post editor advanced hint': 'Relay targets, mention recipients, client tag, NSFW, and proof of work.',
'Post editor advanced hint':
'Relay targets, mention recipients, client tag, content warning, and proof of work.',
'Open Advanced to adjust mention recipients': 'Open Advanced to adjust who receives this message.',
'Add recipients using nostr: mentions (e.g., nostr:npub1...) or open Advanced':
'Add nostr:npub… or nostr:nevent… mentions in the text, or open Advanced to pick recipients.',
@ -2122,6 +2123,13 @@ export default { @@ -2122,6 +2123,13 @@ export default {
'Long-form Article': 'Long-form Article',
'Mailbox relays saved': 'Mailbox relays saved',
'Mark as NSFW': 'Mark as NSFW',
'Content warning': 'Content warning',
'Content warning hint':
'Adds a NIP-36 content-warning tag so readers must opt in to view this note.',
'Content warning preset': 'Warning label',
'Custom label…': 'Custom label…',
'Content warning custom placeholder': 'e.g. Violence, Trigger warning',
'Content warning label': '⚠ {{label}}',
'Maximum {{max}} invitees': 'Maximum {{max}} invitees',
'Maximum {{max}} invitees allowed': 'Maximum {{max}} invitees allowed',
Medium: 'Medium',

66
src/lib/content-warning.ts

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
import type { Event } from 'nostr-tools'
/** Default NIP-36 `content-warning` tag value (legacy clients also use `t` = nsfw). */
export const DEFAULT_CONTENT_WARNING_LABEL = 'NSFW'
/** Built-in labels for the post editor (stored verbatim in the `content-warning` tag). */
export const CONTENT_WARNING_PRESETS = [
DEFAULT_CONTENT_WARNING_LABEL,
'Violence',
'Trigger warning',
'Sensitive content',
'Spoilers'
] as const
export type TContentWarningPreset = (typeof CONTENT_WARNING_PRESETS)[number]
export const CONTENT_WARNING_CUSTOM_SELECT_VALUE = '__custom__'
export function normalizeContentWarningLabel(raw: string | undefined | null): string {
const trimmed = (raw ?? '').trim()
if (!trimmed) return DEFAULT_CONTENT_WARNING_LABEL
return trimmed.slice(0, 80)
}
export function buildContentWarningTag(label?: string | null): string[] {
return ['content-warning', normalizeContentWarningLabel(label)]
}
/** Read NIP-36 label from an event (`content-warning` tag, else legacy `t` = nsfw). */
export function getContentWarningLabel(event: Event): string | null {
const row = event.tags.find(([name]) => name === 'content-warning')
if (row) {
const value = row[1]?.trim()
return value || DEFAULT_CONTENT_WARNING_LABEL
}
if (event.tags.some(([name, value]) => name === 't' && value?.toLowerCase() === 'nsfw')) {
return DEFAULT_CONTENT_WARNING_LABEL
}
return null
}
export function isPresetContentWarningLabel(label: string): label is TContentWarningPreset {
return (CONTENT_WARNING_PRESETS as readonly string[]).includes(label)
}
export type TContentWarningDraftOptions = {
isNsfw?: boolean
contentWarningLabel?: string
}
export function contentWarningDraftOptions(
enabled: boolean,
label: string
): TContentWarningDraftOptions {
if (!enabled) return { isNsfw: false }
return { isNsfw: true, contentWarningLabel: normalizeContentWarningLabel(label) }
}
export function appendContentWarningTagIfNeeded(
tags: string[][],
options: TContentWarningDraftOptions
): void {
if (options.isNsfw) {
tags.push(buildContentWarningTag(options.contentWarningLabel))
}
}

13
src/lib/discussion-thread-composer.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import { ExtendedKind } from '@/constants'
import { appendContentWarningTagIfNeeded } from '@/lib/content-warning'
import { extractHashtagsFromContent, normalizeTopic } from '@/lib/discussion-topics'
import { DISCUSSION_TOPICS } from '@/pages/primary/DiscussionsPage/discussionTopics'
import { Event } from 'nostr-tools'
@ -48,10 +49,6 @@ function generateImetaTagsFromUrls(imageUrls: string[]): string[][] { @@ -48,10 +49,6 @@ function generateImetaTagsFromUrls(imageUrls: string[]): string[][] {
return imageUrls.map((url) => ['imeta', 'url', url])
}
function buildDiscussionNsfwTag(): string[] {
return ['content-warning', '']
}
/** Match preset/dynamic list by id or exact label (case-insensitive); otherwise normalize as a new topic slug. */
export function resolveTopicFromInput(raw: string, topics: TopicListEntry[]): string {
const trimmed = raw.trim()
@ -142,8 +139,10 @@ export function collectDiscussionThreadTags(params: { @@ -142,8 +139,10 @@ export function collectDiscussionThreadTags(params: {
author: string
subject: string
isNsfw: boolean
contentWarningLabel?: string
}): string[][] {
const { processedContent, topicForTags, title, dynamicTopics, isReadingGroup, author, subject, isNsfw } = params
const { processedContent, topicForTags, title, dynamicTopics, isReadingGroup, author, subject, isNsfw, contentWarningLabel } =
params
const images = extractImagesFromContent(processedContent)
const hashtags = extractHashtagsFromContent(processedContent)
const tags: string[][] = [['title', title.trim()]]
@ -221,9 +220,7 @@ export function collectDiscussionThreadTags(params: { @@ -221,9 +220,7 @@ export function collectDiscussionThreadTags(params: {
tags.push(...generateImetaTagsFromUrls(images))
}
if (isNsfw) {
tags.push(buildDiscussionNsfwTag())
}
appendContentWarningTagIfNeeded(tags, { isNsfw, contentWarningLabel })
return tags
}

81
src/lib/draft-event.ts

@ -3,6 +3,7 @@ import client from '@/services/client.service' @@ -3,6 +3,7 @@ import client from '@/services/client.service'
import { eventService } from '@/services/client.service'
import customEmojiService from '@/services/custom-emoji.service'
import mediaUpload from '@/services/media-upload.service'
import { appendContentWarningTagIfNeeded } from '@/lib/content-warning'
import { prefixNostrAddresses } from '@/lib/nostr-address'
import { normalizeHashtag, normalizeTopic } from '@/lib/discussion-topics'
import logger from '@/lib/logger'
@ -227,6 +228,7 @@ export async function createShortTextNoteDraftEvent( @@ -227,6 +228,7 @@ export async function createShortTextNoteDraftEvent(
parentEvent?: Event
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
/** NIP-94 imeta rows from uploads (audio/video/images as plain URLs in content). */
@ -266,9 +268,7 @@ export async function createShortTextNoteDraftEvent( @@ -266,9 +268,7 @@ export async function createShortTextNoteDraftEvent(
// p tags
tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -305,6 +305,7 @@ export async function createCommentDraftEvent( @@ -305,6 +305,7 @@ export async function createCommentDraftEvent(
options: {
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
mediaImetaTags?: string[][]
@ -379,9 +380,7 @@ export async function createCommentDraftEvent( @@ -379,9 +380,7 @@ export async function createCommentDraftEvent(
)
}
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -404,6 +403,7 @@ export async function createPublicMessageReplyDraftEvent( @@ -404,6 +403,7 @@ export async function createPublicMessageReplyDraftEvent(
options: {
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
mediaImetaTags?: string[][] // Allow media imeta tags for audio/video
@ -449,9 +449,7 @@ export async function createPublicMessageReplyDraftEvent( @@ -449,9 +449,7 @@ export async function createPublicMessageReplyDraftEvent(
...Array.from(recipients).map((pubkey) => buildPTag(pubkey))
)
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -479,6 +477,7 @@ export async function createPublicMessageDraftEvent( @@ -479,6 +477,7 @@ export async function createPublicMessageDraftEvent(
options: {
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
mediaImetaTags?: string[][] // Allow media imeta tags for audio/video
@ -504,9 +503,7 @@ export async function createPublicMessageDraftEvent( @@ -504,9 +503,7 @@ export async function createPublicMessageDraftEvent(
...recipients.map((pubkey) => buildPTag(pubkey))
)
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -1115,12 +1112,14 @@ export async function createPollDraftEvent( @@ -1115,12 +1112,14 @@ export async function createPollDraftEvent(
{ isMultipleChoice, relays, options, endsAt }: TPollCreateData,
{
isNsfw,
contentWarningLabel,
addExpirationTag,
expirationMonths,
mediaImetaTags
}: {
addClientTag?: boolean // accepted for API compat; client tag is added in publish()
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
mediaImetaTags?: string[][]
@ -1166,9 +1165,7 @@ export async function createPollDraftEvent( @@ -1166,9 +1165,7 @@ export async function createPollDraftEvent(
})
}
if (isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, { isNsfw, contentWarningLabel })
if (addExpirationTag && expirationMonths) {
tags.push(buildExpirationTag(expirationMonths))
@ -1606,10 +1603,6 @@ export function applyImwaldAttributionTags( @@ -1606,10 +1603,6 @@ export function applyImwaldAttributionTags(
return draft
}
function buildNsfwTag() {
return ['content-warning', 'NSFW']
}
function buildExpirationTag(months: number): string[] {
const expirationTime = dayjs().add(months, 'month').unix()
return ['expiration', expirationTime.toString()]
@ -1642,6 +1635,7 @@ export async function createHighlightDraftEvent( @@ -1642,6 +1635,7 @@ export async function createHighlightDraftEvent(
options?: {
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
mediaImetaTags?: string[][]
@ -1761,9 +1755,7 @@ export async function createHighlightDraftEvent( @@ -1761,9 +1755,7 @@ export async function createHighlightDraftEvent(
}
// Add optional tags
if (options?.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options ?? {})
if (options?.addExpirationTag && options?.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -1789,6 +1781,7 @@ export async function createVoiceDraftEvent( @@ -1789,6 +1781,7 @@ export async function createVoiceDraftEvent(
options: {
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
/** Extra NIP-94 rows from uploads (merged after content-derived imeta, deduped by URL). */
@ -1809,9 +1802,7 @@ export async function createVoiceDraftEvent( @@ -1809,9 +1802,7 @@ export async function createVoiceDraftEvent(
tags.push(...imetaTags)
tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -1834,6 +1825,7 @@ export async function createVoiceCommentDraftEvent( @@ -1834,6 +1825,7 @@ export async function createVoiceCommentDraftEvent(
options: {
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
/** NIP-94 rows from file upload (merged before `imetaTags`; deduped by URL). */
@ -1907,9 +1899,7 @@ export async function createVoiceCommentDraftEvent( @@ -1907,9 +1899,7 @@ export async function createVoiceCommentDraftEvent(
)
}
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -1931,6 +1921,7 @@ export async function createPictureDraftEvent( @@ -1931,6 +1921,7 @@ export async function createPictureDraftEvent(
title?: string
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
mediaImetaTags?: string[][]
@ -1949,9 +1940,7 @@ export async function createPictureDraftEvent( @@ -1949,9 +1940,7 @@ export async function createPictureDraftEvent(
mergeUploadImetaTagsInto(tags, options.mediaImetaTags)
tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -1986,6 +1975,7 @@ export async function createVideoDraftEvent( @@ -1986,6 +1975,7 @@ export async function createVideoDraftEvent(
title?: string
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
mediaImetaTags?: string[][]
@ -2004,9 +1994,7 @@ export async function createVideoDraftEvent( @@ -2004,9 +1994,7 @@ export async function createVideoDraftEvent(
mergeUploadImetaTagsInto(tags, options.mediaImetaTags)
tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -2036,6 +2024,7 @@ export async function createMusicTrackDraftEvent( @@ -2036,6 +2024,7 @@ export async function createMusicTrackDraftEvent(
genres?: string[]
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
}
): Promise<TDraftEvent> {
const { content: transformedEmojisContent, emojiTags } = transformCustomEmojisInContent(content)
@ -2078,9 +2067,7 @@ export async function createMusicTrackDraftEvent( @@ -2078,9 +2067,7 @@ export async function createMusicTrackDraftEvent(
tags.push(...emojiTags)
tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
return setDraftEventCache({
kind: ExtendedKind.MUSIC_TRACK,
@ -2103,6 +2090,7 @@ export async function createLongFormArticleDraftEvent( @@ -2103,6 +2090,7 @@ export async function createLongFormArticleDraftEvent(
topics?: string[]
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
} = {}
@ -2143,9 +2131,7 @@ export async function createLongFormArticleDraftEvent( @@ -2143,9 +2131,7 @@ export async function createLongFormArticleDraftEvent(
tags.push(...generateImetaTags(images))
}
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -2179,6 +2165,7 @@ export async function createWikiArticleDraftEvent( @@ -2179,6 +2165,7 @@ export async function createWikiArticleDraftEvent(
topics?: string[]
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
}
@ -2208,9 +2195,7 @@ export async function createWikiArticleDraftEvent( @@ -2208,9 +2195,7 @@ export async function createWikiArticleDraftEvent(
}
tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -2236,6 +2221,7 @@ export async function createNostrSpecificationDraftEvent( @@ -2236,6 +2221,7 @@ export async function createNostrSpecificationDraftEvent(
topics?: string[]
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
}
@ -2267,9 +2253,7 @@ export async function createNostrSpecificationDraftEvent( @@ -2267,9 +2253,7 @@ export async function createNostrSpecificationDraftEvent(
}
tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))
@ -2294,6 +2278,7 @@ export async function createPublicationContentDraftEvent( @@ -2294,6 +2278,7 @@ export async function createPublicationContentDraftEvent(
topics?: string[]
addClientTag?: boolean
isNsfw?: boolean
contentWarningLabel?: string
addExpirationTag?: boolean
expirationMonths?: number
}
@ -2323,9 +2308,7 @@ export async function createPublicationContentDraftEvent( @@ -2323,9 +2308,7 @@ export async function createPublicationContentDraftEvent(
}
tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.isNsfw) {
tags.push(buildNsfwTag())
}
appendContentWarningTagIfNeeded(tags, options)
if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths))

1
src/services/post-editor-cache.service.ts

@ -10,6 +10,7 @@ const PERSIST_DEBOUNCE_MS = 5_000 @@ -10,6 +10,7 @@ const PERSIST_DEBOUNCE_MS = 5_000
type TPostSettings = {
isNsfw?: boolean
contentWarningLabel?: string
isPoll?: boolean
pollCreateData?: TPollCreateData
addClientTag?: boolean

Loading…
Cancel
Save