230821 更新:新增article的JSON文件原生api,再也不用手动新增结构数据啦~

230720 更新:修复控制台无容器时报错

230718 更新:修复个人卡片显示评论数量异常。新增作者标识,提升UI样式

240628 更新:新增评论过滤匹配功能;调整、减少冗余UI样式以及JS优化

240702 更新:热评弹窗优化、UI调整、教程地址更新

新版

效果预览

创建数据

  • 创建 [blogRoot]/source/comments/index.md 页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
title: 最新评论
date: 2023-07-17 14:07:01
type: comments
top_img: false
aside: false
top_page: true
top_bg: https://img.meuicat.com/banner
top_item: 速览
top_title: 最新评论
top_tips: 快速预览本站最新评论
---

<!-- 页面内容 -->
  • 修改 [blogRoot]/themes/butterfly/layout/page.pug 来使页面匹配
    + 号直接删除 即是正常缩进)
1
2
3
4
5
6
      when 'categories'
include includes/page/categories.pug
+ when 'comments'
+ include includes/page/comments.pug
default
include includes/page/default-page.pug
  • 新建 [blogRoot]/themes/butterfly/layout/includes/page/comments.pug 页面,并新增以下内容
1
2
3
#comments-page
.page-load
img(src="https://img.meuicat.com/blog/loading.svg" style="margin:auto")
  • 新建 [blogRoot]/themes/butterfly/source/css/_page/comment.styl 样式文件,并新增以下内容
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#comments-page
display: flex
flex-wrap: wrap
gap: 12px
width: 100%
margin-top: 1.5rem

.page-load
display: flex
width: 100%

.comment-card
position: relative
width: calc(100% / 4 - 9px)
border-radius: 12px
border: var(--style-border)
padding: 14px
cursor: pointer
transition: .3s
overflow: hidden
box-shadow: var(--icat-shadow-border)
background: var(--icat-card-bg)
animation: slide-in .6s .4s backwards
will-change: transform

+maxWidth1024()
width: calc(100% / 2 - 6px)

+maxWidth768()
width: 100%

&:hover
border-color: var(--icat-blue)

.comment-more
opacity: 1

.comment-info
display: flex
align-items: center
padding-bottom: 14px
margin-bottom: 8px
border-bottom: 1px dashed var(--hr-border)

img
width: 50px
height: 50px
object-fit: cover
border-radius: 50%
margin: 0 !important

.comment-information
display: flex
flex-direction: column
margin-left: 12px
line-height: 1.5

.comment-user
display: flex
align-items: center
font-weight: bold
font-size: 15px

.comment-author
&:after
content: "\e04f"
font-family: "iconfont" !important
padding-left: 6px
font-size: 14px
color: var(--icat-green)

.comment-time
opacity: .8
font-size: 12px

.comment-content
margin: 8px 5px 0
overflow: hidden
text-overflow: ellipsis
display: -webkit-box
-webkit-box-orient: vertical
-webkit-line-clamp: 2
line-height: 1.7
font-weight: 400

.comment-more
position: absolute
left: 0
top: 0
height: 100%
width: 100%
z-index: 1
background: var(--icat-blue)
color: var(--icat-white)
margin: 0
display: flex
padding: .5rem 1rem
opacity: 0
flex-direction: column
justify-content: space-between
transition: .3s

.comment-title
display: flex
align-items: center
justify-content: space-between

span
display: flex
font-size: 16px
align-items: center
overflow: hidden
text-overflow: ellipsis
display: -webkit-box
-webkit-box-orient: vertical
-webkit-line-clamp: 1
font-weight: 400

i
margin-right: 4px

a
font-size: 14px
color: var(--icat-white)
opacity: .6
margin-left: 4px
white-space: nowrap

.comment-tool
display: flex
flex-wrap: wrap
gap: 4px 8px

a
background: var(--icat-card-bg-op);
color: var(--icat-white)
border-radius: 8px
padding: 4px 8px
font-size: 14px
opacity: .6

a:hover
opacity: 1

[data-theme='dark']
#comments-page
.comment-card
.comment-info
.comment-information
.comment-author
&:after
color: var(--icat-blue)
可选CSS样式
  • 新建 [blogRoot]/themes/butterfly/source/css/comment.css 样式文件,并新增以下内容
    (也可以在自建的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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#comments-page {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-lines: multiple;
-moz-box-lines: multiple;
-o-box-lines: multiple;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
gap: 12px;
width: 100%;
margin-top: 1.5rem;
}
#comments-page .page-load {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
width: 100%;
}
#comments-page .comment-card {
position: relative;
width: calc(100% / 4 - 9px);
border-radius: 12px;
border: var(--style-border);
padding: 14px;
cursor: pointer;
-webkit-transition: 0.3s;
-moz-transition: 0.3s;
-o-transition: 0.3s;
-ms-transition: 0.3s;
transition: 0.3s;
overflow: hidden;
-webkit-box-shadow: var(--icat-shadow-border);
box-shadow: var(--icat-shadow-border);
background: var(--icat-card-bg);
-webkit-animation: slide-in 0.6s 0.4s backwards;
-moz-animation: slide-in 0.6s 0.4s backwards;
-o-animation: slide-in 0.6s 0.4s backwards;
-ms-animation: slide-in 0.6s 0.4s backwards;
animation: slide-in 0.6s 0.4s backwards;
will-change: transform;
}
@media screen and (max-width: 1024px) {
#comments-page .comment-card {
width: calc(100% / 2 - 6px);
}
}
@media screen and (max-width: 768px) {
#comments-page .comment-card {
width: 100%;
}
}
#comments-page .comment-card:hover {
border-color: var(--icat-blue);
}
#comments-page .comment-card:hover .comment-more {
opacity: 1;
-ms-filter: none;
filter: none;
}
#comments-page .comment-card .comment-info {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-align: center;
-moz-box-align: center;
-o-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-bottom: 14px;
margin-bottom: 8px;
border-bottom: 1px dashed var(--hr-border);
}
#comments-page .comment-card .comment-info img {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 50%;
margin: 0 !important;
}
#comments-page .comment-card .comment-info .comment-information {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-o-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
margin-left: 12px;
line-height: 1.5;
}
#comments-page .comment-card .comment-info .comment-information .comment-user {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-align: center;
-moz-box-align: center;
-o-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
font-weight: bold;
font-size: 15px;
}
#comments-page .comment-card .comment-info .comment-information .comment-author:after {
content: "\e04f";
font-family: "iconfont" !important;
padding-left: 6px;
font-size: 14px;
color: var(--icat-green);
}
#comments-page .comment-card .comment-info .comment-information .comment-time {
opacity: 0.8;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
filter: alpha(opacity=80);
font-size: 12px;
}
#comments-page .comment-content {
margin: 8px 5px 0;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-height: 1.7;
font-weight: 400;
}
#comments-page .comment-more {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
z-index: 1;
background: var(--icat-blue);
color: var(--icat-white);
margin: 0;
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
padding: 0.5rem 1rem;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-o-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: justify;
-moz-box-pack: justify;
-o-box-pack: justify;
-ms-flex-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-transition: 0.3s;
-moz-transition: 0.3s;
-o-transition: 0.3s;
-ms-transition: 0.3s;
transition: 0.3s;
}
#comments-page .comment-more .comment-title {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-align: center;
-moz-box-align: center;
-o-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: justify;
-moz-box-pack: justify;
-o-box-pack: justify;
-ms-flex-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
}
#comments-page .comment-more .comment-title span {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
font-size: 16px;
-webkit-box-align: center;
-moz-box-align: center;
-o-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
font-weight: 400;
}
#comments-page .comment-more .comment-title span i {
margin-right: 4px;
}
#comments-page .comment-more .comment-title a {
font-size: 14px;
color: var(--icat-white);
opacity: 0.6;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)";
filter: alpha(opacity=60);
margin-left: 4px;
white-space: nowrap;
}
#comments-page .comment-more .comment-tool {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-lines: multiple;
-moz-box-lines: multiple;
-o-box-lines: multiple;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
gap: 4px 8px;
}
#comments-page .comment-more .comment-tool a {
background: var(--icat-card-bg-op);
color: var(--icat-white);
border-radius: 8px;
padding: 4px 8px;
font-size: 14px;
opacity: 0.6;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)";
filter: alpha(opacity=60);
}
#comments-page .comment-more a:hover {
opacity: 1;
-ms-filter: none;
filter: none;
}
[data-theme='dark'] #comments-page .comment-card .comment-info .comment-information .comment-author:after {
color: var(--icat-blue);
}

