前言

初次看见webring

之前闲着没事干逛开往issues的时候看到了这么个站点:


当时这个按钮引起了我的注意:
引起我注意的按钮
这是个啥东西?友链接力?还是什么?于是我写了个邮件问问这位站长
这位站长给我的回复如下:
站长的回复
果然是类似于友链接力的,而且开往正是受到这个webring的启发
当时还以为那站点能加入的原因是简约,而且加入这个成员大多都是foreign,所以我就没怎么去了解

再次看见

后面闲着没事干,又去逛逛开往这波开往上大分,来到了他的第二人生这个站点
然后我就看到了这篇文章:


再加上我在那站点末尾看见了IndieWeb Webring的标志
才知道Hexo的博客原来也能加入IndieWeb webring

而加入IndieWeb webring的前提条件是:站点必须适配IndieWeb

前提条件

必须要有独立网站和个人域名,没错,就是独立网站和个人域名,其实刚开始看的时候我还以为我看错了,后来才发现我没看错,那官方文档就是这么写的

教程开始!

对IndieWeb的适配分为四个部分:IndieAuth、RelMeAuth和Microformats

IndieAuth(必选)&& RelMeAuth(必选)

这个教程其实很简单,就是在你的Github链接和邮箱链接对应的元素添加rel="me"的标签
并且去 https://indieauth.com/ 那边登录+获取IndieAuth代码放到head里面就行(后面那Web Sign-in form什么的不用添加)
也因此我才把这两个合并到一起

添加标签和代码:

修改[Blogroot]\themes\solitude\layout\includes\widgets\aside\asideInfoCard.pug,转到最后一行

1
2
3
4
5
6
7
            .card-info-social-icons.is-center
each value, label in theme.aside.my_card.information || {}
- var array = value.split('||')
- a.social-icon.u-url(class=array[2] href=url_for(trim(array[0])), title=label)
+ a.social-icon.u-url(class=array[2] href=url_for(trim(array[0])), title=label, rel="me")
i.solitude(class=array[1])


然后修改主题目录下的_config.yml或者博客目录下的_config.solitude.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Aside
# 侧边栏
aside:
# Information card
# 信息卡
my_card:
# social
# 社交信息图标
information:
Github: https://github.com/SinzMise || fab fa-github # 添加你的Github链接(必须!)
Email: mailto:email@sinzmise.top || fa fa-envelope # 添加你的邮件地址(必须!)

...

# Extend
# 扩展
extends:
# Insert in head
# 插入到 head
head:
- <link rel="authorization_endpoint" href="https://indieauth.com/auth">
- <link rel="token_endpoint" href="https://tokens.indieauth.com/token">

懒得写了,反正rel-'me'按照我上面说的来添加就可以
如果你想提前支持IndieAuth,就直接在</head>之前添加这两个就可以了:

1
2
<link rel="authorization_endpoint" href="https://indieauth.com/auth">
<link rel="token_endpoint" href="https://tokens.indieauth.com/token">


然后去 https://indieauth.com/ 这边登录你的账号

如果你用的是静态博客,那么在去登录之前你必须要给博客push上去!要不然提示找不到rel=me标签!


划到最下面,在Try It!这个地方输入你的网址
在这里输入网址
然后点击Sign-In会询问你用Github登录还是用邮箱登录
随便选一个登录之后看到这个就说明成功了:
成功提示
当然在此之前你可以去 https://indiewebify.me/validate-rel-me/ 那边检测
检测成功会提示这个:
检测成功的样子

Microformats(必选)

最难的地方来了
因为这个会引入大量的class标签
如果是别的主题魔改还好,如果像solitude这种文章标题在Header里面就很难改,而且一不小心就识别错误
而如果是Redifine这种使用了Tailwind CSS就更难,因为这个会在页面中留下许多h-开头的类名,导致部分 microformats 解析器解析出错误的数据
这也是为什么我这么讨厌Tailwind CSS

