Webmention 是 W3C 建议,最近一次发布是在 2017 年 1 月 12 日。 那么,什么是 Webmention 呢? 它被描述为…
[…] 当您在网站上提及某个 URL 时,通知该 URL 的一种简单方法。 从接收者的角度来看,这是一种在其他网站提及它时请求通知的方法。
简而言之,这是一种让网站知道它在某个地方被某人以某种方式提及的方式。 Webmention 规范还将其描述为网站让其他人知道它引用了他们的方式。 这基本上归结为您的网站是一个活跃的社交媒体渠道,从其他渠道(例如 Twitter、Instagram、Mastodon、Facebook 等)传达信息。
网站如何实施 Webmentions? 在某些情况下,例如 WordPress,这与安装几个插件一样简单。 其他情况可能并不那么简单,但仍然非常直接。 事实上,我们现在就来做吧!
这是我们的计划
- 声明一个接收 Webmentions 的端点
- 将社交媒体互动处理成 Webmentions
- 将这些提及纳入网站/应用程序
- 设置传出的 Webmentions
幸运的是,我们有一些服务可以使事情变得极其简单。 嗯,除了第三点,但嘿,这并不糟糕,我会逐步介绍我在自己的 atila.io 网站上是如何做的。
我的网站是一个服务器端博客,它是预渲染的,并使用 NextJS 编写。 我选择在客户端发出 Webmention 请求; 因此,它将轻松地在任何其他 React 应用程序中以及在任何其他 JavaScript 应用程序中只需很少的重构即可正常工作。
步骤 1:声明一个接收 Webmentions 的端点
为了拥有一个可用于接受 Webmentions 的端点,我们需要编写脚本并将其添加到自己的服务器中,或者使用像 Webmention.io 这样的服务(这就是我所做的)。
Webmention.io 是免费的,您只需要确认对注册域名的所有权。 验证可以通过多种方式进行。 我通过在我的网站中的链接(指向我的社交媒体个人资料)中添加 rel="me"
属性来进行验证。 它只需要一个这样的链接,但我继续为我的所有帐户都这样做了。
<a
href="https://twitter.com/atilafassina"
target="_blank"
rel="me noopener noreferrer"
>
@AtilaFassina
</a>
以这种方式进行验证,我们还需要确保在该 Twitter 个人资料中有一个指向我们网站的链接。 完成此操作后,我们可以返回 Webmention.io 并添加 URL。
这为我们提供了一个用于接受 Webmentions 的端点! 我们现在需要做的就是将其作为 <link>
标签连接到我们网页的 <head>
中,以便收集这些提及。
<head>
<link rel="webmention" href="https://webmention.io/{user}/webmention" />
<link rel="pingback" href="https://webmention.io/{user}/xmlrpc" />
<!-- ... -->
</head>
请记住,将 {user}
替换为您的 Webmention.io 用户名。
步骤 2:将社交媒体互动处理成 Webmentions
我们已经准备好让 Webmentions 开始流动! 但是等等,我们有一个小问题:实际上没有人使用它们。 我的意思是,我用,你用,Max Böck 用,Swyx 用,还有… 仅此而已。 所以,现在我们需要开始将所有这些有趣的社交媒体互动转换成 Webmentions。
猜猜看? 有一个很棒的免费服务可以做到这一点。 不过请注意:你最好开始 喜欢 IndieWeb,因为我们很快就会参与其中。
Bridgy 连接了我们所有联合的内容,并将它们转换成合适的 Webmentions,这样我们就可以使用它们。 通过 SSO,我们可以逐一整理好我们的每个个人资料。
步骤 3:将这些提及纳入网站/应用程序
现在轮到我们做一些繁重的工作了。 当然,第三方服务可以处理我们所有的数据,但使用它们并显示它们仍然由我们决定。
我们将将其分解成几个阶段。 首先,我们将获取 Webmentions 的数量。 然后,我们将获取这些提及本身。 然后,我们将这些数据连接到 NextJS(但您不必这样做),并显示它们。
获取提及的数量
type TMentionsCountResponse = {
count: number
type: {
like: number
mention: number
reply: number
repost: number
}
}
这是一个从 Webmention.io 端点获取的示例对象。 我对响应进行了格式化,使其更适合我们的需求。 我会在稍后介绍我是如何做到的,但以下是我们将获得的对象
type TMentionsCount = {
mentions: number
likes: number
total: number
}
端点位于
https://webmention.io/api/count.json?target=${post_url}
没有它,请求不会失败,但数据也不会来。 Max Böck 和 Swyx 都将喜欢与转发以及提及与回复组合在一起。 在 Twitter 中,它们是相似的。
const getMentionsCount = async (postURL: string): TMentionsCount => {
const resp = await fetch(
`https://webmention.io/api/count.json?target=${postURL}/`
)
const { type, count } = await resp.json()
return {
likes: type.like + type.repost,
mentions: type.mention + type.reply,
total: count,
}
}
获取实际的提及
在获取响应之前,请注意,响应是分页的,端点在查询中接受三个参数
page
:请求的页面per-page
:页面上要显示的提及数量target
:获取 Webmentions 的 URL
一旦我们点击 https://webmention.io/api/mentions
并传递这些参数,成功响应将是一个对象,它只有一个键 links
,这是一个包含与以下类型匹配的提及的 array
type TMention = {
source: string
verified: boolean
verified_date: string // date string
id: number
private: boolean
data: {
author: {
name: string
url: string
photo: string // url, hosted in webmention.io
}
url: string
name: string
content: string // encoded HTML
published: string // date string
published_ts: number // ms
}
activity: {
type: 'link' | 'reply' | 'repost' | 'like'
sentence: string // pure text, shortened
sentence_html: string // encoded html
}
target: string
}
以上数据足够显示我们网站上的类似评论的节列表。 以下是 TypeScript 中的 fetch 请求
const getMentions = async (
page: string,
postsPerPage: number,
postURL: string
): { links: TWebMention[] } => {
const resp = await fetch(
`https://webmention.io/api/mentions?page=${page}&per-page=${postsPerPage}&target=${postURL}`
)
const list = await resp.json()
return list.links
}
将所有内容连接到 NextJS
我们将暂时在 NextJS 中工作。 如果你没有使用 NextJS 甚至没有 Web 应用程序,也没关系。 我们已经拥有所有数据,因此那些没有在 NextJS 中工作的人可以简单地跳到步骤 4。 我们其他人将在那里与你汇合。
从版本 9.3.0 开始,NextJS 有三种不同的方法可以获取数据
getStaticProps
:在构建时获取数据getStaticPaths
:根据获取的数据指定要预渲染的动态路由getServerSideProps
:在每次请求时获取数据
现在是决定我们将在哪个时间点发出第一个获取提及的请求的时刻。 我们可以使用第一批提及在服务器上预渲染数据,或者我们可以将整个过程放到客户端。 我选择去客户端。
如果你也打算去客户端,我建议使用 SWR。 它是由 Vercel 团队构建的自定义钩子,它提供了良好的缓存、错误和加载状态——它甚至支持 React.Suspense
。
显示 Webmention 计数
许多博客在两个地方显示帖子上的评论数量:在博客文章的顶部(就像这个)以及在底部,就在评论列表的正上方。 让我们对 Webmentions 遵循相同的模式。
首先,让我们为计数创建一个组件
const MentionsCounter = ({ postUrl }) => {
const { t } = useTranslation()
// Setting a default value for `data` because I don't want a loading state
// otherwise you could set: if(!data) return <div>loading...</div>
const { data = {}, error } = useSWR(postUrl, getMentionsCount)
if (error) {
return <ErrorMessage>{t('common:errorWebmentions')}</ErrorMessage>
}
// The default values cover the loading state
const { likes = '-', mentions = '-' } = data
return (
<MentionCounter>
<li>
<Heart title="Likes" />
<CounterData>{Number.isNaN(likes) ? 0 : likes}</CounterData>
</li>
<li>
<Comment title="Mentions" />{' '}
<CounterData>{Number.isNaN(mentions) ? 0 : mentions}</CounterData>
</li>
</MentionCounter>
)
}
由于 SWR,即使我们使用 WebmentionsCounter
组件的两个实例,也只发出一个请求,并且它们都从相同的缓存中获益。
请随时查看我的源代码,了解发生了什么
WebmentionsCounter
(组件)getMentionsCount
(辅助函数)Post 布局组件
(我们使用组件的地方)
显示提及
现在我们已经放置了组件,是时候让所有社交汁液流动起来!
在撰写本文时,useSWRpages
尚未记录。再加上 webmention.io 端点在响应中不提供集合信息(即没有偏移量或总页数),我找不到在这里使用 SWR 的方法。
因此,我目前的实现使用一个状态来保存当前页,另一个状态来处理提及 array
,以及 useEffect
来处理请求。“加载更多”按钮在最后一次请求返回空数组后被禁用。
const Webmentions = ({ postUrl }) => {
const { t } = useTranslation()
const [page, setPage] = useState(0)
const [mentions, addMentions] = useState([])
useEffect(() => {
const fetchMentions = async () => {
const olderMentions = await getMentions(page, 50, postUrl)
addMentions((mentions) => [...mentions, ...olderMentions])
}
fetchMentions()
}, [page])
return (
<>
{mentions.map((mention, index) => (
<Mention key={mention.data.author.name + index}>
<AuthorAvatar src={mention.data.author.photo} lazy />
<MentionContent>
<MentionText
data={mention.data}
activity={mention.activity.type}
/>
</MentionContent>
</Mention>
))}
</MentionList>
{mentions.length > 0 && (
<MoreButton
type="button"
onClick={() => {
setPage(page + 1)
}}
>
{t('common:more')}
</MoreButton>
)}
</>
)
}
代码已简化,以便专注于本文主题。再次提醒,请随意查看完整的实现。
步骤 4:处理出站提及
感谢 Remy Sharp,处理从一个网站到其他网站的出站提及非常容易,并为每个可能的用例或偏好提供了选项。
最快速和最简单的方法是前往 Webmention.app,获取 API 令牌,并设置 Web 钩子。现在,如果您已将 RSS 提要到位,那么使用 IFTT 小程序甚至部署钩子,同样也很容易。
如果您想避免为此功能使用另一个第三方服务(我完全理解),Remy 已经开源了一个名为 wm 的 CLI 包,它可以作为构建后脚本运行。
但这还不够处理出站提及。为了让我们的提及包含的不仅仅是源 URL,我们需要将 微格式 添加到我们的信息中。微格式是关键,因为它是一种标准化的方式,使网站能够以 Webmention 启用网站可以消费的方式分发内容。
从最基本的意义上讲,微格式是标记中的一种基于类的表示法,它为每个部分提供额外的语义含义。在博客文章的情况下,我们将使用两种微格式。
h-entry
:帖子条目h-card
:帖子的作者
h-entry
所需的大多数信息通常都在页面的标题中,因此标题组件最终可能看起来像这样。
<header class="h-entry">
<!-- the post date and time -->
<time datetime="2020-04-22T00:00:00.000Z" class="dt-published">
2020-04-22
</time>
<!-- the post title -->
<h1 class="p-name">
Webmentions with NextJS
</h1>
</header>
就是这样。如果您使用的是 JSX,请记住用 className
替换 class
,datetime
是驼峰式大小写(dateTime
),并且可以使用新的 Date('2020-04-22').toISOString()
函数。
h-card
也是如此。在大多数情况下(像我一样),作者信息在文章下方。以下是我的页面页脚的外观。
<footer class="h-card">
<!-- the author name -->
<span class="p-author">Atila Fassina</span>
<!-- the authot image-->
<img
alt="Author’s photograph: Atila Fassina"
class="u-photo"
src="/images/internal-avatar.jpg"
lazy
/>
</footer>
现在,每当我们从这篇博客文章发送出站提及时,它都会将完整信息显示给接收者。
总结
我希望这篇文章能帮助您更多地了解 Webmentions(甚至了解 IndieWeb 整体),也许还能帮助您将此功能添加到您自己的网站或应用程序中。如果它做到了,请考虑将这篇文章分享到您的网络中。我将不胜感激!😉
参考资料
- 在静态网站上使用 Web Mentions(Max Böck)
- 客户端 Webmentions(Swyx)
- 发送出站 Webmentions(Remy Sharp)
- 您的第一个 Webmention(Aaron Parecki)
一个用于检查帖子中微格式标记的好工具是 https://indiewebify.me/
对于测试您的 Webmention 实现,请查看 https://webmention.rocks/
当然,非常棒的工具!非常感谢,凯文!
我已经注册了我的博客,并且正在尝试 webmention.io API,以查看我有哪些提及(理论上我的博客已经在 Twitter 上被提及了很多次),但 API 只返回了几个提及。看起来 brid.ly 只处理了一些最近的推文,而且只针对您自己的个人推文。我认为这有点令人失望。有没有办法获取我所有推文历史的 Web Mentions,并获取包含指向我网站链接的第三方推文?
不幸的是,我认为它只跟踪您在网站上设置 pingback
的时间段。据我所知,没有办法追溯地获取提及……
很棒的文章,我刚刚在我的网站上添加了 Webmentions,并写了一篇关于它的文章 https://gregbenner.life/you-dont-mention-webmentions/,其中介绍了如何在 gridsome.js 中使用它。