From c4bc67410c0ffb0d33d266af7a8ff1963e75ec1f Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Sun, 4 Jun 2023 17:20:47 +0700 Subject: [PATCH] add link preview --- src-tauri/tauri.conf.json | 2 +- src/app/note/components/kind1.tsx | 6 +++ src/app/note/components/mentions/note.tsx | 38 ++------------- src/app/note/components/parent.tsx | 38 ++------------- src/app/note/components/preview/image.tsx | 7 +-- src/app/note/components/preview/link.tsx | 39 ++++++++++++++++ src/renderer/index.css | 2 +- src/stores/constants.tsx | 2 + src/utils/hooks/useEvent.tsx | 57 +++++++++++++++++++++++ src/utils/hooks/useOpenGraph.tsx | 28 +++++++++++ src/utils/parser.tsx | 16 +++---- 11 files changed, 154 insertions(+), 81 deletions(-) create mode 100644 src/app/note/components/preview/link.tsx create mode 100644 src/utils/hooks/useEvent.tsx create mode 100644 src/utils/hooks/useOpenGraph.tsx diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 6491c50c..8bceed65 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -22,7 +22,7 @@ "http": { "all": true, "request": true, - "scope": ["https://rbr.bio/*", "https://void.cat/*", "https://metadata.lume.nu/*"] + "scope": ["https://void.cat/*", "https://skrape.dev/*"] }, "fs": { "all": false, diff --git a/src/app/note/components/kind1.tsx b/src/app/note/components/kind1.tsx index f7c5344d..7492c4a7 100644 --- a/src/app/note/components/kind1.tsx +++ b/src/app/note/components/kind1.tsx @@ -1,3 +1,4 @@ +import { LinkPreview } from "./preview/link"; import { MentionNote } from "@app/note/components/mentions/note"; import { MentionUser } from "@app/note/components/mentions/user"; import { ImagePreview } from "@app/note/components/preview/image"; @@ -32,6 +33,11 @@ export function Kind1({ ) : ( <> )} + {Array.isArray(content.links) && content.links.length ? ( + + ) : ( + <> + )} {Array.isArray(content.notes) && content.notes.length ? ( content.notes.map((note: string) => ( diff --git a/src/app/note/components/mentions/note.tsx b/src/app/note/components/mentions/note.tsx index 736baefd..72fd80f9 100644 --- a/src/app/note/components/mentions/note.tsx +++ b/src/app/note/components/mentions/note.tsx @@ -3,43 +3,15 @@ import { Kind1063 } from "@app/note/components/kind1063"; import { NoteSkeleton } from "@app/note/components/skeleton"; import { NoteQuoteUser } from "@app/note/components/user/quote"; import { NoteWrapper } from "@app/note/components/wrapper"; -import { RelayContext } from "@shared/relayProvider"; -import { READONLY_RELAYS } from "@stores/constants"; +import { useEvent } from "@utils/hooks/useEvent"; import { noteParser } from "@utils/parser"; -import { memo, useContext } from "react"; -import useSWRSubscription from "swr/subscription"; +import { memo } from "react"; export const MentionNote = memo(function MentionNote({ id }: { id: string }) { - const pool: any = useContext(RelayContext); + const data = useEvent(id); - const { data, error } = useSWRSubscription( - id ? id : null, - (key, { next }) => { - const unsubscribe = pool.subscribe( - [ - { - ids: [key], - }, - ], - READONLY_RELAYS, - (event: any) => { - next(null, event); - }, - undefined, - undefined, - { - unsubscribeOnEose: true, - }, - ); - - return () => { - unsubscribe(); - }; - }, - ); - - const kind1 = !error && data?.kind === 1 ? noteParser(data) : null; - const kind1063 = !error && data?.kind === 1063 ? data.tags : null; + const kind1 = data?.kind === 1 ? noteParser(data) : null; + const kind1063 = data?.kind === 1063 ? data.tags : null; return ( { - const unsubscribe = pool.subscribe( - [ - { - ids: [key], - }, - ], - READONLY_RELAYS, - (event: any) => { - next(null, event); - }, - undefined, - undefined, - { - unsubscribeOnEose: true, - }, - ); - - return () => { - unsubscribe(); - }; - }, - ); - - const kind1 = !error && data?.kind === 1 ? noteParser(data) : null; - const kind1063 = !error && data?.kind === 1063 ? data.tags : null; + const kind1 = data?.kind === 1 ? noteParser(data) : null; + const kind1063 = data?.kind === 1063 ? data.tags : null; return (
diff --git a/src/app/note/components/preview/image.tsx b/src/app/note/components/preview/image.tsx index 7706595e..b03bb8dc 100644 --- a/src/app/note/components/preview/image.tsx +++ b/src/app/note/components/preview/image.tsx @@ -1,12 +1,9 @@ import { Image } from "@shared/image"; -import useEmblaCarousel from "embla-carousel-react"; export function ImagePreview({ urls }: { urls: string[] }) { - const [emblaRef] = useEmblaCarousel(); - return ( -
-
+
+
{urls.map((url) => ( + ); +} diff --git a/src/renderer/index.css b/src/renderer/index.css index 381f494d..f32f5bbb 100644 --- a/src/renderer/index.css +++ b/src/renderer/index.css @@ -15,7 +15,7 @@ button { } .markdown { - @apply prose prose-zinc max-w-none select-text break-words dark:prose-invert prose-p:m-0 prose-p:leading-tight prose-a:text-[15px] prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-500 hover:prose-a:text-fuchsia-600 prose-ol:mb-1 prose-ul:mb-1 prose-li:text-[15px] prose-li:leading-tight prose-blockquote:m-0 prose-hr:mx-0 prose-hr:my-2; + @apply prose prose-zinc max-w-none select-text break-words dark:prose-invert prose-p:m-0 prose-p:leading-tight prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-500 hover:prose-a:text-fuchsia-600 prose-ol:mb-1 prose-ul:mb-1 prose-li:leading-tight prose-blockquote:m-0 prose-hr:mx-0 prose-hr:my-2; } /* For Webkit-based browsers (Chrome, Safari and Opera) */ diff --git a/src/stores/constants.tsx b/src/stores/constants.tsx index d3d96bde..fb2ca099 100644 --- a/src/stores/constants.tsx +++ b/src/stores/constants.tsx @@ -5,6 +5,8 @@ export const DEFAULT_AVATAR = "https://void.cat/d/KmypFh2fBdYCEvyJrPiN89.webp"; export const DEFAULT_CHANNEL_BANNER = "https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg"; +export const OPENGRAPH_KEY = "9EJG4SY-19Q4M5J-H8R29C9-091XPCC"; + // read-only relay list export const READONLY_RELAYS = [ "wss://welcome.nostr.wine", diff --git a/src/utils/hooks/useEvent.tsx b/src/utils/hooks/useEvent.tsx new file mode 100644 index 00000000..db9ee6cb --- /dev/null +++ b/src/utils/hooks/useEvent.tsx @@ -0,0 +1,57 @@ +import { RelayContext } from "@shared/relayProvider"; +import { useActiveAccount } from "@stores/accounts"; +import { READONLY_RELAYS } from "@stores/constants"; +import { createNote, getNoteByID } from "@utils/storage"; +import { getParentID } from "@utils/transform"; +import { useContext } from "react"; +import useSWR from "swr"; +import useSWRSubscription from "swr/subscription"; + +const fetcher = ([, id]) => getNoteByID(id); + +export function useEvent(id: string) { + const pool: any = useContext(RelayContext); + const account = useActiveAccount((state: any) => state.account); + + const { data: cache } = useSWR(["event", id], fetcher); + const { data: newest } = useSWRSubscription( + !cache ? id : null, + (key, { next }) => { + const unsubscribe = pool.subscribe( + [ + { + ids: [key], + }, + ], + READONLY_RELAYS, + (event: any) => { + const parentID = getParentID(event.tags, event.id); + // insert event to local database + createNote( + event.id, + account.id, + event.pubkey, + event.kind, + event.tags, + event.content, + event.created_at, + parentID, + ); + // update state + next(null, event); + }, + undefined, + undefined, + { + unsubscribeOnEose: true, + }, + ); + + return () => { + unsubscribe(); + }; + }, + ); + + return cache ? cache : newest; +} diff --git a/src/utils/hooks/useOpenGraph.tsx b/src/utils/hooks/useOpenGraph.tsx new file mode 100644 index 00000000..932b1252 --- /dev/null +++ b/src/utils/hooks/useOpenGraph.tsx @@ -0,0 +1,28 @@ +import { OPENGRAPH_KEY } from "@stores/constants"; +import { fetch } from "@tauri-apps/api/http"; +import useSWR from "swr"; + +const fetcher = async (url: string) => { + const result = await fetch(url, { + method: "GET", + timeout: 20, + }); + if (result.ok) { + return result.data; + } else { + return null; + } +}; + +export function useOpenGraph(url: string) { + const { data, error, isLoading } = useSWR( + `https://skrape.dev/api/opengraph/?url=${url}&key=${OPENGRAPH_KEY}`, + fetcher, + ); + + return { + data: data, + error: error, + isLoading: isLoading, + }; +} diff --git a/src/utils/parser.tsx b/src/utils/parser.tsx index b8c48c40..23adb3d4 100644 --- a/src/utils/parser.tsx +++ b/src/utils/parser.tsx @@ -13,12 +13,14 @@ export function noteParser(event: Event) { notes: string[]; images: string[]; videos: string[]; + links: string[]; } = { original: event.content, parsed: event.content, notes: [], images: [], videos: [], + links: [], }; // handle media @@ -36,10 +38,15 @@ export function noteParser(event: Event) { content.videos.push(url); // remove url from original content content.parsed = content.parsed.replace(url, ""); + } else { + // push to store + content.links.push(url); + // remove url from original content + content.parsed = content.parsed.replace(url, ""); } }); - // map hashtag to em + // map hashtag to link content.original.match(/#(\w+)(?!:\/\/)/g)?.forEach((item) => { content.parsed = content.parsed.replace(item, `[${item}](/search/${item})`); }); @@ -47,13 +54,6 @@ export function noteParser(event: Event) { // handle nostr mention references.forEach((item) => { const profile = item.profile; - const event = item.event; - - if (event) { - content.notes.push(event.id); - content.parsed = content.parsed.replace(item.text, ""); - } - if (profile) { content.parsed = content.parsed.replace(item.text, `*${profile.pubkey}*`); }