因此目前只有Solitude的魔改教程,而且只有必要的class,其它主题或者非Hexo博客可以参考一下wiki:
https://microformats.org/wiki/h-card
https://microformats.org/wiki/h-entry

h-card(个人信息)

修改[Blogroot]\themes\solitude\layout\includes\widgets\aside\asideInfoCard.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- .card-widget.card-info
+ .card-widget.card-info.h-card
.card-content
.card-info-avatar.is-center
.author-info__top-group
.author-info__sayhi#author-info__sayhi(onclick="sco.changeWittyWord()")
.avatar-img-group
- img.avatar-img(alt=_p('aside.avatar'), src=theme.aside.my_card.author.img)
+ img.u-photo.avatar-img(alt=_p('aside.avatar'), src=theme.aside.my_card.author.img)
if theme.aside.my_card.author.sticker
.avatar-sticker
img.avatar-sticker-img(src=theme.aside.my_card.author.sticker, alt=_p('aside.sticker'))
.author-info__description_group
.author-info__description!= theme.aside.my_card.content
.author-info__bottom-group
span.author-info__bottom-group-left
- .author-info__name= config.author
- .author-info__desc!= theme.aside.my_card.description
+ .author-info__name.p-name= config.author
+ .author-info__desc.p-note!= theme.aside.my_card.description
.card-info-social-icons.is-center
each value, label in theme.aside.my_card.information || {}
- var array = value.split('||')
- a.social-icon(class=array[2] href=url_for(trim(array[0])), title=label, rel="me")
+ a.social-icon.u-url(class=array[2] href=url_for(trim(array[0])), title=label, rel="me")
i.solitude(class=array[1])

修改完成后给博客push上去
然后去 https://indiewebify.me/validate-h-card/ 这边检测h-card的代码添加情况
如果识别到你设置的信息那就是成功了
成功提示

h-entry(博客信息)

修改[Blogroot]\themes\solitude\layout\post.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extends includes/layout.pug

block content
main.layout#content-inner
- #post
+ #post.h-entry
+ a.h-card.p-author(href=url_for("/") rel="author" style='display:none')= config.author
+ a.u-url.p-name(href=urlNoIndex() style='display:none')= page.title
+ time.dt-published(datetime=date_xml(page.date) style='display:none')= date_xml(page.date)
+ time.dt-updated(datetime=date_xml(page.updated) style='display:none')= date_xml(page.updated)
+ if page.categories.data.length > 0
+ a.p-category(href=url_for('/' + page.categories.data[0].path) style='display:none')= page.categories.data[0].name
if page.not_cover
include includes/widgets/post/postInfo
article.post-content.article-container
if theme.post_ai.enable
include includes/widgets/post/post-ai
- != page.content
+ .e-content!= page.content

修改[Blogroot]\themes\solitude\layout\includes\widgets\post\copyright.pug,转到第25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
            each item in theme.post.share.list || []