/* 最新评论页 */
  • _config.butterfly.yml 主题配置文件中 inject 下的 head 引入 comment.css
1
2
3
4
5
6
7
8
9
  ···

inject:
head:
- <link rel="stylesheet" href="/css/comment.css"> # 最新评论页
bottom:
- ···

···
  • 创建 [blogRoot]/themes/butterfly/source/js/comment.js 文件,并新增以下内容
    (或写在自建的公共 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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
let article;
const comment = {
fetchData(options, type, exclude) {
fetch('{envId}', {
method: "POST",
body: JSON.stringify({
"event": "GET_RECENT_COMMENTS",
"accessToken": "{YOUR_TOKEN}",
"includeReply": true,
...options
}),
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json()).then(response => {
let html = '',
data = response.data;

if (exclude) {
switch (type) {
case 'visitor':
data = data.filter(item => item.mailMd5 === exclude);
break;
case 'v-shield':
data = data.filter(item => item.mailMd5 !== exclude);
break;
case 'a-shield':
data = data.filter(item => item.url !== exclude);
break;
default:
break;
}
}

data.forEach(item => {
const createdDate = new Date(item.created);
const formattedDate = `${createdDate.getFullYear().toString().slice(-2)}-${createdDate.getMonth() + 1}-${createdDate.getDate()} ${createdDate.getHours()}:${createdDate.getMinutes()}:${createdDate.getSeconds()}`;
html += `<div class="comment-card">
<div class="comment-info">
<img src="${item.avatar}" class="nolazyload">
<div class="comment-information">
<span class="${['亦封', '亦小封'].includes(item.nick) ? 'comment-author' : ''} comment-user" data-mailMd5="${item.mailMd5}">${item.nick}</span>
<span class="comment-time">${formattedDate}</span>
</div>
</div>
<div class="comment-content">${item.commentText.replaceAll('<', '&lt;').replaceAll('>', '&gt;')}</div>
<div class="comment-more">
<div class="comment-title">
<span class="comment-link" title="查看此文章" onclick="pjax.loadUrl('${item.url}')">
<i class="iconfont icat-read"></i>
${article[item.url]}
</span>
<a onclick="pjax.loadUrl('${item.url}#${item.id}')">查看评论</a>
</div>
<div class="comment-tool">`

let a = `<a href="javascript:void(0)" onclick="comment.article(event)" title="显示此文章所有评论">查看更多</a>`,
b = `<a href="javascript:void(0)" onclick="comment.article(event, true)" title="不显示此文章的评论">屏蔽文章</a>`,
c = `<a href="javascript:void(0)" onclick="comment.visitor(event, true)" title="不显示该访客的评论">屏蔽Ta</a>`,
d = `<a href="javascript:void(0)" onclick="comment.visitor(event)" title="显示该访客的所有评论">查看Ta更多评论</a>`
e = `<a href="javascript:void(0)" onclick="comment.data()" title="查看本站最新评论">返回评论</a>`;
switch (type) {
case 'article':
html += e + c + d;
break;
case 'visitor':
html += e + a + b;
break;
case 'v-shield':
case 'a-shield':
html += a + b + c + d + e;
break;
default:
if (!type) html += a + b + c + d;
break;
}

html += `</div>
</div>
</div>`
});
document.getElementById('comments-page').innerHTML = html;
});
},
visitor(event, shield) {
const spanElement = event.target.closest('.comment-card').querySelector('.comment-user');
const mail = spanElement.getAttribute('data-mailMd5');
if (shield) {
this.fetchData({
"pageSize": -1
}, 'v-shield', mail);
return
}
this.fetchData({
"pageSize": -1
}, 'visitor', mail);
},
article(event, shield) {
const spanElement = event.target.closest('.comment-card').querySelector('.comment-link');
const url = spanElement.getAttribute('onclick').match(/'(\/.*?)'/)[1];
if (shield) {
this.fetchData({
"pageSize": -1
}, 'a-shield', url);
return
}
this.fetchData({
"urls": [url]
}, 'article');
},
data() {
if (!article) fetch('/article.json').then(res => res.json()).then(data => { article = data; });
this.fetchData({
"pageSize": 100
});
}
};

window.DOMReady = function () {
if (document.querySelector('#comments-page')) comment.data();
};

window.addEventListener("load", DOMReady)
document.addEventListener("pjax:complete", DOMReady)

// 最新评论页

注意:
{envId} 和 {YOUR_TOKEN} 需要替换为对应值

  • _config.butterfly.yml 主题配置文件中 inject 下的 bottom 引入 comment.js
1
2
3
4
5
6
7
8
9
  ···

inject:
head:
- ···
bottom:
- <script type="text/javascript" src="/js/comment.js"></script> # 最新评论页

···

个人卡片总评论

  • 修改 [blogRoot]/themes/butterfly/layout/includes/widget/card_author.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
27
28
29
30
if theme.aside.card_author.enable
.card-widget.card-info
.is-center
.avatar-img
img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar")
.author-info__name= config.author
.author-info__description!= theme.aside.card_author.description || config.description

.card-info-data.site-data.is-center
a(href=url_for(config.archive_dir) + '/')
.headline= _p('aside.articles')
.length-num= site.posts.length
a(href=url_for(config.tag_dir) + '/')
.headline= _p('aside.tags')
.length-num= site.tags.length
- a(href=url_for(config.category_dir) + '/')
- .headline= _p('aside.categories')
- .length-num= site.categories.length
+ a(href="/comments/")
+ .headline= _p('评论')
+ .length-num.comment-total= _p('0')

if theme.aside.card_author.button.enable
a#card-info-btn(href=theme.aside.card_author.button.link)
i(class=theme.aside.card_author.button.icon)
span=theme.aside.card_author.button.text

if(theme.social)
.card-info-social-icons.is-center
!=fragment_cache('social', function(){return partial('includes/header/social')})
  • 新增 /themes/butterfly/source/js/comment.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
let article;
const comment = {
···

+ total: function() {
+ fetch('{envId}', {
+ method: "POST",
+ body: JSON.stringify({ event: "GET_RECENT_COMMENTS", accessToken: "{YOUR_TOKEN}", includeReply: true, pageSize: -1 }),
+ headers: { 'Content-Type': 'application/json' }
+ }).then(res => res.json()).then(({ data }) => {
+ const totalBox = document.querySelectorAll('.length-num.comment-total');
+ if (totalBox) element.innerText = data.length;
+ // console.log('本站Twikoo总评论数:', totalComments);
+ });
+ } // 评论总数
};

window.DOMReady = function () {
+ comment.total();
if (document.querySelector('#comments-page')) comment.data();
};

window.addEventListener("load", DOMReady)
document.addEventListener("pjax:complete", DOMReady)

热评弹窗

本小节魔改内容包含 右键菜单 添加热评开关功能,如有需要请移步至 Butterfly的魔改教程:右键菜单

样式与旧版有细微差异,如需旧版可查看下方的旧版热评弹窗教程

  • 新增 [blogRoot]/themes/butterfly/layout/includes/layout.pug 页面内容
    (**+** 号直接删除 即是正常缩进)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        ···

if (footerBg)
if (footerBg === true)
- var footer_bg = bg_img
else
- var footer_bg = isImgOrUrl(theme.footer_bg) ? `background-image: url('${url_for(footerBg)}')` : `background: ${footerBg}`
else
- var footer_bg = ''

+ #comment-barrage
footer#footer(style=footer_bg)
!=partial('includes/footer', {}, {cache: true})

else
include ./404.pug

include ./rightside.pug
···
  • 新建 [blogRoot]/themes/butterfly/source/css/_page/barrage.styl 样式文件,并新增以下内容
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#comment-barrage
position: fixed
bottom: 40px
right: 68px
display: flex
flex-direction: column
justify-content: end
align-items: flex-end
z-index: 999
transition: .3s
user-select: none

+maxWidth768()
display: none

.comment-barrage-item
min-width: 286px
max-width: 286px
width: fit-content
min-height: 80px
max-height: 150px
margin: 4px
padding: 8px 14px
background: var(--card-bg)
border-radius: 8px
color: var(--icat-fontcolor)
animation: barrageIn .6s cubic-bezier(0.42, 0, 0.3, 1.11)
transition: .3s
display: flex
flex-direction: column
border: var(--style-border)
backdrop-filter: saturate(180%) blur(20px)
position: fixed
box-shadow: var(--icat-shadow-border)
overflow: hidden

&:hover
border: 1px solid var(--icat-blue)
box-shadow: var(--icat-shadow-blue)

&.out
opacity: 0
animation: barrageOut 0.6s cubic-bezier(0.42, 0, 0.3, 1.11)

&.hovered
opacity: 0

pre
display: none

li
display: none


p
color: var(--icat-fontcolor)
margin: 8px 0 0
line-height: 1.2
-webkit-line-clamp: 2
display: -webkit-box
-webkit-box-orient: vertical
font-size: 12px
font-weight: bold
overflow: hidden
text-overflow: ellipsis

&:hover
color: var(--icat-blue)

img
&:not(.tk-owo-emotion)
display: none

&.tk-owo-emotion
width: 16px
padding: 0
margin: 0
transform: translateY(2px)

blockquote
display: none

br
display: none

.barrageHead
height: 30px
padding: 0
line-height: 30px
font-size: 12px
border-bottom: var(--style-border)
display: flex
justify-content: space-between
align-items: center
font-weight: bold
padding-bottom: 6px

.barrageAvatar
width: 18px
height: 18px
margin: 0
margin-right: 8px
border-radius: 50%
background: var(--icat-blue)

.barrageTime
margin-left: 4px

.comment-barrage-close
color: var(--icat-secondtext)
cursor: pointer
line-height: 1
margin-left: auto

&:hover
color: var(--icat-blue)

.barrageContent
font-size: 14px
font-weight: normal
height: calc(100% - 30px)
overflow: hidden
width: fit-content
max-height: 48px

a
pointer-events:none
font-size: 14px


&:-webkit-scrollbar
height: 0
width: 4px

&-button
display: none
可选CSS样式
  • 新建 [blogRoot]/themes/butterfly/source/css/barrage.css 样式文件,并新增以下内容
    (也可以在自建的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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#comment-barrage {
position: fixed;
bottom: 40px;
right: 68px;
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-o-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: end;
-moz-box-pack: end;
-o-box-pack: end;
-ms-flex-pack: end;
-webkit-justify-content: end;
justify-content: end;
-webkit-box-align: end;
-moz-box-align: end;
-o-box-align: end;
-ms-flex-align: end;
-webkit-align-items: flex-end;
align-items: flex-end;
z-index: 999;
-webkit-transition: 0.3s;
-moz-transition: 0.3s;
-o-transition: 0.3s;
-ms-transition: 0.3s;
transition: 0.3s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media screen and (max-width: 768px) {
#comment-barrage {
display: none;
}
}
.comment-barrage-item {
min-width: 286px;
max-width: 286px;
width: fit-content;
min-height: 80px;
max-height: 150px;
margin: 4px;
padding: 8px 14px;
background: var(--card-bg);
border-radius: 8px;
color: var(--icat-fontcolor);
-webkit-animation: barrageIn 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
-moz-animation: barrageIn 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
-o-animation: barrageIn 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
-ms-animation: barrageIn 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
animation: barrageIn 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
-webkit-transition: 0.3s;
-moz-transition: 0.3s;
-o-transition: 0.3s;
-ms-transition: 0.3s;
transition: 0.3s;
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-o-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
border: var(--style-border);
backdrop-filter: saturate(180%) blur(20px);
position: fixed;
-webkit-box-shadow: var(--icat-shadow-border);
box-shadow: var(--icat-shadow-border);
overflow: hidden;
}
.comment-barrage-item:hover {
border: 1px solid var(--icat-blue);
-webkit-box-shadow: var(--icat-shadow-blue);
box-shadow: var(--icat-shadow-blue);
}
.comment-barrage-item.out {
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
-webkit-animation: barrageOut 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
-moz-animation: barrageOut 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
-o-animation: barrageOut 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
-ms-animation: barrageOut 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
animation: barrageOut 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
}
.comment-barrage-item.hovered {
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
}
.comment-barrage-item pre {
display: none;
}
.comment-barrage-item li {
display: none;
}
.comment-barrage-item p {
color: var(--icat-fontcolor);
margin: 8px 0 0;
line-height: 1.2;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
font-size: 12px;
font-weight: bold;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}
.comment-barrage-item p:hover {
color: var(--icat-blue);
}
.comment-barrage-item p img:not(.tk-owo-emotion) {
display: none;
}
.comment-barrage-item p img.tk-owo-emotion {
width: 16px;
padding: 0;
margin: 0;
-webkit-transform: translateY(2px);
-moz-transform: translateY(2px);
-o-transform: translateY(2px);
-ms-transform: translateY(2px);
transform: translateY(2px);
}
.comment-barrage-item blockquote {
display: none;
}
.comment-barrage-item br {
display: none;
}
.comment-barrage-item .barrageHead {
height: 30px;
padding: 0;
line-height: 30px;
font-size: 12px;
border-bottom: var(--style-border);
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-pack: justify;
-moz-box-pack: justify;
-o-box-pack: justify;
-ms-flex-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-moz-box-align: center;
-o-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
font-weight: bold;
padding-bottom: 6px;
}
.comment-barrage-item .barrageAvatar {
width: 18px;
height: 18px;
margin: 0;
margin-right: 8px;
border-radius: 50%;
background: var(--icat-blue);
}
.comment-barrage-item .barrageTime {
margin-left: 4px;
}
.comment-barrage-item .comment-barrage-close {
color: var(--icat-secondtext);
cursor: pointer;
line-height: 1;
margin-left: auto;
}
.comment-barrage-item .comment-barrage-close:hover {
color: var(--icat-blue);
}
.comment-barrage-item .barrageContent {
font-size: 14px;
font-weight: normal;
height: calc(100% - 30px);
overflow: hidden;
width: fit-content;
max-height: 48px;
}
.comment-barrage-item .barrageContent a {
pointer-events: none;
font-size: 14px;
}
.comment-barrage-item .barrageContent:-webkit-scrollbar {
height: 0;
width: 4px;
}
.comment-barrage-item .barrageContent:-webkit-scrollbar-button {
display: none;
}

/* 热评弹窗 */
  • _config.butterfly.yml 主题配置文件中 inject 下的 head 引入 barrage.css
1
2
3
4
5
6
7
8
9
  ···

inject:
head:
- <link rel="stylesheet" href="/css/barrage.css"> # 热评弹窗
bottom:
- ···

···
  • 全替换 [blogRoot]/themes/butterfly/source/js/comment.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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
let article,
Interval = null,
hoverBarrage = false;
const comment = {
initBarrage(data) {
if (!data.length) return;
let Index = 0,
box = [],
dom = document.querySelector('#comment-barrage');

clearInterval(Interval);
Interval = null;

Interval = setInterval(() => {
if (box.length >= 1 && !hoverBarrage) {
removeBarrage(box.shift());
}
if(!hoverBarrage){
BarrageBox(data[Index]);
Index += 1;
Index %= data.length;
}
}, 5000);
$("#menu-commentBarrage span").text("关闭热评");
$("#comment-barrage").hover(function() {
hoverBarrage = true;
console.log("热评悬浮");
}, function() {
hoverBarrage = false;
console.log("停止悬浮");
});

async function BarrageBox(data){
const time = await meuicat.changeTime([data.created]);

let barrage = document.createElement('div');
barrage.className = 'comment-barrage-item'
barrage.innerHTML = `
<div class="barrageHead">
<img class="barrageAvatar" src="${data.avatar}"/>
<div class="barrageNick">${data.nick}</div>
<div class="barrageTime">评论于${time[0].timeString}</div>
<a class="comment-barrage-close" href="javascript:rm.switchCommentBarrage()"><i class="iconfont icat-close"></i></a>
</div>
<a class="barrageContent" href="javascript:void(0);" onclick="meuicat.scrollTo('${data.id}');">${data.comment}</a>
`
box.push(barrage);
dom.append(barrage);
}
function removeBarrage(barrage){
barrage.className = 'comment-barrage-item out';
setTimeout(()=>{
dom.removeChild(barrage);
},1000)
}

const scrollBarrage = btf.throttle(function(){
let visibleBottom = window.scrollY + document.documentElement.clientHeight,
comment = document.getElementById('post-comment'),
centerY = comment.offsetTop+(comment.offsetHeight/2);

if(document.body.clientWidth > 768){
if(centerY > visibleBottom){
dom.style.bottom = '40px';
} else {
dom.style.bottom = '-200px';
}
}
}, 200)

document.addEventListener('scroll', scrollBarrage);
document.addEventListener('pjax:send', function(){
clearInterval(Interval);
document.removeEventListener('scroll', scrollBarrage);
});
}, //热评弹窗
fetchData(options, type, exclude) {
fetch('{envId}', {
method: "POST",
body: JSON.stringify({
"event": "GET_RECENT_COMMENTS",
"accessToken": "{YOUR_TOKEN}",
"includeReply": true,
...options
}),
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json()).then(response => {
let html = '',
data = response.data;

if (type === 'total') {
const totalBox = document.querySelectorAll('.length-num.comment-total, .N_comments');
totalBox.forEach(element => {
if (element.classList.contains('N_comments')) {
element.innerText = data.length + '条评论';
} else {
element.innerText = data.length;
}
});
return
};
if (exclude) {
switch (type) {
case 'visitor':
data = data.filter(item => item.mailMd5 === exclude);
break;
case 'v-shield':
data = data.filter(item => item.mailMd5 !== exclude);
break;
case 'a-shield':
data = data.filter(item => item.url !== exclude);
break;
default:
break;
}
}

if (type === 'barrage') return this.initBarrage(data);

data.forEach(item => {
const createdDate = new Date(item.created);
const formattedDate = `${createdDate.getFullYear().toString().slice(-2)}-${createdDate.getMonth() + 1}-${createdDate.getDate()} ${createdDate.getHours()}:${createdDate.getMinutes()}:${createdDate.getSeconds()}`;
html += `<div class="comment-card">
<div class="comment-info">
<img src="${item.avatar}" class="nolazyload">
<div class="comment-information">
<span class="${['亦封', '亦小封'].includes(item.nick) ? 'comment-author' : ''} comment-user" data-mailMd5="${item.mailMd5}">${item.nick}</span>
<span class="comment-time">${formattedDate}</span>
</div>
</div>
<div class="comment-content">${item.commentText.replaceAll('<', '&lt;').replaceAll('>', '&gt;')}</div>
<div class="comment-more">
<div class="comment-title">
<span class="comment-link" title="查看此文章" onclick="pjax.loadUrl('${item.url}')">
<i class="iconfont icat-read"></i>
${article[item.url]}
</span>
<a onclick="pjax.loadUrl('${item.url}#${item.id}')">查看评论</a>
</div>
<div class="comment-tool">`

let a = `<a href="javascript:void(0)" onclick="comment.article(event)" title="显示此文章所有评论">查看更多</a>`,
b = `<a href="javascript:void(0)" onclick="comment.article(event, true)" title="不显示此文章的评论">屏蔽文章</a>`,
c = `<a href="javascript:void(0)" onclick="comment.visitor(event, true)" title="不显示该访客的评论">屏蔽Ta</a>`,
d = `<a href="javascript:void(0)" onclick="comment.visitor(event)" title="显示该访客的所有评论">查看Ta更多评论</a>`
e = `<a href="javascript:void(0)" onclick="comment.data()" title="查看本站最新评论">返回评论</a>`;
switch (type) {
case 'article':
html += e + c + d;
break;
case 'visitor':
html += e + a + b;
break;
case 'v-shield':
case 'a-shield':
html += a + b + c + d + e;
break;
default:
if (!type) html += a + b + c + d;
break;
}

html += `</div>
</div>
</div>`
});
document.getElementById('comments-page').innerHTML = html;
});
},
visitor(event, shield) {
const spanElement = event.target.closest('.comment-card').querySelector('.comment-user');
const mail = spanElement.getAttribute('data-mailMd5');
if (shield) {
this.fetchData({
"pageSize": -1
}, 'v-shield', mail);
return
}
this.fetchData({
"pageSize": -1
}, 'visitor', mail);
},
article(event, shield) {
const spanElement = event.target.closest('.comment-card').querySelector('.comment-link');
const url = spanElement.getAttribute('onclick').match(/'(\/.*?)'/)[1];
if (shield) {
this.fetchData({
"pageSize": -1
}, 'a-shield', url);
return
}
this.fetchData({
"urls": [url]
}, 'article');
},
data() {
if (!article) {
fetch('/articles.json').then(res => res.json()).then(data => {
article = {};
[...data.post, ...data.page].forEach(item => {
article[item.link] = item.title;
});
});
}
this.fetchData({
"pageSize": 100
});
}, // 最新评论页
};
单独使用热评弹窗
  • 创建 [blogRoot]/themes/butterfly/source/js/barrage.js 文件,并新增以下内容
    (或写在自建的公共 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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
let Interval = null,
hoverBarrage = false;
const barrage = {
fetchData(options) {
fetch('{envId}', {
method: "POST",
body: JSON.stringify({
"event": "GET_RECENT_COMMENTS",
"accessToken": "{YOUR_TOKEN}",
"includeReply": true,
...options
}),
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json()).then(response => {
let html = '',
data = response.data;
let Index = 0,
box = [],
dom = document.querySelector('#comment-barrage');

clearInterval(Interval);
Interval = null;

Interval = setInterval(() => {
if (box.length >= 1 && !hoverBarrage) {
removeBarrage(box.shift());
}
if(!hoverBarrage){
BarrageBox(data[Index]);
Index += 1;
Index %= data.length;
}
}, 5000);
$("#menu-commentBarrage span").text("关闭热评");
$("#comment-barrage").hover(function() {
hoverBarrage = true;
console.log("热评悬浮");
}, function() {
hoverBarrage = false;
console.log("停止悬浮");
});

async function BarrageBox(data){
const time = await meuicat.changeTime([data.created]);

let barrage = document.createElement('div');
barrage.className = 'comment-barrage-item'
barrage.innerHTML = `
<div class="barrageHead">
<img class="barrageAvatar" src="${data.avatar}"/>
<div class="barrageNick">${data.nick}</div>
<div class="barrageTime">评论于${time[0].timeString}</div>
<a class="comment-barrage-close" href="javascript:rm.switchCommentBarrage()"><i class="iconfont icat-close"></i></a>
</div>
<a class="barrageContent" href="javascript:void(0);" onclick="meuicat.scrollTo('${data.id}');">${data.comment}</a>
`
box.push(barrage);
dom.append(barrage);
}
function removeBarrage(barrage){
barrage.className = 'comment-barrage-item out';
setTimeout(()=>{
dom.removeChild(barrage);
},1000)
}

const scrollBarrage = btf.throttle(function(){
let visibleBottom = window.scrollY + document.documentElement.clientHeight,
comment = document.getElementById('post-comment'),
centerY = comment.offsetTop+(comment.offsetHeight/2);

if(document.body.clientWidth > 768){
if(centerY > visibleBottom){
dom.style.bottom = '40px';
} else {
dom.style.bottom = '-200px';
}
}
}, 200)

document.addEventListener('scroll', scrollBarrage);
document.addEventListener('pjax:send', function(){
clearInterval(Interval);
document.removeEventListener('scroll', scrollBarrage);
});

});
},
data() {
this.fetchData({ "urls": [window.location.pathname] });
}
};

window.DOMReady = function () {
barrage.data();
};

window.addEventListener("load", DOMReady)
document.addEventListener("pjax:complete", DOMReady)

// 热评弹窗

注意:
{envId} 和 {YOUR_TOKEN} 需要替换为对应值

  • _config.butterfly.yml 主题配置文件中 inject 下的 bottom 引入 barrage.js
1
2
3
4
5
6
7
8
9
  ···

inject:
head:
- ···
bottom:
- <script type="text/javascript" src="/js/barrage.js"></script> # 热评弹窗

···

旧版

效果预览

历史迭代教程

创建数据

  • 创建 [blogRoot]/source/comments/index.md 页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
title: 最新评论
date: 2023-07-17 14:07:01
type: comments
top_img: false
aside: false
top_page: true
top_bg: https://img.meuicat.com/banner
top_item: 速览
top_title: 最新评论
top_tips: 快速预览本站最新评论
---

<!-- 页面内容 -->
  • 修改 [blogRoot]/themes/butterfly/layout/page.pug 来使页面匹配
    + 号直接删除 即是正常缩进)
1
2
3
4
5
6
      when 'categories'
include includes/page/categories.pug
+ when 'comments'
+ include includes/page/comments.pug
default
include includes/page/default-page.pug
  • 新建 [blogRoot]/themes/butterfly/layout/includes/page/comments.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
27
28
29
30
31
#comments-page

script.
if (1) {
fetch('/article.json').then(res => res.json()).then(data => {
let article = data
fetch('{envId}', {
method: "POST",
body: JSON.stringify({ "event": "GET_RECENT_COMMENTS", "accessToken": "{YOUR_TOKEN}", "includeReply": true, "pageSize": 100, }),
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json()).then(({ data }) => {
let html = ''
data.forEach(item => {
const createdDate = new Date(item.created);
const formattedDate = `${createdDate.getFullYear().toString().slice(-2)}-${createdDate.getMonth() + 1}-${createdDate.getDate()} ${createdDate.getHours()}:${createdDate.getMinutes()}:${createdDate.getSeconds()}`;
html += `<div class="comment-card" title="${item.commentText.replaceAll(/<.*?>/g, '').replaceAll(/[\s\n]/g, '')}" onclick="pjax.loadUrl('${item.url}#${item.id}')">
<div class="comment-info">
<img src="${item.avatar}" class="nolazyload">
<div class="comment-information">
<span class="${item.nick === '亦封' ? 'comment-user comment-author' : 'comment-user'}">${item.nick}</span>
<span class="comment-time">${formattedDate}</span>
</div>
</div>
<div class="comment-content">${item.commentText.replaceAll('<', '&lt;').replaceAll('>', '&gt;')}</div>
<div class="comment-article">"${article[item.url]}"</div>
</div>`
})
document.getElementById('comments-page').innerHTML = html
})
})
}
  • 创建 [blogRoot]/themes/butterfly/scripts/helpers/article-json.js 文件,并新增以下内容,用来生成并处理 article.json 数据文件
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
/**
* MeuiCat
* generate json - article_comments
* developer:meuciat.com
*/

'use strict'

hexo.extend.generator.register('theData', function (locals) {
const postsData = locals.posts
.filter(post => post.path !== '/' && post.comments !== false)
.map(post => {
const link = post.permalink.replace(/^(?:\/\/|[^/]+)*\//, '/');
return {
[link]: post.title || "暂无标题"
};
});

const pagesData = locals.pages
.filter(page => page.path !== '/' && page.comments !== false && !page.source.endsWith('.js') && !page.source.endsWith('.css'))
.map(page => {
const link = page.permalink.replace(/^(?:\/\/|[^/]+)*\//, '/').replace('index.html', '');
return {
[link]: page.title || "暂无标题"
};
});

const combinedData = postsData.concat(pagesData);

const jsonData = combinedData.reduce((acc, obj) => {
const key = Object.keys(obj)[0];
const value = obj[key];
acc[key] = value;
return acc;
}, {});

return {
path: 'article.json',
data: JSON.stringify(jsonData)
};
});
  • 新建 [blogRoot]/source/css/comments.css 样式文件,并新增以下内容
    (也可以在自建的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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#comments-page {
display: flex;
flex-wrap: wrap;
gap: 12px;
min-height: 100px;
width: 100%;
margin-top: 1.5rem;
}
#comments-page .comment-card {
position: relative;
width: calc(100% / 4 - 9px);
border-radius: 12px;
border: var(--style-border);
padding: 14px;
cursor: pointer;
transition: .3s;
overflow: hidden;
box-shadow: var(--icat-shadow-border);
background: var(--icat-card-bg);
}
@media screen and (max-width: 900px) {
#comments-page .comment-card {
width: calc(100% / 2 - 6px);
}
}
@media screen and (max-width: 768px) {
#comments-page .comment-card {
width: 100%;
}
}
#comments-page .comment-card:hover {
border-color: var(--icat-blue);
}
#comments-page .comment-card:hover .comment-article {
opacity: 1;
top: 0;
}
#comments-page .comment-card .comment-info {
display: flex;
align-items: center;
padding-bottom: 14px;
margin-bottom: 8px;
border-bottom: 1px dashed var(--hr-border);
}
#comments-page .comment-card .comment-info img {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 50%;
margin: 0 !important;
}
#comments-page .comment-card .comment-info .comment-information {
display: flex;
flex-direction: column;
margin-left: 12px;
line-height: 1.5;
}
#comments-page .comment-card .comment-info .comment-information .comment-user {
display: flex;
align-items: center;
font-weight: bold;
font-size: 15px;
}
#comments-page .comment-card .comment-info .comment-information .comment-author::after {
content: "\e04f";
font-family: "iconfont" !important;
padding-left: 6px;
font-size: 14px;
color: var(--icat-green);
}
[data-theme="dark"] #comments-page .comment-card .comment-info .comment-information .comment-author::after {
color: var(--icat-blue);
}
#comments-page .comment-card .comment-info .comment-information .comment-time {
opacity: .8;
font-size: 12px;
}
#comments-page .comment-card .comment-info .comment-content {
margin: 8px 5px 0;
}
.comment-content, .comment-article {
transition: .3s;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-height: 1.7;
font-weight: 400;
}
.comment-article {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
z-index: 1;
background: var(--icat-blue);
color: white;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
opacity: 0;
text-align: center;
}

