diff --git a/src/components/Content/index.tsx b/src/components/Content/index.tsx
index baeb4a2b..e1454d46 100644
--- a/src/components/Content/index.tsx
+++ b/src/components/Content/index.tsx
@@ -3,13 +3,10 @@ import {
EmbeddedEmojiParser,
EmbeddedEventParser,
EmbeddedHashtagParser,
- EmbeddedImageParser,
EmbeddedLNInvoiceParser,
- EmbeddedMediaParser,
EmbeddedMentionParser,
- EmbeddedNormalUrlParser,
+ EmbeddedUrlParser,
EmbeddedWebsocketUrlParser,
- EmbeddedYoutubeParser,
parseContent
} from '@/lib/content-parser'
import { getImageInfosFromEvent } from '@/lib/event'
@@ -40,10 +37,7 @@ const Content = memo(
if (!_content) return null
const nodes = parseContent(_content, [
- EmbeddedYoutubeParser,
- EmbeddedImageParser,
- EmbeddedMediaParser,
- EmbeddedNormalUrlParser,
+ EmbeddedUrlParser,
EmbeddedLNInvoiceParser,
EmbeddedWebsocketUrlParser,
EmbeddedEventParser,
diff --git a/src/components/ContentPreview/Content.tsx b/src/components/ContentPreview/Content.tsx
index 371d0469..82531903 100644
--- a/src/components/ContentPreview/Content.tsx
+++ b/src/components/ContentPreview/Content.tsx
@@ -1,9 +1,8 @@
import {
EmbeddedEmojiParser,
EmbeddedEventParser,
- EmbeddedImageParser,
EmbeddedMentionParser,
- EmbeddedMediaParser,
+ EmbeddedUrlParser,
parseContent
} from '@/lib/content-parser'
import { cn } from '@/lib/utils'
@@ -25,8 +24,7 @@ export default function Content({
const { t } = useTranslation()
const nodes = useMemo(() => {
return parseContent(content, [
- EmbeddedImageParser,
- EmbeddedMediaParser,
+ EmbeddedUrlParser,
EmbeddedEventParser,
EmbeddedMentionParser,
EmbeddedEmojiParser
@@ -36,9 +34,6 @@ export default function Content({
return (
{nodes.map((node, index) => {
- if (node.type === 'text') {
- return node.data
- }
if (node.type === 'image' || node.type === 'images') {
return index > 0 ? ` [${t('image')}]` : `[${t('image')}]`
}
@@ -57,6 +52,7 @@ export default function Content({
if (!emoji) return node.data
return
}
+ return node.data
})}
)
diff --git a/src/components/ProfileAbout/index.tsx b/src/components/ProfileAbout/index.tsx
index 1cf9e9ba..8d14cf87 100644
--- a/src/components/ProfileAbout/index.tsx
+++ b/src/components/ProfileAbout/index.tsx
@@ -1,21 +1,21 @@
import {
EmbeddedHashtagParser,
EmbeddedMentionParser,
- EmbeddedNormalUrlParser,
+ EmbeddedUrlParser,
EmbeddedWebsocketUrlParser,
parseContent
} from '@/lib/content-parser'
import { detectLanguage } from '@/lib/utils'
+import { useTranslationService } from '@/providers/TranslationServiceProvider'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
+import { toast } from 'sonner'
import {
EmbeddedHashtag,
EmbeddedMention,
EmbeddedNormalUrl,
EmbeddedWebsocketUrl
} from '../Embedded'
-import { useTranslationService } from '@/providers/TranslationServiceProvider'
-import { toast } from 'sonner'
export default function ProfileAbout({ about, className }: { about?: string; className?: string }) {
const { t, i18n } = useTranslation()
@@ -33,14 +33,11 @@ export default function ProfileAbout({ about, className }: { about?: string; cla
const nodes = parseContent(translatedAbout ?? about, [
EmbeddedWebsocketUrlParser,
- EmbeddedNormalUrlParser,
+ EmbeddedUrlParser,
EmbeddedHashtagParser,
EmbeddedMentionParser
])
return nodes.map((node, index) => {
- if (node.type === 'text') {
- return node.data
- }
if (node.type === 'url') {
return
}
@@ -53,6 +50,7 @@ export default function ProfileAbout({ about, className }: { about?: string; cla
if (node.type === 'mention') {
return
}
+ return node.data
})
}, [about, translatedAbout])
diff --git a/src/lib/content-parser.ts b/src/lib/content-parser.ts
index 10349c38..24c758aa 100644
--- a/src/lib/content-parser.ts
+++ b/src/lib/content-parser.ts
@@ -3,13 +3,12 @@ import {
EMBEDDED_MENTION_REGEX,
EMOJI_SHORT_CODE_REGEX,
HASHTAG_REGEX,
- IMAGE_REGEX,
LN_INVOICE_REGEX,
URL_REGEX,
- MEDIA_REGEX,
WS_URL_REGEX,
YOUTUBE_URL_REGEX
} from '@/constants'
+import { isImage, isMedia } from './url'
export type TEmbeddedNodeType =
| 'text'
@@ -36,7 +35,9 @@ export type TEmbeddedNode =
data: string[]
}
-type TContentParser = { type: Exclude; regex: RegExp }
+type TContentParser =
+ | { type: Exclude; regex: RegExp }
+ | ((content: string) => TEmbeddedNode[])
export const EmbeddedHashtagParser: TContentParser = {
type: 'hashtag',
@@ -58,31 +59,11 @@ export const EmbeddedEventParser: TContentParser = {
regex: EMBEDDED_EVENT_REGEX
}
-export const EmbeddedImageParser: TContentParser = {
- type: 'image',
- regex: IMAGE_REGEX
-}
-
-export const EmbeddedMediaParser: TContentParser = {
- type: 'media',
- regex: MEDIA_REGEX
-}
-
export const EmbeddedWebsocketUrlParser: TContentParser = {
type: 'websocket-url',
regex: WS_URL_REGEX
}
-export const EmbeddedNormalUrlParser: TContentParser = {
- type: 'url',
- regex: URL_REGEX
-}
-
-export const EmbeddedYoutubeParser: TContentParser = {
- type: 'youtube',
- regex: YOUTUBE_URL_REGEX
-}
-
export const EmbeddedEmojiParser: TContentParser = {
type: 'emoji',
regex: EMOJI_SHORT_CODE_REGEX
@@ -93,6 +74,48 @@ export const EmbeddedLNInvoiceParser: TContentParser = {
regex: LN_INVOICE_REGEX
}
+export const EmbeddedUrlParser: TContentParser = (content: string) => {
+ const matches = content.matchAll(URL_REGEX)
+ const result: TEmbeddedNode[] = []
+ let lastIndex = 0
+ for (const match of matches) {
+ const matchStart = match.index!
+ // Add text before the match
+ if (matchStart > lastIndex) {
+ result.push({
+ type: 'text',
+ data: content.slice(lastIndex, matchStart)
+ })
+ }
+
+ const url = match[0]
+ let type: TEmbeddedNodeType = 'url'
+ if (isImage(url)) {
+ type = 'image'
+ } else if (isMedia(url)) {
+ type = 'media'
+ } else if (YOUTUBE_URL_REGEX.test(url)) {
+ type = 'youtube'
+ }
+
+ // Add the match as specific type
+ result.push({
+ type,
+ data: url
+ })
+
+ lastIndex = matchStart + url.length
+ }
+ // Add text after the last match
+ if (lastIndex < content.length) {
+ result.push({
+ type: 'text',
+ data: content.slice(lastIndex)
+ })
+ }
+ return result
+}
+
export function parseContent(content: string, parsers: TContentParser[]) {
let nodes: TEmbeddedNode[] = [{ type: 'text', data: content.trim() }]
@@ -100,6 +123,11 @@ export function parseContent(content: string, parsers: TContentParser[]) {
nodes = nodes
.flatMap((node) => {
if (node.type !== 'text') return [node]
+
+ if (typeof parser === 'function') {
+ return parser(node.data)
+ }
+
const matches = node.data.matchAll(parser.regex)
const result: TEmbeddedNode[] = []
let lastIndex = 0
diff --git a/src/lib/event-metadata.ts b/src/lib/event-metadata.ts
index 1a3e941e..5dc09399 100644
--- a/src/lib/event-metadata.ts
+++ b/src/lib/event-metadata.ts
@@ -70,7 +70,7 @@ export function getProfileFromEvent(event: Event) {
created_at: event.created_at
}
} catch (err) {
- console.error(err)
+ console.error(event.content, err)
return {
pubkey: event.pubkey,
npub: pubkeyToNpub(event.pubkey) ?? '',
diff --git a/src/lib/url.ts b/src/lib/url.ts
index 054c4cb5..bc723096 100644
--- a/src/lib/url.ts
+++ b/src/lib/url.ts
@@ -117,10 +117,22 @@ export function isImage(url: string) {
}
}
-export function isVideo(url: string) {
+export function isMedia(url: string) {
try {
- const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov']
- return videoExtensions.some((ext) => new URL(url).pathname.toLowerCase().endsWith(ext))
+ const mediaExtensions = [
+ '.mp4',
+ '.webm',
+ '.ogg',
+ '.mov',
+ '.mp3',
+ '.wav',
+ '.flac',
+ '.aac',
+ '.m4a',
+ '.opus',
+ '.wma'
+ ]
+ return mediaExtensions.some((ext) => new URL(url).pathname.toLowerCase().endsWith(ext))
} catch {
return false
}