case item
when 'qq'
- a.social-share-ico.icon-qq(href=`https://connect.qq.com/widget/shareqq/index.html?url=${encodedPath}&title=${encodedTitle}&desc=${encodedDescription}&summary=${encodedDescription}&site=${encodedTitle}&pics=${encodedIcon}` title=_p('post.share.qq'))
+ a.u-syndication.social-share-ico.icon-qq(href=`https://connect.qq.com/widget/shareqq/index.html?url=${encodedPath}&title=${encodedTitle}&desc=${encodedDescription}&summary=${encodedDescription}&site=${encodedTitle}&pics=${encodedIcon}` title=_p('post.share.qq'))
i.solitude.fab.fa-qq
when 'weibo'
- a.social-share-ico.icon-weibo(href=`http://service.weibo.com/share/share.php?url=${encodedPath}&title=${encodedTitle}&pic=${encodedIcon}` title=_p('post.share.weibo'))
+ a.u-syndication.social-share-ico.icon-weibo(href=`http://service.weibo.com/share/share.php?url=${encodedPath}&title=${encodedTitle}&pic=${encodedIcon}` title=_p('post.share.weibo'))
i.solitude.fab.fa-weibo
when 'telegram'
- a.social-share-ico.icon-telegram(href=`https://t.me/share/url?url=${encodedPath}&text=${encodedTitle}` title=_p('post.share.telegram'))
+ a.u-syndication.social-share-ico.icon-telegram(href=`https://t.me/share/url?url=${encodedPath}&text=${encodedTitle}` title=_p('post.share.telegram'))
i.solitude.fab.fa-telegram
when 'whatsapp'
- a.social-share-ico.icon-whatsapp(href=`https://api.whatsapp.com/send?text=${encodedTitle} ${encodedPath}` title=_p('post.share.whatsapp'))
+ a.u-syndication.social-share-ico.icon-whatsapp(href=`https://api.whatsapp.com/send?text=${encodedTitle} ${encodedPath}` title=_p('post.share.whatsapp'))
i.solitude.fab.fa-whatsapp
when 'linkedin'
- a.social-share-ico.icon-linkedin(href=`https://www.linkedin.com/shareArticle?mini=true&url=${encodedPath}&title=${encodedTitle}&summary=${encodedDescription}&source=${encodedTitle}` title=_p('post.share.linkedin'))
+ a.u-syndication.social-share-ico.icon-linkedin(href=`https://www.linkedin.com/shareArticle?mini=true&url=${encodedPath}&title=${encodedTitle}&summary=${encodedDescription}&source=${encodedTitle}` title=_p('post.share.linkedin'))
i.solitude.fab.fa-linkedin
when 'facebook'
- a.social-share-ico.icon-facebook(href=`https://www.facebook.com/sharer/sharer.php?u=${encodedPath}` title=_p('post.share.facebook'))
+ a.u-syndication.social-share-ico.icon-facebook(href=`https://www.facebook.com/sharer/sharer.php?u=${encodedPath}` title=_p('post.share.facebook'))
i.solitude.fab.fa-facebook
when 'twitter'
- a.social-share-ico.icon-twitter(href=`https://twitter.com/intent/tweet?url=${encodedPath}&text=${encodedTitle}` title=_p('post.share.twitter'))
+ a.u-syndication.social-share-ico.icon-twitter(href=`https://twitter.com/intent/tweet?url=${encodedPath}&text=${encodedTitle}` title=_p('post.share.twitter'))
i.solitude.fab.fa-twitter

两个步骤修改完成之后给博客push上去
然后去 https://indiewebify.me/validate-h-entry/ 这边检测h-card的代码添加情况
提示:Success!We found the following post h-entry on your site就成功了

可选添加

如果你做完前面这三个步骤,恭喜你,你的站点已经适配IndieWeb了
适配完IndieWeb之后就可以选择加入IndieWeb webring或者添加WebMention(也就是我那评论下面的网络回响)

WebMention (可选)

官方介绍(翻译后):

Webmention 是一种用于跨 Web 对话和交互的开放 Web 标准(W3C 推荐标准),是一个强大的构建块,用于不断增长的分布式网络,该网络包含跨 Web 的点对点评论点赞转发和其他响应

前往 https://webmention.io/ 登录你的网站
登录之后点击最上方的Setting,按照图片的来
按照图片来
然后点击最上方的Sites,新增一个网站,新增完成之后点击你新增站点的“Get Setup Code”获取代码
获取到的代码在这
把这个地方的<link>代码也给复制到</head>前面

然后,去 https://webmention.rocks/receive/2 检验你的代码是否安装成功
如果提示这个就代表成功了:
成功的提示

添加 网络回响 功能

这个获取Mention的js代码是基于别人的方案而改来
在此基础上适配了pjax

新增[Blogroot]\themes\solitude\layout\includes\widgets\third-party\webmention.pug,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
hr
#post-comment
.comment-head
.comment-headline
i.fas.fa-comments.fa-fw
span= ' 网络回响'