/* 最新评论页面样式 */
  • _config.butterfly.yml 主题配置文件中 inject 下的 head 引入 comments.css 样式
1
2
3
4
5
6
7
8
9
10
  ···

inject:
head:
- <link rel="stylesheet" href="/css/comments.css"> # 最新评论 - 样式
- ···
bottom:
- ···

···

个人卡片总评论

  • 修改 [blogRoot]/themes/butterfly/layout/includes/widget/card_author.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
27
28
29
30
if theme.aside.card_author.enable
.card-widget.card-info
.is-center
.avatar-img
img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar")
.author-info__name= config.author
.author-info__description!= theme.aside.card_author.description || config.description

.card-info-data.site-data.is-center
a(href=url_for(config.archive_dir) + '/')
.headline= _p('aside.articles')
.length-num= site.posts.length
a(href=url_for(config.tag_dir) + '/')
.headline= _p('aside.tags')
.length-num= site.tags.length
- a(href=url_for(config.category_dir) + '/')
- .headline= _p('aside.categories')
- .length-num= site.categories.length
+ a(href="/comments/")
+ .headline= _p('评论')
+ .length-num.icat-pc-comment= _p('0')

if theme.aside.card_author.button.enable
a#card-info-btn(href=theme.aside.card_author.button.link)
i(class=theme.aside.card_author.button.icon)
span=theme.aside.card_author.button.text

