|
|
|
@ -6,6 +6,8 @@ import Wikilink from '@/components/UniversalContent/Wikilink' |
|
|
|
import { BookstrContent } from '@/components/Bookstr' |
|
|
|
import { BookstrContent } from '@/components/Bookstr' |
|
|
|
import WebPreview from '@/components/WebPreview' |
|
|
|
import WebPreview from '@/components/WebPreview' |
|
|
|
import SpotifyEmbeddedPlayer from '@/components/SpotifyEmbeddedPlayer' |
|
|
|
import SpotifyEmbeddedPlayer from '@/components/SpotifyEmbeddedPlayer' |
|
|
|
|
|
|
|
import FountainEmbeddedPlayer from '@/components/FountainEmbeddedPlayer' |
|
|
|
|
|
|
|
import WavlakeEmbeddedPlayer from '@/components/WavlakeEmbeddedPlayer' |
|
|
|
import ZapStreamLiveEventEmbed from '@/components/ZapStreamLiveEventEmbed' |
|
|
|
import ZapStreamLiveEventEmbed from '@/components/ZapStreamLiveEventEmbed' |
|
|
|
import YoutubeEmbeddedPlayer from '@/components/YoutubeEmbeddedPlayer' |
|
|
|
import YoutubeEmbeddedPlayer from '@/components/YoutubeEmbeddedPlayer' |
|
|
|
import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata' |
|
|
|
import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata' |
|
|
|
@ -33,11 +35,15 @@ import { |
|
|
|
ExtendedKind, |
|
|
|
ExtendedKind, |
|
|
|
isNip52CalendarCardKind, |
|
|
|
isNip52CalendarCardKind, |
|
|
|
SPOTIFY_OPEN_URL_REGEX, |
|
|
|
SPOTIFY_OPEN_URL_REGEX, |
|
|
|
|
|
|
|
FOUNTAIN_OPEN_URL_REGEX, |
|
|
|
|
|
|
|
WAVLAKE_OPEN_URL_REGEX, |
|
|
|
WS_URL_REGEX, |
|
|
|
WS_URL_REGEX, |
|
|
|
YOUTUBE_URL_REGEX, |
|
|
|
YOUTUBE_URL_REGEX, |
|
|
|
ZAP_STREAM_WATCH_URL_REGEX |
|
|
|
ZAP_STREAM_WATCH_URL_REGEX |
|
|
|
} from '@/constants' |
|
|
|
} from '@/constants' |
|
|
|
import { isSpotifyOpenUrl } from '@/lib/spotify-url' |
|
|
|
import { isSpotifyOpenUrl } from '@/lib/spotify-url' |
|
|
|
|
|
|
|
import { isFountainOpenUrl } from '@/lib/fountain-url' |
|
|
|
|
|
|
|
import { isWavlakeOpenUrl } from '@/lib/wavlake-url' |
|
|
|
import { canonicalZapStreamWatchUrl, isZapStreamWatchUrl } from '@/lib/zap-stream-url' |
|
|
|
import { canonicalZapStreamWatchUrl, isZapStreamWatchUrl } from '@/lib/zap-stream-url' |
|
|
|
import { isEmbeddableYoutubeUrl } from '@/lib/youtube-url' |
|
|
|
import { isEmbeddableYoutubeUrl } from '@/lib/youtube-url' |
|
|
|
import { EMOJI_SHORT_CODE_REGEX, NOSTR_URI_INLINE_REGEX } from '@/lib/content-patterns' |
|
|
|
import { EMOJI_SHORT_CODE_REGEX, NOSTR_URI_INLINE_REGEX } from '@/lib/content-patterns' |
|
|
|
@ -431,6 +437,14 @@ function isSpotifyUrl(url: string): boolean { |
|
|
|
return regex.test(url) |
|
|
|
return regex.test(url) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isWavlakeUrl(url: string): boolean { |
|
|
|
|
|
|
|
return isWavlakeOpenUrl(url) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isFountainUrl(url: string): boolean { |
|
|
|
|
|
|
|
return isFountainOpenUrl(url) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function isZapStreamUrl(url: string): boolean { |
|
|
|
function isZapStreamUrl(url: string): boolean { |
|
|
|
const flags = ZAP_STREAM_WATCH_URL_REGEX.flags.replace('g', '') |
|
|
|
const flags = ZAP_STREAM_WATCH_URL_REGEX.flags.replace('g', '') |
|
|
|
const regex = new RegExp(ZAP_STREAM_WATCH_URL_REGEX.source, flags) |
|
|
|
const regex = new RegExp(ZAP_STREAM_WATCH_URL_REGEX.source, flags) |
|
|
|
@ -1309,6 +1323,61 @@ function parseMarkdownContentLegacy( |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const wavlakeUrlMatches = Array.from(content.matchAll(WAVLAKE_OPEN_URL_REGEX)) |
|
|
|
|
|
|
|
wavlakeUrlMatches.forEach((match) => { |
|
|
|
|
|
|
|
if (match.index !== undefined) { |
|
|
|
|
|
|
|
const url = match[0] |
|
|
|
|
|
|
|
const start = match.index |
|
|
|
|
|
|
|
const end = match.index + match[0].length |
|
|
|
|
|
|
|
const isInMarkdown = patterns.some( |
|
|
|
|
|
|
|
(p) => |
|
|
|
|
|
|
|
(p.type === 'markdown-link' || |
|
|
|
|
|
|
|
p.type === 'markdown-image-link' || |
|
|
|
|
|
|
|
p.type === 'markdown-image' || |
|
|
|
|
|
|
|
p.type === 'youtube-url' || |
|
|
|
|
|
|
|
p.type === 'spotify-url') && |
|
|
|
|
|
|
|
start >= p.index && |
|
|
|
|
|
|
|
start < p.end |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
if (!isInMarkdown && !isWithinBlockPattern(start, end, blockPatterns) && isWavlakeUrl(url)) { |
|
|
|
|
|
|
|
patterns.push({ |
|
|
|
|
|
|
|
index: start, |
|
|
|
|
|
|
|
end: end, |
|
|
|
|
|
|
|
type: 'wavlake-url', |
|
|
|
|
|
|
|
data: { url } |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const fountainUrlMatches = Array.from(content.matchAll(FOUNTAIN_OPEN_URL_REGEX)) |
|
|
|
|
|
|
|
fountainUrlMatches.forEach((match) => { |
|
|
|
|
|
|
|
if (match.index !== undefined) { |
|
|
|
|
|
|
|
const url = match[0] |
|
|
|
|
|
|
|
const start = match.index |
|
|
|
|
|
|
|
const end = match.index + match[0].length |
|
|
|
|
|
|
|
const isInMarkdown = patterns.some( |
|
|
|
|
|
|
|
(p) => |
|
|
|
|
|
|
|
(p.type === 'markdown-link' || |
|
|
|
|
|
|
|
p.type === 'markdown-image-link' || |
|
|
|
|
|
|
|
p.type === 'markdown-image' || |
|
|
|
|
|
|
|
p.type === 'youtube-url' || |
|
|
|
|
|
|
|
p.type === 'spotify-url' || |
|
|
|
|
|
|
|
p.type === 'wavlake-url') && |
|
|
|
|
|
|
|
start >= p.index && |
|
|
|
|
|
|
|
start < p.end |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
if (!isInMarkdown && !isWithinBlockPattern(start, end, blockPatterns) && isFountainUrl(url)) { |
|
|
|
|
|
|
|
patterns.push({ |
|
|
|
|
|
|
|
index: start, |
|
|
|
|
|
|
|
end: end, |
|
|
|
|
|
|
|
type: 'fountain-url', |
|
|
|
|
|
|
|
data: { url } |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
const zapstreamUrlMatches = Array.from(content.matchAll(ZAP_STREAM_WATCH_URL_REGEX)) |
|
|
|
const zapstreamUrlMatches = Array.from(content.matchAll(ZAP_STREAM_WATCH_URL_REGEX)) |
|
|
|
zapstreamUrlMatches.forEach((match) => { |
|
|
|
zapstreamUrlMatches.forEach((match) => { |
|
|
|
if (match.index !== undefined) { |
|
|
|
if (match.index !== undefined) { |
|
|
|
@ -1321,7 +1390,9 @@ function parseMarkdownContentLegacy( |
|
|
|
p.type === 'markdown-image-link' || |
|
|
|
p.type === 'markdown-image-link' || |
|
|
|
p.type === 'markdown-image' || |
|
|
|
p.type === 'markdown-image' || |
|
|
|
p.type === 'youtube-url' || |
|
|
|
p.type === 'youtube-url' || |
|
|
|
p.type === 'spotify-url') && |
|
|
|
p.type === 'spotify-url' || |
|
|
|
|
|
|
|
p.type === 'wavlake-url' || |
|
|
|
|
|
|
|
p.type === 'fountain-url') && |
|
|
|
start >= p.index && |
|
|
|
start >= p.index && |
|
|
|
start < p.end |
|
|
|
start < p.end |
|
|
|
) |
|
|
|
) |
|
|
|
@ -1345,7 +1416,7 @@ function parseMarkdownContentLegacy( |
|
|
|
const end = match.index + match[0].length |
|
|
|
const end = match.index + match[0].length |
|
|
|
// Only add if not already covered by a markdown link/image-link/image or YouTube URL and not in block pattern
|
|
|
|
// Only add if not already covered by a markdown link/image-link/image or YouTube URL and not in block pattern
|
|
|
|
const isInMarkdown = patterns.some(p =>
|
|
|
|
const isInMarkdown = patterns.some(p =>
|
|
|
|
(p.type === 'markdown-link' || p.type === 'markdown-image-link' || p.type === 'markdown-image' || p.type === 'youtube-url' || p.type === 'spotify-url' || p.type === 'zapstream-url') &&
|
|
|
|
(p.type === 'markdown-link' || p.type === 'markdown-image-link' || p.type === 'markdown-image' || p.type === 'youtube-url' || p.type === 'spotify-url' || p.type === 'wavlake-url' || p.type === 'fountain-url' || p.type === 'zapstream-url') && |
|
|
|
start >= p.index &&
|
|
|
|
start >= p.index &&
|
|
|
|
start < p.end |
|
|
|
start < p.end |
|
|
|
) |
|
|
|
) |
|
|
|
@ -2328,6 +2399,20 @@ function parseMarkdownContentLegacy( |
|
|
|
<SpotifyEmbeddedPlayer url={url} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
<SpotifyEmbeddedPlayer url={url} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
} else if (pattern.type === 'wavlake-url') { |
|
|
|
|
|
|
|
const { url } = pattern.data |
|
|
|
|
|
|
|
parts.push( |
|
|
|
|
|
|
|
<div key={`wavlake-url-${patternIdx}`} className="my-2"> |
|
|
|
|
|
|
|
<WavlakeEmbeddedPlayer url={url} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} else if (pattern.type === 'fountain-url') { |
|
|
|
|
|
|
|
const { url } = pattern.data |
|
|
|
|
|
|
|
parts.push( |
|
|
|
|
|
|
|
<div key={`fountain-url-${patternIdx}`} className="my-2"> |
|
|
|
|
|
|
|
<FountainEmbeddedPlayer url={url} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
} else if (pattern.type === 'zapstream-url') { |
|
|
|
} else if (pattern.type === 'zapstream-url') { |
|
|
|
const { url } = pattern.data |
|
|
|
const { url } = pattern.data |
|
|
|
parts.push( |
|
|
|
parts.push( |
|
|
|
@ -3776,6 +3861,20 @@ function parseMarkdownContentMarked( |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (isWavlakeUrl(cleaned)) { |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<div key={`${key}-line-wavlake-${lineIdx}`} className="my-2"> |
|
|
|
|
|
|
|
<WavlakeEmbeddedPlayer url={cleaned} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (isFountainUrl(cleaned)) { |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<div key={`${key}-line-fountain-${lineIdx}`} className="my-2"> |
|
|
|
|
|
|
|
<FountainEmbeddedPlayer url={cleaned} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
if (isZapStreamUrl(cleaned)) { |
|
|
|
if (isZapStreamUrl(cleaned)) { |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div key={`${key}-line-zapstream-${lineIdx}`} className="my-2"> |
|
|
|
<div key={`${key}-line-zapstream-${lineIdx}`} className="my-2"> |
|
|
|
@ -3949,6 +4048,20 @@ function parseMarkdownContentMarked( |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (isWavlakeUrl(cleaned)) { |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<div key={`${key}-wavlake-url`} className="my-2"> |
|
|
|
|
|
|
|
<WavlakeEmbeddedPlayer url={cleaned} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (isFountainUrl(cleaned)) { |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<div key={`${key}-fountain-url`} className="my-2"> |
|
|
|
|
|
|
|
<FountainEmbeddedPlayer url={cleaned} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
if (isZapStreamUrl(cleaned)) { |
|
|
|
if (isZapStreamUrl(cleaned)) { |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div key={`${key}-zapstream-url`} className="my-2"> |
|
|
|
<div key={`${key}-zapstream-url`} className="my-2"> |
|
|
|
@ -4023,6 +4136,20 @@ function parseMarkdownContentMarked( |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (soleHref && isWavlakeUrl(soleHref)) { |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<div key={`${key}-wavlake-sole-link`} className="my-2"> |
|
|
|
|
|
|
|
<WavlakeEmbeddedPlayer url={soleHref} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (soleHref && isFountainUrl(soleHref)) { |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<div key={`${key}-fountain-sole-link`} className="my-2"> |
|
|
|
|
|
|
|
<FountainEmbeddedPlayer url={soleHref} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
if (soleHref && isZapStreamUrl(soleHref)) { |
|
|
|
if (soleHref && isZapStreamUrl(soleHref)) { |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div key={`${key}-zapstream-sole-link`} className="my-2"> |
|
|
|
<div key={`${key}-zapstream-sole-link`} className="my-2"> |
|
|
|
@ -4112,6 +4239,24 @@ function parseMarkdownContentMarked( |
|
|
|
) |
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (cleaned && isWavlakeUrl(cleaned)) { |
|
|
|
|
|
|
|
flushInlineSegment(segmentIdx++) |
|
|
|
|
|
|
|
nodes.push( |
|
|
|
|
|
|
|
<div key={`${key}-inline-wavlake-with-media-${idx}`} className="my-2"> |
|
|
|
|
|
|
|
<WavlakeEmbeddedPlayer url={cleaned} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (cleaned && isFountainUrl(cleaned)) { |
|
|
|
|
|
|
|
flushInlineSegment(segmentIdx++) |
|
|
|
|
|
|
|
nodes.push( |
|
|
|
|
|
|
|
<div key={`${key}-inline-fountain-with-media-${idx}`} className="my-2"> |
|
|
|
|
|
|
|
<FountainEmbeddedPlayer url={cleaned} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
if (cleaned && isZapStreamUrl(cleaned)) { |
|
|
|
if (cleaned && isZapStreamUrl(cleaned)) { |
|
|
|
flushInlineSegment(segmentIdx++) |
|
|
|
flushInlineSegment(segmentIdx++) |
|
|
|
nodes.push( |
|
|
|
nodes.push( |
|
|
|
@ -5463,6 +5608,48 @@ export default function MarkdownArticle({ |
|
|
|
return spotifyUrls |
|
|
|
return spotifyUrls |
|
|
|
}, [event.id, JSON.stringify(event.tags)]) |
|
|
|
}, [event.id, JSON.stringify(event.tags)]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const tagWavlakeUrls = useMemo(() => { |
|
|
|
|
|
|
|
const wavlakeUrls: string[] = [] |
|
|
|
|
|
|
|
const seenUrls = new Set<string>() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
event.tags |
|
|
|
|
|
|
|
.filter((tag) => tag[0] === 'r' && tag[1]) |
|
|
|
|
|
|
|
.forEach((tag) => { |
|
|
|
|
|
|
|
const url = tag[1]! |
|
|
|
|
|
|
|
if (!url.startsWith('http://') && !url.startsWith('https://')) return |
|
|
|
|
|
|
|
if (!isWavlakeUrl(url)) return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
|
|
|
|
if (cleaned && !seenUrls.has(cleaned)) { |
|
|
|
|
|
|
|
wavlakeUrls.push(cleaned) |
|
|
|
|
|
|
|
seenUrls.add(cleaned) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return wavlakeUrls |
|
|
|
|
|
|
|
}, [event.id, JSON.stringify(event.tags)]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const tagFountainUrls = useMemo(() => { |
|
|
|
|
|
|
|
const fountainUrls: string[] = [] |
|
|
|
|
|
|
|
const seenUrls = new Set<string>() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
event.tags |
|
|
|
|
|
|
|
.filter((tag) => tag[0] === 'r' && tag[1]) |
|
|
|
|
|
|
|
.forEach((tag) => { |
|
|
|
|
|
|
|
const url = tag[1]! |
|
|
|
|
|
|
|
if (!url.startsWith('http://') && !url.startsWith('https://')) return |
|
|
|
|
|
|
|
if (!isFountainUrl(url)) return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
|
|
|
|
if (cleaned && !seenUrls.has(cleaned)) { |
|
|
|
|
|
|
|
fountainUrls.push(cleaned) |
|
|
|
|
|
|
|
seenUrls.add(cleaned) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return fountainUrls |
|
|
|
|
|
|
|
}, [event.id, JSON.stringify(event.tags)]) |
|
|
|
|
|
|
|
|
|
|
|
const tagZapStreamUrls = useMemo(() => { |
|
|
|
const tagZapStreamUrls = useMemo(() => { |
|
|
|
const zapUrls: string[] = [] |
|
|
|
const zapUrls: string[] = [] |
|
|
|
const seenUrls = new Set<string>() |
|
|
|
const seenUrls = new Set<string>() |
|
|
|
@ -5497,6 +5684,8 @@ export default function MarkdownArticle({ |
|
|
|
if (isImage(url) || isMedia(url) || isHlsPlaylistUrl(url) || isBlossomBudBlobUrl(url)) return |
|
|
|
if (isImage(url) || isMedia(url) || isHlsPlaylistUrl(url) || isBlossomBudBlobUrl(url)) return |
|
|
|
if (isYouTubeUrl(url)) return // Exclude YouTube URLs
|
|
|
|
if (isYouTubeUrl(url)) return // Exclude YouTube URLs
|
|
|
|
if (isSpotifyUrl(url)) return |
|
|
|
if (isSpotifyUrl(url)) return |
|
|
|
|
|
|
|
if (isWavlakeUrl(url)) return |
|
|
|
|
|
|
|
if (isFountainUrl(url)) return |
|
|
|
if (isZapStreamWatchUrl(url)) return |
|
|
|
if (isZapStreamWatchUrl(url)) return |
|
|
|
|
|
|
|
|
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
@ -5659,6 +5848,34 @@ export default function MarkdownArticle({ |
|
|
|
return urls |
|
|
|
return urls |
|
|
|
}, [event.content]) |
|
|
|
}, [event.content]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const wavlakeUrlsInContent = useMemo(() => { |
|
|
|
|
|
|
|
const urls = new Set<string>() |
|
|
|
|
|
|
|
const urlRegex = /https?:\/\/[^\s<>"']+/g |
|
|
|
|
|
|
|
let match |
|
|
|
|
|
|
|
while ((match = urlRegex.exec(event.content)) !== null) { |
|
|
|
|
|
|
|
const url = match[0] |
|
|
|
|
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
|
|
|
|
if (cleaned && isWavlakeUrl(cleaned)) { |
|
|
|
|
|
|
|
urls.add(cleaned) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return urls |
|
|
|
|
|
|
|
}, [event.content]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const fountainUrlsInContent = useMemo(() => { |
|
|
|
|
|
|
|
const urls = new Set<string>() |
|
|
|
|
|
|
|
const urlRegex = /https?:\/\/[^\s<>"']+/g |
|
|
|
|
|
|
|
let match |
|
|
|
|
|
|
|
while ((match = urlRegex.exec(event.content)) !== null) { |
|
|
|
|
|
|
|
const url = match[0] |
|
|
|
|
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
|
|
|
|
if (cleaned && isFountainUrl(cleaned)) { |
|
|
|
|
|
|
|
urls.add(cleaned) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return urls |
|
|
|
|
|
|
|
}, [event.content]) |
|
|
|
|
|
|
|
|
|
|
|
const zapstreamUrlsInContent = useMemo(() => { |
|
|
|
const zapstreamUrlsInContent = useMemo(() => { |
|
|
|
const urls = new Set<string>() |
|
|
|
const urls = new Set<string>() |
|
|
|
const urlRegex = /https?:\/\/[^\s<>"']+/g |
|
|
|
const urlRegex = /https?:\/\/[^\s<>"']+/g |
|
|
|
@ -5688,6 +5905,8 @@ export default function MarkdownArticle({ |
|
|
|
!isHlsPlaylistUrl(url) && |
|
|
|
!isHlsPlaylistUrl(url) && |
|
|
|
!isYouTubeUrl(url) && |
|
|
|
!isYouTubeUrl(url) && |
|
|
|
!isSpotifyUrl(url) && |
|
|
|
!isSpotifyUrl(url) && |
|
|
|
|
|
|
|
!isWavlakeUrl(url) && |
|
|
|
|
|
|
|
!isFountainUrl(url) && |
|
|
|
!isZapStreamWatchUrl(url) |
|
|
|
!isZapStreamWatchUrl(url) |
|
|
|
) { |
|
|
|
) { |
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
@ -5755,6 +5974,20 @@ export default function MarkdownArticle({ |
|
|
|
}) |
|
|
|
}) |
|
|
|
}, [tagSpotifyUrls, spotifyUrlsInContent]) |
|
|
|
}, [tagSpotifyUrls, spotifyUrlsInContent]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const leftoverTagWavlakeUrls = useMemo(() => { |
|
|
|
|
|
|
|
return tagWavlakeUrls.filter((url) => { |
|
|
|
|
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
|
|
|
|
return cleaned && !wavlakeUrlsInContent.has(cleaned) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
}, [tagWavlakeUrls, wavlakeUrlsInContent]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const leftoverTagFountainUrls = useMemo(() => { |
|
|
|
|
|
|
|
return tagFountainUrls.filter((url) => { |
|
|
|
|
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
|
|
|
|
return cleaned && !fountainUrlsInContent.has(cleaned) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
}, [tagFountainUrls, fountainUrlsInContent]) |
|
|
|
|
|
|
|
|
|
|
|
const leftoverTagZapStreamUrls = useMemo(() => { |
|
|
|
const leftoverTagZapStreamUrls = useMemo(() => { |
|
|
|
return tagZapStreamUrls.filter((canon) => !zapstreamUrlsInContent.has(canon)) |
|
|
|
return tagZapStreamUrls.filter((canon) => !zapstreamUrlsInContent.has(canon)) |
|
|
|
}, [tagZapStreamUrls, zapstreamUrlsInContent]) |
|
|
|
}, [tagZapStreamUrls, zapstreamUrlsInContent]) |
|
|
|
@ -6193,6 +6426,32 @@ export default function MarkdownArticle({ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
)} |
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{leftoverTagWavlakeUrls.length > 0 && ( |
|
|
|
|
|
|
|
<div className="space-y-4 mb-6"> |
|
|
|
|
|
|
|
{leftoverTagWavlakeUrls.map((url) => { |
|
|
|
|
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<div key={`tag-wavlake-${cleaned}`} className="my-2"> |
|
|
|
|
|
|
|
<WavlakeEmbeddedPlayer url={url} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
})} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{leftoverTagFountainUrls.length > 0 && ( |
|
|
|
|
|
|
|
<div className="space-y-4 mb-6"> |
|
|
|
|
|
|
|
{leftoverTagFountainUrls.map((url) => { |
|
|
|
|
|
|
|
const cleaned = cleanUrl(url) |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<div key={`tag-fountain-${cleaned}`} className="my-2"> |
|
|
|
|
|
|
|
<FountainEmbeddedPlayer url={url} className="max-w-[400px]" mustLoad={!lazyMedia} /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
})} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
{leftoverTagZapStreamUrls.length > 0 && ( |
|
|
|
{leftoverTagZapStreamUrls.length > 0 && ( |
|
|
|
<div className="space-y-4 mb-6"> |
|
|
|
<div className="space-y-4 mb-6"> |
|
|
|
{leftoverTagZapStreamUrls.map((url) => ( |
|
|
|
{leftoverTagZapStreamUrls.map((url) => ( |
|
|
|
|