.comment-wrap
p!= '你有对这篇文章写<a href="https://indieweb.org/responses" target="_blank">回应</a>吗? 你可以在这里提交你的文章网址(文章需要包含这篇文章的地址,点击这里了解 <a href="https://indieweb.org/Webmention" target="_blank">Webmention</a>):'
br
form.form-webmention(action=url_for(theme.webmention.endpoint) method="post" target="_blank")
input#form-webmention-source(name="source" placeholder='你的文章网址' required="" type="url")
input(name="target" type="hidden" value=urlNoIndex())
input.form-webmention-btn(type="submit" value="Send Webmention")
.webmention-timeline

修改[Blogroot]\themes\solitude\layout\post.pug
给这个功能加在comment下面
1
2
3
4
5
            if page.comment
- var comment_js = true
include includes/widgets/third-party/comments/comment
+ if theme.webmention.enable && page.webmention !== false
+ !=partial('includes/widgets/third-party/webmention', {}, {cache: true})

引入css代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
.wm-avatar {
border-radius: 50%;
margin: 0;
}
.webmention-avatars .avatar-wrapper {
margin-right: -8px;
}
a.avatar-wrapper {
display: inline-block;
width: 50px;
height: 50px;
position: relative;
}
.replies {
margin: 0;
padding: 0;
}
.reply {
list-style: none;
display: flex;
position: relative;
padding: 0;
align-items: flex-start;
margin-top: 0.6rem;
}
.reply p {
margin: 0;
}
.reply .text {
margin-left: 1rem;
font-size: 14px;
}
.reply-author-name {
font-weight: 500;
}
.form-webmention {
display: flex;
}
.form-webmention-btn {
flex: 0 0 auto;
margin-left: 12px;
align-items: baseline;
border: 0;
cursor: pointer;
}
.form-webmention-btn,
a.retweetBtn {
display: flex;
justify-content: center;
padding: 0 12px;
background: var(--anzhiyu-reverse);
color: var(--efu-secondbg);
}
#form-webmention-source {
flex: 1 0 auto;
border: 1px solid;
padding: 0 8px;
background: var(--efu-secondbg);
border: var(--style-border-always);
}
#form-webmention-source,
.form-webmention-btn,
a.retweetBtn {
height: 36px;
line-height: 36px;
font-size: 16px;
border-radius: 8px;
}

引入js代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
function webmention() {
const url = window.location.href;
const webmentionBaseUrl = "https://webmention.io"
fetch(webmentionBaseUrl + "/api/mentions.jf2?target=" + encodeURIComponent(url))
.then(function (response) {
console.log('sucess ' + webmentionBaseUrl + "/api/mentions.jf2?target=" + url);
return response.json();
})
.then(function (data) {
var html = '';
const distinctMentions = [
...new Map(data.children.map((item) => [item.author.url, item])).values()].sort((a, b) => new Date(a['wm-received']) - new Date(b['wm-received']));

html += `<div><p>`;

if (distinctMentions.length > 0) {
html += `已经有 ${distinctMentions.length} 朋友喜欢、分享或讨论这篇文章:</p>`;
}
html += `<div className="webmention-avatars">`;
distinctMentions.forEach(function (reply) {
html += `<a class="avatar-wrapper" href=${reply.author.url} key=${reply.author.name}><image class="wm-avatar" loading="lazy" src=${reply.author.photo != "" ? reply.author.photo: "/img/avatar.png"} data-nimg="fill" sizes="(max-width: 768px) 100vw,(max-width: 1200px) 50vw, 33vw"/></a>`;
});
html += `</div>`;

const replies = distinctMentions.filter(
(mention) => ('in-reply-to' in mention || 'mention-of' in mention)&& 'content' in mention
);
if (replies && replies.length) {
html += `<div class="webmention-replies">`;
html += `</br><h3>引用或评论(${replies.length})</h3>`;
html += `<ul class="replies">`;
replies.forEach(function (reply){
html += `<li class="reply" key=${reply["wm-id"]}>`;
html += `<div>`;
html += `<a class="avatar-wrapper" href=${reply.author.url} key=${reply.author.name}><image class="wm-avatar" loading="lazy" src=${reply.author.photo != "" ? reply.author.photo: "/img/avatar.png"} alt=${reply.author.name} data-nimg="fill" sizes="(max-width: 768px) 100vw,(max-width: 1200px) 50vw, 33vw"/></a>`;
html += `</div>`;
html += `<div class="text">`;
html += `<p class="reply-author-name"><a href=${reply.url} target="_blank">${reply.author.name}</a></p>`;
html += `<p class="reply-content">${reply.content.text.slice(0, 200)}</p>`;
html += `</div>`;
html += `</li>`;

});
html += `</ul>`;
html += `</div>`;
}
document.querySelector('div.webmention-timeline').innerHTML = html;
})
.catch(function (ex) {
console.error('fetch webmention error' + ex);
});
}
window.WebMentionLoad = function () {
if (document.querySelector('.webmention-timeline')) webmention();
};
window.addEventListener("load", WebMentionLoad)
document.addEventListener("pjax:complete", WebMentionLoad)

