From cd50d2a86277bee57510d5feeab91b099b593be5 Mon Sep 17 00:00:00 2001 From: Michael Peter Date: Thu, 20 Jul 2023 17:56:32 +0000 Subject: [PATCH] generate opengraph previews for components --- package.json | 2 + pnpm-lock.yaml | 33 +++++ .../widget/[componentName].tsx | 114 +++++++++++++++--- 3 files changed, 130 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 66dfddd0c..e8afbad76 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,9 @@ "react-dom": "18.2.0", "react-hook-form": "^7.43.9", "react-singleton-hook": "^3.1.1", + "remark": "^14.0.3", "rudder-sdk-js": "^2.36.0", + "strip-markdown": "^5.0.1", "styled-components": "^5.3.6", "typescript": "5.0.4", "zustand": "^4.3.7" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2aa692b5c..27a8d7f22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,9 +151,15 @@ dependencies: react-singleton-hook: specifier: ^3.1.1 version: 3.4.0(react-dom@18.2.0)(react@18.2.0) + remark: + specifier: ^14.0.3 + version: 14.0.3 rudder-sdk-js: specifier: ^2.36.0 version: 2.36.0 + strip-markdown: + specifier: ^5.0.1 + version: 5.0.1 styled-components: specifier: ^5.3.6 version: 5.3.10(react-dom@18.2.0)(react-is@17.0.2)(react@18.2.0) @@ -8510,6 +8516,25 @@ packages: unified: 10.1.2 dev: false + /remark-stringify@10.0.3: + resolution: {integrity: sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==} + dependencies: + '@types/mdast': 3.0.11 + mdast-util-to-markdown: 1.5.0 + unified: 10.1.2 + dev: false + + /remark@14.0.3: + resolution: {integrity: sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==} + dependencies: + '@types/mdast': 3.0.11 + remark-parse: 10.0.1 + remark-stringify: 10.0.3 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + dev: false + /remove-trailing-slash@0.1.1: resolution: {integrity: sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==} dev: false @@ -8878,6 +8903,14 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + /strip-markdown@5.0.1: + resolution: {integrity: sha512-IvoKZrXtWAnlEjRfDlT3yRtGRvpX3RSg+nwAHONmshpSCoxgjZV2xX9ZYvEmwupmYobJtws9oDdTwLUu/5PoMQ==} + dependencies: + '@types/mdast': 3.0.11 + '@types/unist': 2.0.6 + unified: 10.1.2 + dev: false + /sturdy-websocket@0.1.12: resolution: {integrity: sha512-PA7h8LdjaMoIlC5HAwLVzae4raGWgyroscV4oUpEiTtEFINcNa47/CKYT3e98o+FfsJgrclI2pYpaJrz0aaoew==} dependencies: diff --git a/src/pages/[componentAccountId]/widget/[componentName].tsx b/src/pages/[componentAccountId]/widget/[componentName].tsx index f7ca2d54d..d52634bf6 100644 --- a/src/pages/[componentAccountId]/widget/[componentName].tsx +++ b/src/pages/[componentAccountId]/widget/[componentName].tsx @@ -1,6 +1,10 @@ +import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; +import { remark } from 'remark'; +import strip from 'strip-markdown'; +import { MetaTags } from '@/components/MetaTags'; import { VmComponent } from '@/components/vm/VmComponent'; import { useBosComponents } from '@/hooks/useBosComponents'; import { useDefaultLayout } from '@/hooks/useLayout'; @@ -8,7 +12,76 @@ import { useAuthStore } from '@/stores/auth'; import { useCurrentComponentStore } from '@/stores/current-component'; import type { NextPageWithLayout } from '@/utils/types'; -const ViewComponentPage: NextPageWithLayout = () => { +type ComponentMetaPreview = { + title: string; + description: string; + imageUrl: string | null; +}; + +type ComponentPayload = Record }>; +type ComponentMetadata = { + name: string; + description: string; + linktree: { + website: string; + }; + image: ImageData; + tags: Record; +}; + +type ImageData = { + ipfs_cid?: string; +}; + +function returnImageUrl(data: ImageData | undefined) { + if (data?.ipfs_cid) { + return `https://i.near.social/large/https://ipfs.near.social/ipfs/${data.ipfs_cid}`; + } + return null; +} + +async function fetchPreviewData(accountId: string, componentName: string): Promise { + const response = await fetch(`https://api.near.social/get?keys=${accountId}/widget/${componentName}/**`); + const responseData: ComponentPayload = await response.json(); + const metadata = responseData[accountId]?.widget?.[componentName]?.metadata; + + if (!metadata) { + return null; + } + + const strippedDescriptionVFile = await remark().use(strip).process(metadata.description); + // recommended conversion from remark docs + const strippedDescription = String(strippedDescriptionVFile); + + return { + title: `${metadata.name} by ${accountId} on BOS`, + description: strippedDescription, + imageUrl: returnImageUrl(metadata.image), + }; +} + +export const getServerSideProps: GetServerSideProps<{ + meta: ComponentMetaPreview | null; +}> = async ({ params }) => { + const componentAccountId = params?.componentAccountId; + const componentName = params?.componentName; + + if (typeof componentAccountId !== 'string' || typeof componentName !== 'string') { + return { + notFound: true, + }; + } + + const meta = await fetchPreviewData(componentAccountId, componentName); + + return { + props: { + meta, + }, + }; +}; + +const ViewComponentPage: NextPageWithLayout = ({ meta }: InferGetServerSidePropsType) => { const router = useRouter(); const setComponentSrc = useCurrentComponentStore((store) => store.setSrc); const componentSrc = `${router.query.componentAccountId}/widget/${router.query.componentName}`; @@ -25,27 +98,30 @@ const ViewComponentPage: NextPageWithLayout = () => { }, [router.query]); return ( -
-
-
- + {meta && } +
+
+
+ > + +
-
+ ); };