Последняя версия моего веб-сайта построена на Next.js, в частности, на замечательном начальном блоге Tailwind/Next.js Тимоти Лина. Я немного изменил его, изменив цветовую схему, убрав такие компоненты, как аналитика, комментарии и некоторые другие, а также создав несколько новых страниц (например, мою страницу сейчас). В рамках этого процесса я хотел добавить в шаблон поддержку веб-упоминаний, интегрировав упоминания с Mastodon, Medium.com и других доступных источников.
Чтобы начать это, вам нужно войти в систему и создать учетную запись в webmention.io и Bridgy. Первый предоставляет вам пару метатегов, которые собирают веб-упоминания, второй связывает ваш сайт с социальными сетями.
После того, как вы добавили соответствующие теги из webmention.io, подключили нужные учетные записи к Bridgy и получили некоторые упоминания на этих сайтах, вы сможете получить доступ к этим упоминаниям через их API. Для моих целей (и ваших, если вы выберете тот же подход) это выглядит как следующий маршрут API Next.js:
import loadWebmentions from '@/lib/webmentions' export default async function handler(req, res) { const target = req.query.target const response = await loadWebmentions(target) res.json(response) }
Вы можете увидеть мои упоминания на живом маршруте здесь.
Я решил отображать упоминания о моих постах (бусты, на языке Mastodon), лайки и комментарии. Для бустов я отрисовываю счет, для лайков я отрисовываю аватарку, а для упоминаний я отрисовываю комментарий полностью. Компонент, который обрабатывает это, выглядит следующим образом:
import siteMetadata from '@/data/siteMetadata' import { Heart, Rocket } from '@/components/icons' import { Spin } from '@/components/Loading' import { useRouter } from 'next/router' import { useJson } from '@/hooks/useJson' import Link from 'next/link' import Image from 'next/image' import { formatDate } from '@/utils/formatters' const WebmentionsCore = () => { const { asPath } = useRouter() const { response, error } = useJson(`/api/webmentions?target=${siteMetadata.siteUrl}${asPath}`) const webmentions = response?.children const hasLikes = webmentions?.filter((mention) => mention['wm-property'] === 'like-of').length > 0 const hasComments = webmentions?.filter((mention) => mention['wm-property'] === 'in-reply-to').length > 0 const boostsCount = webmentions?.filter( (mention) => mention['wm-property'] === 'repost-of' || mention['wm-property'] === 'mention-of' ).length const hasBoosts = boostsCount > 0 const hasMention = hasLikes || hasComments || hasBoosts if (error) return null if (!response) return <Spin className="my-2 flex justify-center" /> const Boosts = () => { return ( <div className="flex flex-row items-center"> <div className="mr-2 h-5 w-5"> <Rocket /> </div> {` `} <span className="text-sm">{boostsCount}</span> </div> ) } const Likes = () => ( <> <div className="flex flex-row items-center"> <div className="mr-2 h-5 w-5"> <Heart /> </div> <ul className="ml-2 flex flex-row"> {webmentions?.map((mention) => { if (mention['wm-property'] === 'like-of') return ( <li key={mention['wm-id']} className="-ml-2"> <Link href={mention.url} target="_blank" rel="noopener noreferrer" > <Image className="h-10 w-10 rounded-full border border-primary-500 dark:border-gray-500" src={mention.author.photo} alt={mention.author.name} width="40" height="40" /> </Link> </li> ) })} </ul> </div> </> ) const Comments = () => { return ( <> {webmentions?.map((mention) => { if (mention['wm-property'] === 'in-reply-to') { return ( <Link className="border-bottom flex flex-row items-center border-gray-100 pb-4" key={mention['wm-id']} href={mention.url} target="_blank" rel="noopener noreferrer" > <Image className="h-12 w-12 rounded-full border border-primary-500 dark:border-gray-500" src={mention.author.photo} alt={mention.author.name} width="48" height="48" /> <div className="ml-3"> <p className="text-sm">{mention.content?.text}</p> <p className="mt-1 text-xs">{formatDate(mention.published)}</p> </div> </Link> ) } })} </> ) } return ( <> {hasMention ? ( <div className="text-gray-500 dark:text-gray-100"> <h4 className="pt-3 text-xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 md:text-2xl md:leading-10 "> Webmentions </h4> {hasBoosts ? ( <div className="pt-2 pb-4"> <Boosts /> </div> ) : null} {hasLikes ? ( <div className="pt-2 pb-4"> <Likes /> </div> ) : null} {hasComments ? ( <div className="pt-2 pb-4"> <Comments /> </div> ) : null} </div> ) : null} </> ) } export default WebmentionsCore
Мы получаем URL-адрес сообщения из фиксированного URL-адреса сайта в метаданных моего сайта, URI из маршрутизатора Next.js, объединяем их и передаем в качестве пути API к моему хуку useJson
, который обертывает useSWR
:
import { useEffect, useState } from 'react' import useSWR from 'swr' export const useJson = (url: string, props?: any) => { const [response, setResponse] = useState<any>({}) const fetcher = (url: string) => fetch(url) .then((res) => res.json()) .catch() const { data, error } = useSWR(url, fetcher, { fallbackData: props, refreshInterval: 30000 }) useEffect(() => { setResponse(data) }, [data, setResponse]) return { response, error, } }
Параметр target
сужает возвращаемые упоминания до тех, которые имеют отношение к текущему сообщению. Получив соответствующий ответ от службы, мы оцениваем данные, чтобы определить, какие типы упоминаний у нас есть, создаем компоненты JSX для их отображения и условно отображаем их на основе наличия соответствующих данных об упоминаниях.
Компонент WebmentionsCore
динамически загружается в каждую запись с использованием следующего родительского компонента:
import dynamic from 'next/dynamic' import { Spin } from '@/components/Loading' const Webmentions = dynamic(() => import('@/components/webmentions/WebmentionsCore'), { ssr: false, loading: () => <Spin className="my-2 flex justify-center" />, }) export default Webmentions
Окончательное отображение выглядит так:
Первоначально опубликовано на https://coryd.dev 18 февраля 2023 г.