如果还不知道怎么引入js和css代码的话看这个:
https://blog.leonus.cn/2022/custom.html
https://akilar.top/posts/ebf20e02/#%E9%AD%94%E6%94%B9%E6%A0%B7%E5%BC%8F%E5%BC%95%E5%85%A5

Butterfly主题的inject,对应的是Solitude主题的extends,别看错!


_config.solitude.yml 中的 新增以下配置项
1
2
3
webmention:
enable: true
endpoint: https://webmention.io/blog.storical.space/webmention # 这个地方换成你获取到的endpoint

新增[Blogroot]\themes\butterfly\layout\includes\third-party\webmention.pug,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
hr
#post-comment
.comment-head
.comment-headline
i.fas.fa-comments.fa-fw
span= ' 网络回响'

.comment-wrap
p!= '你有对这篇文章写<a href="https://indieweb.org/responses" target="_blank">回应</a>吗? 你可以在这里提交你的文章网址(文章需要包含这篇文章的地址,点击这里了解 <a href="https://indieweb.org/Webmention" target="_blank">Webmention</a>):'
br
form.form-webmention(action=url_for(theme.webmention.endpoint) method="post" target="_blank")
input#form-webmention-source(name="source" placeholder='你的文章网址' required="" type="url")
input(name="target" type="hidden" value=urlNoIndex())
input.form-webmention-btn(type="submit" value="Send Webmention")
.webmention-timeline

修改[Blogroot]\themes\solitude\layout\post.pug
给这个功能加在comment下面
1
2
3
4
5
6
    if page.comments !== false && theme.comments && theme.comments.use
- var commentsJsLoad = true
!=partial('includes/third-party/comments/index', {}, {cache: true})

+ if theme.webmention.enable && page.webmention !== false
+ !=partial('includes/third-party/webmention', {}, {cache: true})

引入css代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
.wm-avatar {
border-radius: 50%;
margin: 0;
}
.webmention-avatars .avatar-wrapper {
margin-right: -8px;
}
a.avatar-wrapper {
display: inline-block;
width: 50px;
height: 50px;
position: relative;
}
.replies {
margin: 0;
padding: 0;
}
.reply {
list-style: none;
display: flex;
position: relative;
padding: 0;
align-items: flex-start;
margin-top: 0.6rem;
}
.reply p {
margin: 0;
}
.reply .text {
margin-left: 1rem;
font-size: 14px;
}
.reply-author-name {
font-weight: 500;
}
.form-webmention {
display: flex;
}
.form-webmention-btn {
flex: 0 0 auto;
margin-left: 12px;
align-items: baseline;
border: 0;
cursor: pointer;
}
.form-webmention-btn,
a.retweetBtn {
display: flex;
justify-content: center;
padding: 0 12px;
background: var(--anzhiyu-reverse);
color: var(--efu-secondbg);
}
#form-webmention-source {
flex: 1 0 auto;
border: 1px solid;
padding: 0 8px;
background: var(--efu-secondbg);
border: var(--style-border-always);
}
#form-webmention-source,
.form-webmention-btn,
a.retweetBtn {
height: 36px;
line-height: 36px;
font-size: 16px;
border-radius: 8px;
}