if(theme.social)
.card-info-social-icons.is-center
!=fragment_cache('social', function(){return partial('includes/header/social')})
  • 修改 [blogRoot]/themes/butterfly/layout/includes/sidebar.pug 页面内容
    + 号直接删除 即是正常缩进)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#sidebar
#menu-mask
#sidebar-menus
.avatar-img.is-center
img(src=url_for(theme.avatar.img) onerror=`onerror=null;src='${theme.error_img.flink}'` alt="avatar")
.sidebar-site-data.site-data.is-center
a(href=url_for(config.archive_dir) + '/')
.headline= _p('aside.articles')
.length-num= site.posts.length
a(href=url_for(config.tag_dir) + '/' )
.headline= _p('aside.tags')
.length-num= site.tags.length
- a(href=url_for(config.category_dir) + '/')
- .headline= _p('aside.categories')
- .length-num= site.categories.length
+ a(href="/comments/")
+ .headline= _p('评论')
+ .length-num.icat-pe-comment= _p('0')

hr
!=partial('includes/header/menu_item', {}, {cache: true})
  • 创建 [blogRoot]/source/js/comments.js 文件,并新增以下内容,用来处理获取总评论数量
    (或写在自建的公共 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
fetch('/article.json')
.then(res => res.json())
.then(articleData => {
const urls = Object.keys(articleData);

fetch('{envId}', {
method: "POST",
body: JSON.stringify({ "event": "GET_RECENT_COMMENTS", "accessToken": "{YOUR_TOKEN}", "includeReply": true, "pageSize": -1 }),
headers: { 'Content-Type': 'application/json' }
})
.then(res => res.json())
.then(({ data }) => {
let totalComments = 0;
data.forEach(item => {
totalComments++;
});
const commentElement = document.querySelector('.length-num.icat-pc-comment');
if (commentElement) {
commentElement.innerText = totalComments;
}
const commentElements = document.querySelector('.length-num.icat-pe-comment');
if (commentElements) {
commentElements.innerText = totalComments;
}
console.log('本站Twikoo总评论数:', totalComments);
});
});

// 总评论数量
  • _config.butterfly.yml 主题配置文件中 inject 下的 bottom 引入 comments.js 文件
1
2
3
4
5
6
7
8
9
10
  ···

inject:
head:
- ···
bottom:
- <script async type="text/javascript" src="/js/comments.js"></script>
- ···

···
  • 创建 [blogRoot]/themes/butterfly/scripts/helpers/article-json.js 文件,并新增以下内容,用来生成并处理 article.json 数据文件
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
/**
* MeuiCat
* generate json - article_comments
* developer:meuciat.com
*/

'use strict'

hexo.extend.generator.register('theData', function (locals) {
const postsData = locals.posts
.filter(post => post.path !== '/' && post.comments !== false)
.map(post => {
const link = post.permalink.replace(/^(?:\/\/|[^/]+)*\//, '/');
return {
[link]: post.title || "暂无标题"
};
});

const pagesData = locals.pages
.filter(page => page.path !== '/' && page.comments !== false && !page.source.endsWith('.js') && !page.source.endsWith('.css'))
.map(page => {
const link = page.permalink.replace(/^(?:\/\/|[^/]+)*\//, '/').replace('index.html', '');
return {
[link]: page.title || "暂无标题"
};
});

const combinedData = postsData.concat(pagesData);

const jsonData = combinedData.reduce((acc, obj) => {
const key = Object.keys(obj)[0];
const value = obj[key];
acc[key] = value;
return acc;
}, {});

return {
path: 'article.json',
data: JSON.stringify(jsonData)
};
});

热评弹窗

本小节魔改内容包含 右键菜单 添加热评开关功能,如有需要请移步至 ✨ 本章 - 七小节 | 右键菜单

样式参考 洪佬的 可自行移步张洪Heo

  • 新增 [blogRoot]/themes/butterfly/layout/includes/rightmenu.pug 页面内容
    (**+** 号直接删除 即是正常缩进;注意该内容中 fontawesome 图标 需要自行替换)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    ···

.rightMenu-group.rightMenu-line
a.rightMenu-item(href="javascript:toRandomPost()")
i.iconfont.icat-random
span='随便逛逛'
a.rightMenu-item(href="javascript:rmf.translate();")
i.iconfont.icat-simple-complex
span='繁简转换'
+ a.rightMenu-item(href="javascript:hotreview.switchCommentBarrage();")
+ i.iconfont.icat-danmu
+ span.menu-commentBarrage-text 关闭热评
.rightMenu-group.rightMenu-line
a.rightMenu-item(href="javascript:pjax.loadUrl(\"/privacy/\");")
i.iconfont.icat-conceal
span='隐私协议'
a.rightMenu-item(href="javascript:pjax.loadUrl(\"/cc/\");")
i.iconfont.icat-cc
span='版权协议'
  • 新增 [blogRoot]/themes/butterfly/layout/includes/third-party/comments/index.pug 页面内容
    (**+** 号直接删除 即是正常缩进)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
          ···

when 'Giscus'
#giscus-wrap
when 'Facebook Comments'
.fb-comments(data-colorscheme = theme.display_mode === 'dark' ? 'dark' : 'light'
data-numposts= theme.facebook_comments.pageSize || 10
data-order-by= theme.facebook_comments.order_by || 'social'
data-width="100%")
when 'Remark42'
#remark42
when 'Artalk'
#artalk-wrap

+ .comment-barrage
  • 创建 [blogRoot]/source/js/Barrage.js 文件,并新增以下内容,用来处理跳转和开关变量
    (或写在自建的公共 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
var hotreview = {
switchCommentBarrage:function() {
document.querySelector(".comment-barrage")&&($(".comment-barrage").is(":visible")?($(".comment-barrage").hide(),$(".menu-commentBarrage-text").text("显示热评"),document.querySelector("#consoleCommentBarrage").classList.remove("on"),localStorage.setItem("commentBarrageSwitch","false")):$(".comment-barrage").is(":hidden")&&($(".comment-barrage").show(),$(".menu-commentBarrage-text").text("关闭热评"),document.querySelector("#consoleCommentBarrage").classList.add("on"),localStorage.removeItem("commentBarrageSwitch"))),
rm.hideRightMenu()
},
scrollTo: function(e) {
const t = document.getElementById(e);
if (t) {
const e = t.getBoundingClientRect().top + window.pageYOffset - 80,
o = window.pageYOffset,
n = e - o;
let a = null;
window.requestAnimationFrame((function e(t) {
a || (a = t);
const i = t - a,
l = (c = Math.min(i / 0, 1)) < .5 ? 2 * c * c: (4 - 2 * c) * c - 1;
var c;
window.scrollTo(0, o + n * l),
i < 600 && window.requestAnimationFrame(e)
}))
}
},
}

// 热评弹窗跳转
  • 新建 [blogRoot]/source/css/Barrage.css 样式文件,并新增以下内容
    (也可以在自建的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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
.comment-barrage {
position: fixed;
bottom: 40px;
right: 68px;
display: flex;
flex-direction: column;
justify-content: end;
align-items: flex-end;
z-index: 999;
transition: 0.3s;
user-select: none;
-webkit-user-select: none;
}

@media screen and (max-width: 768px){
.comment-barrage {
display: none!important;
}
}
.comment-barrage-item {
min-width: 286px;
max-width: 286px;
width: fit-content;
min-height: 80px;
max-height: 150px;
margin: 4px;
padding: 8px 14px;
background: var(--icat-maskbgdeep);
border-radius: 8px;
color: var(--icat-fontcolor);
animation: barrageIn 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
transition: 0.3s;
display: flex;
flex-direction: column;
border: var(--style-border-always);
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: blur(20px);
position: fixed;
box-shadow: var(--icat-shadow-border);
overflow: hidden;
}

.comment-barrage-item:hover {
border: 1px solid var(--icat-blue);
box-shadow: var(--icat-shadow-blue);
}

.comment-barrage-item.out{
opacity: 0;
animation: barrageOut 0.6s cubic-bezier(0.42, 0, 0.3, 1.11);
}

.comment-barrage-item.hovered {
opacity: 0;
}

.comment-barrage-item .comment-barrage-close {
color: var(--icat-secondtext);
cursor: pointer;
line-height: 1;
margin: 4px;
}

.comment-barrage-item .comment-barrage-close .icatfont {
font-size: 18px!important;
}

.comment-barrage-item pre {
display: none;
}

.comment-barrage-item li {
display: none;
}

.comment-barrage-item p img:not(.tk-owo-emotion) {
display: none;
}

.comment-barrage-item p img.tk-owo-emotion {
width: 16px;
padding: 0;
margin: 0;
transform: translateY(2px);
}

.comment-barrage-item blockquote {
display: none;
}

.comment-barrage-item br {
display: none;
}

.comment-barrage-item .barrageHead{
height: 30px;
padding: 0;
line-height: 30px;
font-size: 12px;
border-bottom: var(--style-border);
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
padding-bottom: 6px;
}

.comment-barrage-item .barrageHead .barrageTitle {
color: var(--icat-card-bg);
margin-right: 8px;
background: var(--icat-fontcolor);
line-height: 1;
padding: 4px;
border-radius: 4px;
white-space:nowrap;
}

.comment-barrage-item .barrageHead .barrageTitle:hover {
background: var(--icat-blue);
color: var(--icat-white);
}

.comment-barrage-item .barrageAvatar{
width: 18px;
height: 18px;
margin: 0;
margin-left: auto;
margin-right: 8px;
border-radius: 50%;
background: var(--icat-secondbg);
}
.comment-barrage-item .barrageContent{
font-size: 14px!important;
font-weight: normal!important;
height: calc(100% - 30px);
overflow: hidden;
width: fit-content;
max-height: 48px;
}

.comment-barrage-item .barrageContent a {
pointer-events:none;
font-size: 14px!important;
}

.comment-barrage-item .barrageContent::-webkit-scrollbar{
height: 0;
width: 4px;
}
.comment-barrage-item .barrageContent::-webkit-scrollbar-button{
display: none;
}

.barrageHead .comment-barrage-close i {
color: var(--icat-fontcolor);
}

.comment-barrage-item p{
color: var(--icat-fontcolor);
margin: 8px 0 0;
line-height: 1.2;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
font-size: 12px;
font-weight: bold;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}
.comment-barrage-item p:hover,
.barrageHead .comment-barrage-close i:hover {
color: var(--icat-blue);
}

/* 热评弹窗样式 */
  • 创建 [blogRoot]/themes/butterfly/source/js/commentBarrage.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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
var postCommentElement = document.getElementById("post-comment");
if (postCommentElement) {
var commentBarrageConfig = {
//同时最多显示弹幕数
maxBarrage: 1,
//弹幕显示间隔时间ms
barrageTime: 4000,
//twikoo部署地址腾讯云的为环境ID
twikooUrl: "{envId}",
//token获取见上方
accessToken: "{YOUR_TOKEN}",
pageUrl: window.location.pathname,
barrageTimer: [],
barrageList: [],
barrageIndex: 0,
dom: document.querySelector('.comment-barrage'),
}

var commentInterval = null;
var hoverOnCommentBarrage = false;

$(".comment-barrage").hover(function() {
hoverOnCommentBarrage = true;
console.log("热评悬浮");
}, function() {
hoverOnCommentBarrage = false;
console.log("停止悬浮");
});

function initCommentBarrage(){
// console.log("开始创建热评")

var data = JSON.stringify({
"event": "COMMENT_GET",
"commentBarrageConfig.accessToken": commentBarrageConfig.accessToken,
"url": commentBarrageConfig.pageUrl
});
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function() {
if(this.readyState === 4) {
commentBarrageConfig.barrageList = commentLinkFilter(JSON.parse(this.responseText).data);
commentBarrageConfig.dom.innerHTML = '';
}
});
xhr.open("POST", commentBarrageConfig.twikooUrl);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(data);


clearInterval(commentInterval);
commentInterval = null;

commentInterval = setInterval(()=>{
if(commentBarrageConfig.barrageList.length && !hoverOnCommentBarrage){
popCommentBarrage(commentBarrageConfig.barrageList[commentBarrageConfig.barrageIndex]);
commentBarrageConfig.barrageIndex += 1;
commentBarrageConfig.barrageIndex %= commentBarrageConfig.barrageList.length;
}
if((commentBarrageConfig.barrageTimer.length > (commentBarrageConfig.barrageList.length > commentBarrageConfig.maxBarrage?commentBarrageConfig.maxBarrage:commentBarrageConfig.barrageList.length)) && !hoverOnCommentBarrage){
removeCommentBarrage(commentBarrageConfig.barrageTimer.shift())
}
},commentBarrageConfig.barrageTime)
}
function commentLinkFilter(data){
data.sort((a,b)=>{
return a.created - b.created;
})
let newData = [];
data.forEach(item=>{
newData.push(...getCommentReplies(item));
});
return newData;
}
function getCommentReplies(item){
if(item.replies){
let replies = [item];
item.replies.forEach(item=>{
replies.push(...getCommentReplies(item));
})
return replies;
}else{
return [];
}
}
function popCommentBarrage(data){
let barrage = document.createElement('div');
let width = commentBarrageConfig.dom.clientWidth;
let height = commentBarrageConfig.dom.clientHeight;
barrage.className = 'comment-barrage-item'
barrage.innerHTML = `
<div class="barrageHead">
<a class="barrageTitle" href="javascript:hotreview.scrollTo('post-comment')"">热评</a>
<div class="barrageNick">${data.nick}</div>
<img class="barrageAvatar" src="https://cravatar.cn/avatar/${data.mailMd5}"/>
<a class="comment-barrage-close" href="javascript:hotreview.switchCommentBarrage()"><i class="iconfont icat-close"></i></a>
</div>
<a class="barrageContent" href="javascript:hotreview.scrollTo('${data.id}');">${data.comment}</a>
`
commentBarrageConfig.barrageTimer.push(barrage);
commentBarrageConfig.dom.append(barrage);
}
function removeCommentBarrage(barrage){
barrage.className = 'comment-barrage-item out';
setTimeout(()=>{
commentBarrageConfig.dom.removeChild(barrage);
},1000)
}



// 自动隐藏
document.addEventListener('scroll',btf.throttle(function(){
//滚动条高度+视窗高度 = 可见区域底部高度
var visibleBottom = window.scrollY + document.documentElement.clientHeight;
//可见区域顶部高度
var visibleTop = window.scrollY;
// 获取翻页按钮容器
var pagination = document.querySelector('.comment-barrage');
// 获取位置监测容器,此处采用评论区
var eventlistner = document.getElementById('post-comment');
if (eventlistner&&pagination){
var centerY = eventlistner.offsetTop+(eventlistner.offsetHeight/2);
if(document.body.clientWidth > 768){
if(centerY>visibleBottom){
pagination.style.bottom = '40px';
}else{
pagination.style.bottom = '-200px';
}
}
}
}, 200))

initCommentBarrage();

document.addEventListener('pjax:send', function(){
clearInterval(commentInterval);
});

} else {
console.log("iCat提醒您:此页面没有评论!");
}

// 热评弹窗函数
  • _config.butterfly.yml 主题配置文件中 inject 下的 headbottom 分别引入 barragesColor Barrage.css commentBarrage.js Barrage.js
1
2
3
4
5
6
7
8
9
10
11
  ···

inject:
head:
- <link rel="stylesheet" href="/css/Barrage.css"> # 热评弹窗 - 样式
- <style id="barragesColor"></style> # 热评弹窗 - 载体
bottom:
- <script type="text/javascript" src="/js/Barrage.js"></script> # 热评弹窗
- <script type="text/javascript" src="/js/commentBarrage.js" data-pjax=""></script> # 热评弹窗 - 函数处理

···

Token获取

  • 开发人员工具 - 应用 - 本地存储空间 - 你的网址 - twikoo-access-token 里面即可看到对于的值