diff --git a/client/src/components/Home.tsx b/client/src/components/Home.tsx index 206f9d7..46d6448 100644 --- a/client/src/components/Home.tsx +++ b/client/src/components/Home.tsx @@ -47,7 +47,7 @@ const Home = () => {
{filteredAndSortedEvents.map((event, index) => ( - + ))}
diff --git a/client/src/components/Modals/LinkPreview.tsx b/client/src/components/Modals/LinkPreview.tsx new file mode 100644 index 0000000..b6aab13 --- /dev/null +++ b/client/src/components/Modals/LinkPreview.tsx @@ -0,0 +1,53 @@ +import { getLinkPreview } from 'link-preview-js'; +import { useState, useEffect } from 'react'; + + +const LinkModal = ({ key, url }: { key: string, url: string}) => { + const [linkPreview, setLinkPreview] = useState(null); + + useEffect(() => { + const fetchLinkPreview = async () => { + getLinkPreview(url) + .then((preview) => setLinkPreview(preview as LinkPreview)) + .catch((error) => console.error(error)); + }; + + fetchLinkPreview(); + }, [url]); + + if (!linkPreview) { + return <>; // or some loading state + } + + return ( +
+ + {linkPreview.title} +
+ {linkPreview.title} +
+
+
+ ); +}; + +interface LinkPreview { + url: string; + title: string; + siteName?: string; + description?: string; + mediaType: string; + contentType?: string; + images: string[]; + videos: { + url?: string; + secureUrl?: string; + type?: string; + width?: string; + height?: string; + [key: string]: any; + }[]; + [key: string]: any; + } + +export default LinkModal; \ No newline at end of file diff --git a/client/src/components/PostCard/QuoteEmbed.tsx b/client/src/components/Modals/QuoteEmbed.tsx similarity index 96% rename from client/src/components/PostCard/QuoteEmbed.tsx rename to client/src/components/Modals/QuoteEmbed.tsx index 4119e9a..d47bea5 100644 --- a/client/src/components/PostCard/QuoteEmbed.tsx +++ b/client/src/components/Modals/QuoteEmbed.tsx @@ -1,4 +1,4 @@ -import CardContainer from './CardContainer'; +import CardContainer from '../PostCard/CardContainer'; import { FolderIcon } from '@heroicons/react/24/outline'; import { parseContent } from '../../utils/content'; import { Event } from 'nostr-tools'; @@ -82,7 +82,7 @@ const QuoteEmbed = ({ event, metadata }: { event: Event, metadata: Event | null} }, [comment]); return ( -
+
diff --git a/client/src/components/Modals/TextModal.tsx b/client/src/components/Modals/TextModal.tsx new file mode 100644 index 0000000..1ed91c9 --- /dev/null +++ b/client/src/components/Modals/TextModal.tsx @@ -0,0 +1,65 @@ +import QuoteEmbed from "./QuoteEmbed"; +import { getLinkPreview } from 'link-preview-js'; +import { Event } from 'nostr-tools'; +import { useEffect, useState } from "react"; +import { getMetadata, uniqBy } from '../../utils/utils'; +import { subNoteOnce } from '../../utils/subscriptions'; +import { nip19 } from "nostr-tools"; +import LinkModal from "./LinkPreview"; + +const ContentPreview = ({ key, comment }: { key: string, comment: string }) => { + const [finalComment, setFinalComment] = useState(comment) + const [quoteEvents, setQuoteEvents] = useState([]); // Initialize state + const [isExpanded, setIsExpanded] = useState(false); + const [url, setUrl] = useState('') + + // Define your callback function for subGlobalFeed + const onEvent = (event: Event, relay: string) => { + setQuoteEvents((prevEvents) => [...prevEvents, event]); + console.log(event.id + ' ' + event.kind + ' ' + event.tags); + }; + + useEffect(() => { + const urls = comment.match(/\bhttps?:\/\/\S+/gi); + if (urls && urls.length > 0) { + setUrl(url) + setFinalComment(finalComment.replace(url, '').trim()) + } + + const match = comment.match(/\bnostr:([a-z0-9]+)/i); + const nostrQuoteID = match && match[1]; + if (nostrQuoteID && nostrQuoteID.length > 0) { + let id_to_hex = String(nip19.decode(nostrQuoteID as string).data); + subNoteOnce(id_to_hex, onEvent); + setFinalComment(finalComment.replace('nostr:'+nostrQuoteID, '').trim()) + } + }, [comment]); + + const getMetadataEvent = (event: Event) => { + const metadataEvent = quoteEvents.find(e => e.pubkey === event.pubkey && e.kind === 0); + if (metadataEvent) { + return metadataEvent; + } + return null; + } + + return ( +
+ {isExpanded ? finalComment : finalComment.slice(0, 240)} + {finalComment.length > 240 && ( + + )} + {url !== '' && ( + + )} + {quoteEvents[0] && quoteEvents.length > 0 && ( + + )} +
+ ); + } + + export default ContentPreview; + \ No newline at end of file diff --git a/client/src/components/PostCard/PostCard.tsx b/client/src/components/PostCard/PostCard.tsx index 5636f1d..11f0971 100644 --- a/client/src/components/PostCard/PostCard.tsx +++ b/client/src/components/PostCard/PostCard.tsx @@ -3,12 +3,8 @@ import { FolderIcon } from '@heroicons/react/24/outline'; import { parseContent } from '../../utils/content'; import { Event } from 'nostr-tools'; import { nip19 } from 'nostr-tools'; -import { useEffect, useState } from 'react'; -import { subNote } from '../../utils/subscriptions'; import { getMetadata, uniqBy } from '../../utils/utils'; -import { getLinkPreview } from 'link-preview-js'; -import { subNoteOnce } from '../../utils/subscriptions'; -import QuoteEmbed from './QuoteEmbed'; +import ContentPreview from'../Modals/TextModal'; const colorCombos = [ 'from-red-400 to-yellow-500', @@ -42,9 +38,9 @@ const getColorFromHash = (id: string, colors: string[]): string => { const timeAgo = (unixTime: number) => { const seconds = Math.floor((new Date().getTime() / 1000) - unixTime); - + if (seconds < 60) return `now`; - + const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}m`; @@ -58,132 +54,56 @@ const timeAgo = (unixTime: number) => { return `${weeks}w`; }; -const PostCard = ({ event, metadata, replyCount }: { event: Event, metadata: Event | null, replyCount: number}) => { - // Replace 10 with the actual number of comments for each post - const numberOfComments = 10; - let { comment, file } = parseContent(event); - const colorCombo = getColorFromHash(event.pubkey, colorCombos); - const [isExpanded, setIsExpanded] = useState(false); - const truncatedComment = comment.slice(0, 240); +const PostCard = ({ key, event, metadata, replyCount }: { key: string, event: Event, metadata: Event | null, replyCount: number }) => { + let { comment, file } = parseContent(event); + const colorCombo = getColorFromHash(event.pubkey, colorCombos); - let metadataParsed = null; - if (metadata !== null) { - metadataParsed = getMetadata(metadata); - } - - const [linkPreview, setLinkPreview] = useState(null); - const [quoteEvents, setQuoteEvents] = useState([]); // Initialize state - - // Define your callback function for subGlobalFeed - const onEvent = (event: Event, relay: string) => { - setQuoteEvents((prevEvents) => [...prevEvents, event]); - console.log(event.id + ' ' + event.kind + ' ' + event.tags); - }; - - useEffect(() => { - const urls = comment.match(/\bhttps?:\/\/\S+/gi); - if (urls && urls.length > 0) { - getLinkPreview(urls[0]) - .then((preview) => setLinkPreview(preview as LinkPreview)) - .catch((error) => console.error(error)); - } - - const match = comment.match(/\bnostr:([a-z0-9]+)/i); - const nostrQuoteID = match && match[1]; - if (nostrQuoteID && nostrQuoteID.length > 0) { - let id_to_hex = String(nip19.decode(nostrQuoteID as string).data); - subNoteOnce(id_to_hex, onEvent); - - comment = comment.replace(/\bnostr:[a-z0-9]+\b/i, '').trim(); - } - }, [comment]); - - const getMetadataEvent = (event: Event) => { - const metadataEvent = quoteEvents.find(e => e.pubkey === event.pubkey && e.kind === 0); - if (metadataEvent) { - return metadataEvent; - } - return null; + let metadataParsed = null; + if (metadata !== null) { + metadataParsed = getMetadata(metadata); } - + return ( <> -
-
-
- {metadataParsed ? - <> - -
{metadataParsed.name}
- - : - <> -
-
Anonymous
- - } +
); }; -interface LinkPreview { - url: string; - title: string; - siteName?: string; - description?: string; - mediaType: string; - contentType?: string; - images: string[]; - videos: { - url?: string; - secureUrl?: string; - type?: string; - width?: string; - height?: string; - [key: string]: any; - }[]; - [key: string]: any; -} - export default PostCard; \ No newline at end of file