引入js代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
function webmention() {
const url = window.location.href;
const webmentionBaseUrl = "https://webmention.io"
fetch(webmentionBaseUrl + "/api/mentions.jf2?target=" + encodeURIComponent(url))
.then(function (response) {
console.log('sucess ' + webmentionBaseUrl + "/api/mentions.jf2?target=" + url);
return response.json();
})
.then(function (data) {
var html = '';
const distinctMentions = [
...new Map(data.children.map((item) => [item.author.url, item])).values()].sort((a, b) => new Date(a['wm-received']) - new Date(b['wm-received']));

html += `<div><p>`;

if (distinctMentions.length > 0) {
html += `已经有 ${distinctMentions.length} 朋友喜欢、分享或讨论这篇文章:</p>`;
}
html += `<div className="webmention-avatars">`;
distinctMentions.forEach(function (reply) {
html += `<a class="avatar-wrapper" href=${reply.author.url} key=${reply.author.name}><image class="wm-avatar" loading="lazy" src=${reply.author.photo != "" ? reply.author.photo: "/img/avatar.png"} data-nimg="fill" sizes="(max-width: 768px) 100vw,(max-width: 1200px) 50vw, 33vw"/></a>`;
});
html += `</div>`;

const replies = distinctMentions.filter(
(mention) => ('in-reply-to' in mention || 'mention-of' in mention)&& 'content' in mention
);
if (replies && replies.length) {
html += `<div class="webmention-replies">`;
html += `</br><h3>引用或评论(${replies.length})</h3>`;
html += `<ul class="replies">`;
replies.forEach(function (reply){
html += `<li class="reply" key=${reply["wm-id"]}>`;
html += `<div>`;
html += `<a class="avatar-wrapper" href=${reply.author.url} key=${reply.author.name}><image class="wm-avatar" loading="lazy" src=${reply.author.photo != "" ? reply.author.photo: "/img/avatar.png"} alt=${reply.author.name} data-nimg="fill" sizes="(max-width: 768px) 100vw,(max-width: 1200px) 50vw, 33vw"/></a>`;
html += `</div>`;
html += `<div class="text">`;
html += `<p class="reply-author-name"><a href=${reply.url} target="_blank">${reply.author.name}</a></p>`;
html += `<p class="reply-content">${reply.content.text.slice(0, 200)}</p>`;
html += `</div>`;
html += `</li>`;

});
html += `</ul>`;
html += `</div>`;
}
document.querySelector('div.webmention-timeline').innerHTML = html;
})
.catch(function (ex) {
console.error('fetch webmention error' + ex);
});
}
window.WebMentionLoad = function () {
if (document.querySelector('.webmention-timeline')) webmention();
};
window.addEventListener("load", WebMentionLoad)
document.addEventListener("pjax:complete", WebMentionLoad)

如果还不知道怎么引入js和css代码的话看这个:
https://blog.leonus.cn/2022/custom.html
https://akilar.top/posts/ebf20e02/#%E9%AD%94%E6%94%B9%E6%A0%B7%E5%BC%8F%E5%BC%95%E5%85%A5
_config.butterfly.yml 中的 新增以下配置项
1
2
3
webmention:
enable: true
endpoint: https://webmention.io/blog.storical.space/webmention # 这个地方换成你获取到的endpoint

魔改完成之后在 https://webmention.rocks/receive/1 这边测试一下
登录之后输入你的文章链接点击Begin test
如果看见这个提示:
发布成功后的提示
并且去你的测试文章这边看见你的数据:
发送的数据
那就代表成功了

好了,就到一段落,indieweb webring那个我懒得写了,写小记去了