feat: add imeta tags

This commit is contained in:
codytseng
2025-01-08 00:44:52 +08:00
parent 3e609f7236
commit 50658f6d91
3 changed files with 41 additions and 16 deletions

View File

@@ -9,6 +9,7 @@ import {
createPictureNoteDraftEvent, createPictureNoteDraftEvent,
createShortTextNoteDraftEvent createShortTextNoteDraftEvent
} from '@/lib/draft-event' } from '@/lib/draft-event'
import { extractImagesFromContent } from '@/lib/event'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service' import client from '@/services/client.service'
import { ChevronDown, LoaderCircle } from 'lucide-react' import { ChevronDown, LoaderCircle } from 'lucide-react'
@@ -18,7 +19,6 @@ import { useTranslation } from 'react-i18next'
import Mentions from './Mentions' import Mentions from './Mentions'
import Preview from './Preview' import Preview from './Preview'
import Uploader from './Uploader' import Uploader from './Uploader'
import { extractImagesFromContent } from '@/lib/event'
export default function PostContent({ export default function PostContent({
defaultContent = '', defaultContent = '',
@@ -33,6 +33,7 @@ export default function PostContent({
const { toast } = useToast() const { toast } = useToast()
const { publish, checkLogin } = useNostr() const { publish, checkLogin } = useNostr()
const [content, setContent] = useState(defaultContent) const [content, setContent] = useState(defaultContent)
const [pictureInfos, setPictureInfos] = useState<{ url: string; tags: string[][] }[]>([])
const [posting, setPosting] = useState(false) const [posting, setPosting] = useState(false)
const [showMoreOptions, setShowMoreOptions] = useState(false) const [showMoreOptions, setShowMoreOptions] = useState(false)
const [addClientTag, setAddClientTag] = useState(false) const [addClientTag, setAddClientTag] = useState(false)
@@ -73,10 +74,10 @@ export default function PostContent({
} }
const draftEvent = const draftEvent =
isPictureNote && !parentEvent && hasImages isPictureNote && !parentEvent && hasImages
? await createPictureNoteDraftEvent(content, { addClientTag }) ? await createPictureNoteDraftEvent(content, pictureInfos, { addClientTag })
: parentEvent && parentEvent.kind !== kinds.ShortTextNote : parentEvent && parentEvent.kind !== kinds.ShortTextNote
? await createCommentDraftEvent(content, parentEvent, { addClientTag }) ? await createCommentDraftEvent(content, parentEvent, pictureInfos, { addClientTag })
: await createShortTextNoteDraftEvent(content, { : await createShortTextNoteDraftEvent(content, pictureInfos, {
parentEvent, parentEvent,
addClientTag addClientTag
}) })
@@ -127,7 +128,12 @@ export default function PostContent({
{content && <Preview content={content} />} {content && <Preview content={content} />}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Uploader setContent={setContent} /> <Uploader
onUploadSuccess={({ url, tags }) => {
setPictureInfos((prev) => [...prev, { url, tags }])
setContent((prev) => `${prev}\n${url}`)
}}
/>
<Button <Button
variant="link" variant="link"
className="text-foreground gap-0 px-0" className="text-foreground gap-0 px-0"

View File

@@ -6,9 +6,9 @@ import { useRef, useState } from 'react'
import { z } from 'zod' import { z } from 'zod'
export default function Uploader({ export default function Uploader({
setContent onUploadSuccess
}: { }: {
setContent: React.Dispatch<React.SetStateAction<string>> onUploadSuccess: ({ url, tags }: { url: string; tags: string[][] }) => void
}) { }) {
const [uploading, setUploading] = useState(false) const [uploading, setUploading] = useState(false)
const { signHttpAuth } = useNostr() const { signHttpAuth } = useNostr()
@@ -42,7 +42,7 @@ export default function Uploader({
const tags = z.array(z.array(z.string())).parse(data.nip94_event?.tags ?? []) const tags = z.array(z.array(z.string())).parse(data.nip94_event?.tags ?? [])
const imageUrl = tags.find(([tagName]) => tagName === 'url')?.[1] const imageUrl = tags.find(([tagName]) => tagName === 'url')?.[1]
if (imageUrl) { if (imageUrl) {
setContent((prevContent) => `${prevContent}\n${imageUrl}`) onUploadSuccess({ url: imageUrl, tags })
} else { } else {
throw new Error('No image url found') throw new Error('No image url found')
} }

View File

@@ -47,6 +47,7 @@ export function createRepostDraftEvent(event: Event): TDraftEvent {
export async function createShortTextNoteDraftEvent( export async function createShortTextNoteDraftEvent(
content: string, content: string,
pictureInfos: { url: string; tags: string[][] }[],
options: { options: {
parentEvent?: Event parentEvent?: Event
addClientTag?: boolean addClientTag?: boolean
@@ -70,6 +71,11 @@ export async function createShortTextNoteDraftEvent(
tags.push(['e', parentEventId, '', 'reply']) tags.push(['e', parentEventId, '', 'reply'])
} }
const { images } = extractImagesFromContent(content)
if (images && images.length) {
tags.push(...generateImetaTags(images, pictureInfos))
}
if (options.addClientTag) { if (options.addClientTag) {
tags.push(['client', 'jumble']) tags.push(['client', 'jumble'])
} }
@@ -98,6 +104,7 @@ export function createRelaySetDraftEvent(relaySet: TRelaySet): TDraftEvent {
export async function createPictureNoteDraftEvent( export async function createPictureNoteDraftEvent(
content: string, content: string,
pictureInfos: { url: string; tags: string[][] }[],
options: { options: {
addClientTag?: boolean addClientTag?: boolean
} = {} } = {}
@@ -109,8 +116,7 @@ export async function createPictureNoteDraftEvent(
throw new Error('No images found in content') throw new Error('No images found in content')
} }
const tags = images const tags = generateImetaTags(images, pictureInfos)
.map((image) => ['imeta', `url ${image}`])
.concat(pubkeys.map((pubkey) => ['p', pubkey])) .concat(pubkeys.map((pubkey) => ['p', pubkey]))
.concat(quoteEventIds.map((eventId) => ['q', eventId])) .concat(quoteEventIds.map((eventId) => ['q', eventId]))
.concat(hashtags.map((hashtag) => ['t', hashtag])) .concat(hashtags.map((hashtag) => ['t', hashtag]))
@@ -130,6 +136,7 @@ export async function createPictureNoteDraftEvent(
export async function createCommentDraftEvent( export async function createCommentDraftEvent(
content: string, content: string,
parentEvent: Event, parentEvent: Event,
pictureInfos: { url: string; tags: string[][] }[],
options: { options: {
addClientTag?: boolean addClientTag?: boolean
} = {} } = {}
@@ -153,12 +160,15 @@ export async function createCommentDraftEvent(
['e', parentEventId], ['e', parentEventId],
['k', parentEventKind.toString()], ['k', parentEventKind.toString()],
['p', parentEventPubkey] ['p', parentEventPubkey]
].concat( ]
pubkeys .concat(pubkeys.map((pubkey) => ['p', pubkey]))
.map((pubkey) => ['p', pubkey]) .concat(quoteEventIds.map((eventId) => ['q', eventId]))
.concat(quoteEventIds.map((eventId) => ['q', eventId])) .concat(hashtags.map((hashtag) => ['t', hashtag]))
.concat(hashtags.map((hashtag) => ['t', hashtag]))
) const { images } = extractImagesFromContent(content)
if (images && images.length) {
tags.push(...generateImetaTags(images, pictureInfos))
}
if (options.addClientTag) { if (options.addClientTag) {
tags.push(['client', 'jumble']) tags.push(['client', 'jumble'])
@@ -171,3 +181,12 @@ export async function createCommentDraftEvent(
created_at: dayjs().unix() created_at: dayjs().unix()
} }
} }
function generateImetaTags(imageUrls: string[], pictureInfos: { url: string; tags: string[][] }[]) {
return imageUrls.map((imageUrl) => {
const pictureInfo = pictureInfos.find((info) => info.url === imageUrl)
return pictureInfo
? ['imeta', ...pictureInfo.tags.map(([n, v]) => `${n} ${v}`)]
: ['imeta', `url ${imageUrl}`]
})
}