开发记录
开发记录
2020-10-22:第一版搭建
实现了侧栏插入
修复了原生botui.js每次跳动到顶部的bug
添加了pjax重载。避免换页消失
</div></div>
2020-10-25:添加按钮
修复了每次进入页面优先跳到页面正中的不良体验。
新增翻转按钮,移除pjax重载。
移除data-pjax,将自动加载改为手动加载。将对话主动权交给用户。
2020-11-16:版本优化
增加butterfly_v3.3.0配置方案。
2020-12-13:版本优化
增加butterfly_v3.4.0配置方案。
2021-1-31:适配3.6.0
- 更新v3.6.0适配方案
botui.js简介
botui.js是一个简单的聊天机器人框架,使用它可以完成简易的脚本对话式交流。缺点是只能在自己设定的逻辑内进行有限问答,而不是像真正的AI那样智能会话。
静态资源下载
**由于本教程涉及的所有修改对缩进格式等有严格要求,担心自己控制不好的可以直接下载静态资源,将压缩包内的butterfly文件夹复制到[Blogroot]\theme\
目录下覆盖现有主题文件夹即可跳过以下教程的前4步,直接到主题配置文件_config.butterfly.yml
中参照第5、6两步修改配置项。如果不希望全局引入侧栏,请读者自行根据自己的主题版本,参看第2步进行修改。
修改步骤
在
~\[blogroot]\themes\butterfly\layout\includes\widget\
目录下新建card_botui.pug
,注意对齐格式。可以自定义修改按钮显示的内容。1
2
3
4
5
6
7
8
9
10
11
12
13.card-widget.card-botui
.card-content(style='height:320px;')
.item-headline
i.fas.fa-comments
span= _p('aside.card_botui')
#hello-测试.botui-app-container(style='width:100%;padding:0.5px')
bot-ui
.facemain
figure
div
span.face 要来和我聊聊么?
span.face
a(href='javascript:void(0);' onclick='botui_init()') 欢迎光临资源宝修改
~\[blogroot]\themes\butterfly\layout\includes\widget\index.pug
,注意对齐格式。对xwcker疑问的解答:
关于include ./card_botui.pug和!=partial(‘includes/widget/card_botui’, {}, {cache:theme.fragment_cache})两种写法的区别,其实本质上都是可以引入card_botui的,最终呈现的效果并无区别。不过后者使用了hexo自带的缓存,能够更快的生成页面。
用于区分是否是butterfly_v3.3.0的关键,在于index.pug中是否存在if is_post()这个判断方法。
1
2
3
4
5
6
7
8
9
10
11
12
13#aside_content.aside_content
if theme.aside.card_author.enable
include ./card_author.pug
.sticky_layout
+ if theme.aside.card_botui.enable
+ include ./card_botui.pug
if theme.aside.card_announcement.enable
include ./card_announcement.pug
if theme.aside.card_recent_post.enable
include ./card_recent_post.pug
if theme.newest_comments.enable
include ./card_newest_comment.pug
if theme.ad && theme.ad.aside此处写法是在站点页和文章页都添加了
card_botui
,只需要文章页有的就只写上面这个。只需要站点页有的就只写下面这个。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#aside_content.aside_content
if theme.aside.card_author.enable
!=partial('includes/widget/card_author', {}, {cache:theme.fragment_cache})
if theme.aside.card_announcement.enable
!=partial('includes/widget/card_announcement', {}, {cache:theme.fragment_cache})
.sticky_layout
if is_post()
if showToc
include ./card_post_toc.pug
+ if theme.aside.card_botui.enable
+ !=partial('includes/widget/card_botui', {}, {cache:theme.fragment_cache})
if theme.aside.card_recent_post.enable
!=partial('includes/widget/card_recent_post', {}, {cache:theme.fragment_cache})
if theme.ad && theme.ad.aside
!=partial('includes/widget/card_ad', {}, {cache:theme.fragment_cache})
else
+ if theme.aside.card_botui.enable
+ !=partial('includes/widget/card_botui', {}, {cache:theme.fragment_cache})
if theme.aside.card_recent_post.enable
!=partial('includes/widget/card_recent_post', {}, {cache:theme.fragment_cache})
if theme.ad && theme.ad.aside
!=partial('includes/widget/card_ad', {}, {cache:theme.fragment_cache})
if theme.newest_comments.enable
!=partial('includes/widget/card_newest_comment', {}, {cache:theme.fragment_cache})
if theme.aside.card_categories.enable
!=partial('includes/widget/card_categories', {}, {cache:theme.fragment_cache})
if theme.aside.card_tags.enable
!=partial('includes/widget/card_tags', {}, {cache:theme.fragment_cache})
if theme.aside.card_archives.enable
!=partial('includes/widget/card_archives', {}, {cache:theme.fragment_cache})
if theme.aside.card_webinfo.enable
!=partial('includes/widget/card_webinfo', {}, {cache:theme.fragment_cache})此处写法是在站点页和文章页都添加了card_botui,只需要文章页有的就只写上面这个。只需要站点页有的就只写下面这个。
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#aside_content.aside_content
//- post
if is_post()
if showToc && theme.toc.style_simple
.sticky_layout
include ./card_post_toc.pug
else
!=partial('includes/widget/card_author', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_announcement', {}, {cache:theme.fragment_cache})
.sticky_layout
if showToc
include ./card_post_toc.pug
+ if theme.aside.card_botui.enable
+ !=partial('includes/widget/card_botui', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_recent_post', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_ad', {}, {cache:theme.fragment_cache})
else
//- page
!=partial('includes/widget/card_author', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_announcement', {}, {cache:theme.fragment_cache})
.sticky_layout
+ if theme.aside.card_botui.enable
+ !=partial('includes/widget/card_botui', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_recent_post', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_ad', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_newest_comment', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_categories', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_tags', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_archives', {}, {cache:theme.fragment_cache})
!=partial('includes/widget/card_webinfo', {}, {cache:theme.fragment_cache})此处写法是在站点页和文章页都添加了card_botui,只需要文章页有的就只写上面这个。只需要站点页有的就只写下面这个。
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#aside_content.aside_content
//- post
if is_post()
if showToc && theme.toc.style_simple
.sticky_layout
include ./card_post_toc.pug
else
!=partial('includes/widget/card_author', {}, {cache: true})
!=partial('includes/widget/card_announcement', {}, {cache: true})
.sticky_layout
if showToc
include ./card_post_toc.pug
+ if theme.aside.card_botui.enable
+ !=partial('includes/widget/card_botui', {}, {cache: true})
!=partial('includes/widget/card_recent_post', {}, {cache: true})
!=partial('includes/widget/card_ad', {}, {cache: true})
else
//- page
!=partial('includes/widget/card_author', {}, {cache: true})
!=partial('includes/widget/card_announcement', {}, {cache: true})
.sticky_layout
+ if theme.aside.card_botui.enable
+ !=partial('includes/widget/card_botui', {}, {cache: true})
!=partial('includes/widget/card_recent_post', {}, {cache: true})
!=partial('includes/widget/card_ad', {}, {cache: true})
!=partial('includes/widget/card_newest_comment', {}, {cache: true})
!=partial('includes/widget/card_categories', {}, {cache: true})
!=partial('includes/widget/card_tags', {}, {cache: true})
!=partial('includes/widget/card_archives', {}, {cache: true})
!=partial('includes/widget/card_webinfo', {}, {cache: true})在
~\[blogroot]\themes\butterfly\source\css\
目录下新建card_botui.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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352@import url(https://fonts.googleapis.com/css?family=Open+Sans);
.botui-container{
font-size:14px;
background-color:#fff;
font-family:"Open Sans",sans-serif
}
.botui-messages-container{
padding:10px 20px
}
.botui-actions-container{
padding:10px 20px
}
.botui-message{
min-height:30px
}
.botui-message-content{
padding:7px 13px;
border-radius:15px;
color:#595a5a;
background-color:#ebebeb
}
.botui-message-content.human{
color:#f7f8f8;
background-color:#919292
}
.botui-message-content.text{
line-height:1.3
}
.botui-message-content.loading{
background-color:rgba(206,206,206,.5);
line-height:1.3;
text-align:center
}
.botui-message-content.embed{
padding:5px;
border-radius:5px
}
.botui-message-content-link{
color:#919292
}
.botui-actions-text-input{
border:0;
outline:0;
border-radius:0;
padding:5px 7px;
font-family:"Open Sans",sans-serif;
background-color:transparent;
color:#595a5a;
border-bottom:1px solid #919292
}
.botui-actions-text-submit{
color:#fff;
width:30px;
padding:5px;
height:30px;
line-height:1;
border-radius:50%;
border:1px solid #919292;
background:#777979
}
.botui-actions-buttons-button{
border:0;
color:#fff;
line-height:1;
cursor:pointer;
font-size:14px;
font-weight:500;
padding:7px 15px;
border-radius:4px;
font-family:"Open Sans",sans-serif;
background:#777979;
box-shadow:2px 3px 4px 0 rgba(0,0,0,.25)
}
.botui-actions-text-select{
border:0;
outline:0;
border-radius:0;
padding:5px 7px;
font-family:"Open Sans",sans-serif;
background-color:transparent;
color:#595a5a;
border-bottom:1px solid #919292
}
.botui-actions-text-searchselect{
border:0;
outline:0;
border-radius:0;
padding:5px 7px;
font-family:"Open Sans",sans-serif;
background-color:transparent;
color:#595a5a;
border-bottom:1px solid #919292
}
.botui-actions-text-searchselect .dropdown-toggle{
border:none
}
.botui-actions-text-searchselect .selected-tag{
background-color:transparent ;
border:0
}
.slide-fade-enter-active{
transition:all .3s ease
}
.slide-fade-enter,.slide-fade-leave-to{
opacity:0;
transform:translateX(-10px)
}
.dot{
width:.5rem;
height:.5rem;
border-radius:.5rem;
display:inline-block;
background-color:#919292
}
.dot:nth-last-child(1){
margin-left:.3rem;
animation:loading .6s .3s linear infinite
}
.dot:nth-last-child(2){
margin-left:.3rem;
animation:loading .6s .2s linear infinite
}
.dot:nth-last-child(3){
animation:loading .6s .1s linear infinite
}
@keyframes loading{
0%{transform:translate(0,0);
background-color:#ababab
}
25%{transform:translate(0,-3px)
}
50%{transform:translate(0,0);
background-color:#ababab
}
75%{transform:translate(0,3px)
}
100%{transform:translate(0,0)}
}
/*
* botui 0.3.9
* A JS library to build the UI for your bot
* https://botui.org
*
* Copyright 2019, Moin Uddin
* Released under the MIT license.
*/
a.botui-message-content-link:focus {
outline: thin dotted
}
a.botui-message-content-link:focus:active, a.botui-message-content-link:focus:hover {
outline: 0
}
form.botui-actions-text {
margin: 0
}
button.botui-actions-buttons-button, input.botui-actions-text-input {
margin: 0;
font-size: 100%;
line-height: normal;
vertical-align: baseline
}
button.botui-actions-buttons-button::-moz-focus-inner, input.botui-actions-text-input::-moz-focus-inner {
border: 0;
padding: 0
}
button.botui-actions-buttons-button {
cursor: pointer;
-webkit-appearance: button
}
.botui-app-container {
width: 100%;
height: 100%;
line-height: 1
}
.botui-container {
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden
}
.botui-message {
margin-top: 10px;
margin-bottom:10px
min-height: 20px
}
.botui-message:after {
display: block;
content: "";
clear: both
}
.botui-message-content {
width: auto;
max-width: 85%;
display: inline-block
}
.botui-message-content.human {
float: right
}
.botui-message-content iframe {
width: 100%
}
.botui-message-content-image {
margin: 2px 0;
display: block;
max-width: 200px;
max-height: 200px
}
.botui-message-content-link {
text-decoration: underline
}
.profil {
position: relative;
border-radius: 50%
}
.profil.human {
float: right;
margin-left: 0
}
.profil.agent {
float: left;
margin-right: 0
}
.profil>img {
width: 26px;
height: 26px;
border: 1px solid #e8e8e8
}
.profil>img.agent {
content: url(http://decodemoji.com/img/logos/blue_moji_hat.svg);
border-radius:50%
}
button.botui-actions-buttons-button{
margin-top:10px;
margin-bottom:10px
}
button.botui-actions-buttons-button:not(:last-child){
margin-right:10px
}
@media (min-width:400px){
.botui-actions-text-submit{
display:none
}
}
.botui.botui-container::-webkit-scrollbar {
width: 0 ;
}
/* 按钮动效 */
.facemain { font-size: 100%; padding: 0; margin: 0;}
/* Reset */
.facemain *,
.facemain *:after,
.facemain *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* Clearfix hack by Nicolas Gallagher: http://nicolasgallagher.com/micro-clearfix-hack/ */
.clearfix:before,
.clearfix:after {
content: " ";
display: table;
}
.clearfix:after {
clear: both;
}
.facemain{
height: 250px;
width:300px;
margin: 0 auto;
border-radius: 10px;
vertical-align:middle;
display:table-cell;
background: #494A5F;
color: #D5D6E2;
font-weight: 500;
font-size: 1.05em;
font-family: "Microsoft YaHei","Segoe UI", "Lucida Grande", Helvetica, Arial,sans-serif;
}
.facemain a{ color: rgba(255, 255, 255, 0.6);outline: none;text-decoration: none;-webkit-transition: 0.2s;transition: 0.2s;}
.facemain a:hover,a:focus{color:#74777b;text-decoration: none;}
.facemain figure {
width: 200px;
height: 60px;
margin: 50px auto;
cursor: pointer;
perspective: 500px;
-webkit-perspective: 500px;
}
.facemain figure div {
height: 100%;
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d;
transition: 0.25s;
-webkit-transition: 0.25s;
}
.facemain figure:hover div {
transform: rotateX(-90deg);
}
.facemain span.face {
width: 100%;
height: 100%;
position: absolute;
box-sizing: border-box;
border: 5px solid #fff;
font-family: 'Source Sans Pro',sans-serif;
line-height: 50px;
font-size: 17pt;
text-align: center;
text-transform: uppercase;
}
.facemain span.face:nth-child(1) {
color: #fff;
transform: translate3d(0, 0, 30px);
-webkit-transform: translate3d(0, 0, 30px);
}
.facemain span.face:nth-child(2) {
color: #094b2c;
background: #fff;
transform: rotateX(90deg) translate3d(0, 0, 30px);
-webkit-transform: rotateX(90deg) translate3d(0, 0, 30px);
}在
~\[blogroot]\themes\butterfly\source\js\
目录下新建botui.js
和botui_init.js
,botui.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
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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421/*
* botui 0.3.9
* A JS library to build the UI for your bot
* https://botui.org
*
* Copyright 2019, Moin Uddin
* Released under the MIT license.
*/
(function (root, factory) {
"use strict";
if (typeof define === 'function' && define.amd) {
define([], function () {
return (root.BotUI = factory(root));
});
} else {
root.BotUI = factory(root);
}
}(typeof window !== 'undefined' ? window : this, function (root, undefined) {
"use strict";
var BotUI = (function (id, opts) {
opts = opts || {};
if(!id) {
throw Error('BotUI: Container id is required as first argument.');
}
if(!document.getElementById(id)) {
throw Error('BotUI: Element with id #' + id + ' does not exist.');
}
if(!root.Vue && !opts.vue) {
throw Error('BotUI: Vue is required but not found.');
}
var _botApp, // current vue instance.
_options = {
debug: false,
fontawesome: true,
searchselect: true
},
_container, // the outermost Element. Needed to scroll to bottom, for now.
_interface = {}, // methods returned by a BotUI() instance.
_actionResolve,
_markDownRegex = {
icon: /!\(([^\)]+)\)/igm, // !(icon)
image: /!\[(.*?)\]\((.*?)\)/igm, // ![aleternate text](src)
link: /\[([^\[]+)\]\(([^\)]+)\)(\^?)/igm // [text](link) ^ can be added at end to set the target as 'blank'
},
_fontAwesome = 'https://use.fontawesome.com/ea731dcb6f.js',
_esPromisePollyfill = 'https://cdn.jsdelivr.net/es6-promise/4.1.0/es6-promise.min.js', // mostly for IE
_searchselect = "https://unpkg.com/vue-select@2.4.0/dist/vue-select.js";
root.Vue = root.Vue || opts.vue;
// merge opts passed to constructor with _options
for (var prop in _options) {
if (opts.hasOwnProperty(prop)) {
_options[prop] = opts[prop];
}
}
if(!root.Promise && typeof Promise === "undefined" && !opts.promise) {
loadScript(_esPromisePollyfill);
}
function _linkReplacer(match, $1, $2, $3) {
var _target = $3 ? 'blank' : ''; // check if '^' sign is present with link syntax
return "<a class='botui-message-content-link' target='" + _target + "' href='" + $2 +"'>" + $1 + "</a>";
}
function _parseMarkDown(text) {
return text
.replace(_markDownRegex.image, "<img class='botui-message-content-image' src='$2' alt='$1' />")
.replace(_markDownRegex.icon, "<i class='botui-icon botui-message-content-icon fa fa-$1'></i>")
.replace(_markDownRegex.link, _linkReplacer);
}
function loadScript(src, cb) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
if(cb) {
script.onload = cb;
}
document.body.appendChild(script);
}
function _handleAction(text) {
if(_instance.action.addMessage) {
_interface.message.human({
delay: 100,
content: text
});
}
_instance.action.show = !_instance.action.autoHide;
}
var _botuiComponent = {
template: '<div class=\"botui botui-container\" v-botui-container><div class=\"botui-messages-container\"><div v-for=\"msg in messages\" class=\"botui-message\" :class=\"msg.cssClass\" v-botui-scroll><transition name=\"slide-fade\"><div v-if=\"msg.visible\"><div v-if=\"msg.photo && !msg.loading\" :class=\"[\'profil\', \'profile\', {human: msg.human, \'agent\': !msg.human}]\"> <img :src=\"msg.photo\" :class=\"[{human: msg.human, \'agent\': !msg.human}]\"></div><div :class=\"[{human: msg.human, \'botui-message-content\': true}, msg.type]\"><span v-if=\"msg.type == \'text\'\" v-text=\"msg.content\" v-botui-markdown></span><span v-if=\"msg.type == \'html\'\" v-html=\"msg.content\"></span> <iframe v-if=\"msg.type == \'embed\'\" :src=\"msg.content\" frameborder=\"0\" allowfullscreen></iframe></div></div></transition><div v-if=\"msg.photo && msg.loading && !msg.human\" :class=\"[\'profil\', \'profile\', {human: msg.human, \'agent\': !msg.human}]\"> <img :src=\"msg.photo\" :class=\"[{human: msg.human, \'agent\': !msg.human}]\"></div><div v-if=\"msg.loading\" class=\"botui-message-content loading\"><i class=\"dot\"></i><i class=\"dot\"></i><i class=\"dot\"></i></div></div></div><div class=\"botui-actions-container\"><transition name=\"slide-fade\"><div v-if=\"action.show\" v-botui-scroll><form v-if=\"action.type == \'text\'\" class=\"botui-actions-text\" @submit.prevent=\"handle_action_text()\" :class=\"action.cssClass\"><i v-if=\"action.text.icon\" class=\"botui-icon botui-action-text-icon fa\" :class=\"\'fa-\' + action.text.icon\"></i> <input type=\"text\" ref=\"input\" :type=\"action.text.sub_type\" v-model=\"action.text.value\" class=\"botui-actions-text-input\" :placeholder=\"action.text.placeholder\" :size=\"action.text.size\" :value=\" action.text.value\" :class=\"action.text.cssClass\" required v-focus/> <button type=\"submit\" :class=\"{\'botui-actions-buttons-button\': !!action.text.button, \'botui-actions-text-submit\': !action.text.button}\"><i v-if=\"action.text.button && action.text.button.icon\" class=\"botui-icon botui-action-button-icon fa\" :class=\"\'fa-\' + action.text.button.icon\"></i> <span>{{(action.text.button && action.text.button.label) || \'Go\'}}</span></button></form><form v-if=\"action.type == \'select\'\" class=\"botui-actions-select\" @submit.prevent=\"handle_action_select()\" :class=\"action.cssClass\"><i v-if=\"action.select.icon\" class=\"botui-icon botui-action-select-icon fa\" :class=\"\'fa-\' + action.select.icon\"></i><v-select v-if=\"action.select.searchselect && !action.select.multipleselect\" v-model=\"action.select.value\" :value=\"action.select.value\" :placeholder=\"action.select.placeholder\" class=\"botui-actions-text-searchselect\" :label=\"action.select.label\" :options=\"action.select.options\"></v-select><v-select v-else-if=\"action.select.searchselect && action.select.multipleselect\" multiple v-model=\"action.select.value\" :value=\"action.select.value\" :placeholder=\"action.select.placeholder\" class=\"botui-actions-text-searchselect\" :label=\"action.select.label\" :options=\"action.select.options\"></v-select> <select v-else v-model=\"action.select.value\" class=\"botui-actions-text-select\" :placeholder=\"action.select.placeholder\" :size=\"action.select.size\" :class=\"action.select.cssClass\" required v-focus><option v-for=\"option in action.select.options\" :class=\"action.select.optionClass\" v-bind:value=\"option.value\" :disabled=\"(option.value == \'\')?true:false\" :selected=\"(action.select.value == option.value)?\'selected\':\'\'\"> {{ option.text }}</option></select> <button type=\"submit\" :class=\"{\'botui-actions-buttons-button\': !!action.select.button, \'botui-actions-select-submit\': !action.select.button}\"><i v-if=\"action.select.button && action.select.button.icon\" class=\"botui-icon botui-action-button-icon fa\" :class=\"\'fa-\' + action.select.button.icon\"></i> <span>{{(action.select.button && action.select.button.label) || \'Ok\'}}</span></button></form><div v-if=\"action.type == \'button\'\" class=\"botui-actions-buttons\" :class=\"action.cssClass\"> <button type=\"button\" :class=\"button.cssClass\" class=\"botui-actions-buttons-button\" v-botui-scroll v-for=\"button in action.button.buttons\" @click=\"handle_action_button(button)\"><i v-if=\"button.icon\" class=\"botui-icon botui-action-button-icon fa\" :class=\"\'fa-\' + button.icon\"></i> {{button.text}}</button></div><form v-if=\"action.type == \'buttontext\'\" class=\"botui-actions-text\" @submit.prevent=\"handle_action_text()\" :class=\"action.cssClass\"><i v-if=\"action.text.icon\" class=\"botui-icon botui-action-text-icon fa\" :class=\"\'fa-\' + action.text.icon\"></i> <input type=\"text\" ref=\"input\" :type=\"action.text.sub_type\" v-model=\"action.text.value\" class=\"botui-actions-text-input\" :placeholder=\"action.text.placeholder\" :size=\"action.text.size\" :value=\"action.text.value\" :class=\"action.text.cssClass\" required v-focus/> <button type=\"submit\" :class=\"{\'botui-actions-buttons-button\': !!action.text.button, \'botui-actions-text-submit\': !action.text.button}\"><i v-if=\"action.text.button && action.text.button.icon\" class=\"botui-icon botui-action-button-icon fa\" :class=\"\'fa-\' + action.text.button.icon\"></i> <span>{{(action.text.button && action.text.button.label) || \'Go\'}}</span></button><div class=\"botui-actions-buttons\" :class=\"action.cssClass\"> <button type=\"button\" :class=\"button.cssClass\" class=\"botui-actions-buttons-button\" v-for=\"button in action.button.buttons\" @click=\"handle_action_button(button)\" autofocus><i v-if=\"button.icon\" class=\"botui-icon botui-action-button-icon fa\" :class=\"\'fa-\' + button.icon\"></i> {{button.text}}</button></div></form></div></transition></div></div>', // replaced by HTML template during build. see Gulpfile.js
data: function () {
return {
action: {
text: {
size: 30,
placeholder: 'Write here ..'
},
button: {},
show: false,
type: 'text',
autoHide: true,
addMessage: true
},
messages: []
};
},
computed: {
isMobile: function () {
return root.innerWidth && root.innerWidth <= 768;
}
},
methods: {
handle_action_button: function (button) {
for (var i = 0; i < this.action.button.buttons.length; i++) {
if(this.action.button.buttons[i].value == button.value && typeof(this.action.button.buttons[i].event) == 'function') {
this.action.button.buttons[i].event(button);
if (this.action.button.buttons[i].actionStop) return false;
break;
}
}
_handleAction(button.text);
var defaultActionObj = {
type: 'button',
text: button.text,
value: button.value
};
for (var eachProperty in button) {
if (button.hasOwnProperty(eachProperty)) {
if (eachProperty !== 'type' && eachProperty !== 'text' && eachProperty !== 'value') {
defaultActionObj[eachProperty] = button[eachProperty];
}
}
}
_actionResolve(defaultActionObj);
},
handle_action_text: function () {
if(!this.action.text.value) return;
_handleAction(this.action.text.value);
_actionResolve({
type: 'text',
value: this.action.text.value
});
this.action.text.value = '';
},
handle_action_select: function () {
if(this.action.select.searchselect && !this.action.select.multipleselect) {
if(!this.action.select.value.value) return;
_handleAction(this.action.select.value[this.action.select.label]);
_actionResolve({
type: 'text',
value: this.action.select.value.value,
text: this.action.select.value.text,
obj: this.action.select.value
});
}
if(this.action.select.searchselect && this.action.select.multipleselect) {
if(!this.action.select.value) return;
var values = new Array();
var labels = new Array();
for (var i = 0; i < this.action.select.value.length; i++) {
values.push(this.action.select.value[i].value);
labels.push(this.action.select.value[i][this.action.select.label]);
}
_handleAction(labels.join(', '));
_actionResolve({
type: 'text',
value: values.join(', '),
text: labels.join(', '),
obj: this.action.select.value
});
}
else {
if(!this.action.select.value) return;
for (var i = 0; i < this.action.select.options.length; i++) { // Find select title
if (this.action.select.options[i].value == this.action.select.value) {
_handleAction(this.action.select.options[i].text);
_actionResolve({
type: 'text',
value: this.action.select.value,
text: this.action.select.options[i].text
});
}
}
}
}
}
};
root.Vue.directive('botui-markdown', function (el, binding) {
if(binding.value == 'false') return; // v-botui-markdown="false"
el.innerHTML = _parseMarkDown(el.textContent);
});
root.Vue.directive('botui-scroll', {
inserted: function (el) {
_container.scrollTop = _container.scrollHeight;
// 弹弹乐问题定位
el.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"});
}
});
root.Vue.directive('focus', {
inserted: function (el) {
el.focus();
}
});
root.Vue.directive('botui-container', {
inserted: function (el) {
_container = el;
}
});
_botApp = new root.Vue({
components: {
'bot-ui': _botuiComponent
}
}).$mount('#' + id);
var _instance = _botApp.$children[0]; // to access the component's data
function _addMessage(_msg) {
if(!_msg.loading && !_msg.content) {
throw Error('BotUI: "content" is required in a non-loading message object.');
}
_msg.type = _msg.type || 'text';
_msg.visible = (_msg.delay || _msg.loading) ? false : true;
var _index = _instance.messages.push(_msg) - 1;
return new Promise(function (resolve, reject) {
setTimeout(function () {
if(_msg.delay) {
_msg.visible = true;
if(_msg.loading) {
_msg.loading = false;
}
}
resolve(_index);
}, _msg.delay || 0);
});
}
function _checkOpts(_opts) {
if(typeof _opts === 'string') {
_opts = {
content: _opts
};
}
return _opts || {};
}
_interface.message = {
add: function (addOpts) {
return _addMessage( _checkOpts(addOpts) );
},
bot: function (addOpts) {
addOpts = _checkOpts(addOpts);
return _addMessage(addOpts);
},
human: function (addOpts) {
addOpts = _checkOpts(addOpts);
addOpts.human = true;
return _addMessage(addOpts);
},
get: function (index) {
return Promise.resolve(_instance.messages[index]);
},
remove: function (index) {
_instance.messages.splice(index, 1);
return Promise.resolve();
},
update: function (index, msg) { // only content can be updated, not the message type.
var _msg = _instance.messages[index];
_msg.content = msg.content;
_msg.visible = !msg.loading;
_msg.loading = !!msg.loading;
return Promise.resolve(msg.content);
},
removeAll: function () {
_instance.messages.splice(0, _instance.messages.length);
return Promise.resolve();
}
};
function mergeAtoB(objA, objB) {
for (var prop in objA) {
if (!objB.hasOwnProperty(prop)) {
objB[prop] = objA[prop];
}
}
}
function _checkAction(_opts) {
if(!_opts.action && !_opts.actionButton && !_opts.actionText) {
throw Error('BotUI: "action" property is required.');
}
}
function _showActions(_opts) {
_checkAction(_opts);
mergeAtoB({
type: 'text',
cssClass: '',
autoHide: true,
addMessage: true
}, _opts);
_instance.action.type = _opts.type;
_instance.action.cssClass = _opts.cssClass;
_instance.action.autoHide = _opts.autoHide;
_instance.action.addMessage = _opts.addMessage;
return new Promise(function(resolve, reject) {
_actionResolve = resolve; // resolved when action is performed, i.e: button clicked, text submitted, etc.
setTimeout(function () {
_instance.action.show = true;
}, _opts.delay || 0);
});
};
_interface.action = {
show: _showActions,
hide: function () {
_instance.action.show = false;
return Promise.resolve();
},
text: function (_opts) {
_checkAction(_opts);
_instance.action.text = _opts.action;
return _showActions(_opts);
},
button: function (_opts) {
_checkAction(_opts);
_opts.type = 'button';
_instance.action.button.buttons = _opts.action;
return _showActions(_opts);
},
select: function (_opts) {
_checkAction(_opts);
_opts.type = 'select';
_opts.action.label = _opts.action.label || 'text';
_opts.action.value = _opts.action.value || '';
_opts.action.searchselect = typeof _opts.action.searchselect !== 'undefined' ? _opts.action.searchselect : _options.searchselect;
_opts.action.multipleselect = _opts.action.multipleselect || false;
if (_opts.action.searchselect && typeof(_opts.action.value) == 'string') {
if (!_opts.action.multipleselect) {
for (var i = 0; i < _opts.action.options.length; i++) { // Find object
if (_opts.action.options[i].value == _opts.action.value) {
_opts.action.value = _opts.action.options[i]
}
}
}
else {
var vals = _opts.action.value.split(',');
_opts.action.value = new Array();
for (var i = 0; i < _opts.action.options.length; i++) { // Find object
for (var j = 0; j < vals.length; j++) { // Search values
if (_opts.action.options[i].value == vals[j]) {
_opts.action.value.push(_opts.action.options[i]);
}
}
}
}
}
if (!_opts.action.searchselect) { _opts.action.options.unshift({value:'',text : _opts.action.placeholder}); }
_instance.action.button = _opts.action.button;
_instance.action.select = _opts.action;
return _showActions(_opts);
},
buttontext: function (_opts) {
_checkAction(_opts);
_opts.type = 'buttontext';
_instance.action.button.buttons = _opts.actionButton;
_instance.action.text = _opts.actionText;
return _showActions(_opts);
}
};
if(_options.fontawesome) {
loadScript(_fontAwesome);
}
if(_options.searchselect) {
loadScript(_searchselect, function() {
Vue.component('v-select', VueSelect.VueSelect);
});
}
if(_options.debug) {
_interface._botApp = _botApp; // current Vue instance
}
return _interface;
});
return BotUI;
}));botui_init.js
这个是整个项目的关键,聊天内容全部在这里进行设计,此处仅以我的项目作为示例,可以参阅botui的github仓库查阅使用文档,或者在我的项目上进行内容修改。
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
134function botui_init() {
var botui = new BotUI("hello-测试");
botui.message.add({
delay: 800,
content: "Hi, 欢迎光临测试の资源宝😊"
}).then(function() {
botui.message.add({
delay: 1100,
content: "我是店长测试😄"
}).then(function() {
botui.message.add({
delay: 1100,
content: "你也可以叫我Aki~😋"
}).then(function() {
botui.action.button({
delay: 1600,
action: [{
text: "我想知道更多关于资源宝的故事!😃",
value: "sure"
}, {
text: "好的,就这样吧,拜拜!🙄",
value: "skip"
}]
}).then(function(a) {
"sure" == a.value && sure();
"skip" == a.value && end()
})
})
})
});
var sure = function() {
botui.message.add({
delay: 600,
content: "🎉🎉🎉🎉🎉🎉"
}).then(function() {
secondpart()
})
},
end = function() {
botui.message.add({
delay: 600,
content: "w(゚Д゚)w 不要走!再看看嘛!"
})
},
secondpart = function() {
botui.message.add({
delay: 5000,
content: "首先呢,很感谢您肯在这里驻足片刻❤️。测试の资源宝是一个个人性质的博客,我会在这里发表各种各样的内容。"
}).then(function() {
botui.message.add({
delay: 15000,
content: "起这个名字是因为想到了安卓的命名方式,安卓历代版本都用甜品的名字命名🍰,例如9是Pineapple cake(菠萝蛋糕)🍰,8是Oreo(奥利奥)🍩,那我干脆就甜到底了。因此可以看到我的分类里面都是糖。之后就发现了一个很纠结的问题,除了巧克力,我想不到其他的不带糖字的糖果。当然了,无伤大雅。才怪咯!超难受的好么!偏偏我那么喜欢巧克力🍫,我是不会把它删掉的。"
}).then(function() {
botui.message.add({
delay: 5000,
content: "分类也有一点我的恶趣味在。👀"
}).then(function() {
botui.message.add({
delay: 8000,
content: "比如巧克力是Ubuntu的教程,棉花糖是windows的教程,糖葫芦就是各种通用教程啦!🎉"
}).then(function() {
botui.message.add({
delay: 5000,
content: "泡泡糖是个人日记哦,流水账一样的,不要看,很羞耻的。😶"
}).then(function() {
botui.message.add({
delay: 4000,
content: "我个人最推荐的是太妃糖版块哦,这里可都是我引以为豪的作品呢💝!马卡龙酌情观看吧,长篇连载对我来说是个挑战,很可能断更。👻"
}).then(function() {
botui.action.button({
delay: 1100,
action: [{
text: "为什么叫测试の资源宝呢?🤔",
value: "why-mashiro"
}]
}).then(function(a) {
thirdpart()
})
})
})
})
})
})
})
},
thirdpart = function() {
botui.message.add({
delay: 1e3,
content: "诶?测试是我的英文名啊😏,资源宝,emm🤔,大概是因为我在现实中也很想开一家资源宝吧。"
}).then(function() {
botui.action.button({
delay: 1500,
action: [{
text: "😲,那英文名为什么叫测试呢?",
value: "why-cat"
}]
}).then(function(a) {
fourthpart()
})
})
},
fourthpart = function() {
botui.message.add({
delay: 3000,
content: "这个是因为我的名字的释义用日文发音,其中有一节是Akira,用英文谐音拼写就是测试了 "
}).then(function() {
botui.message.add({
delay: 3000,
content: "灵感来自于刀剑神域~"
}).then(function() {
botui.action.button({
delay: 1500,
action: [{
text: "方便透露一下真名吗?👀",
value: "why-domain"
}]
}).then(function(a) {
fifthpart()
})
})
})
},
fifthpart = function() {
botui.message.add({
delay: 5000,
content: "emmmm,流水幽吟绕耳边,煦风馨语抚心弦,挥臂欲揽冰钩月,银星斟酌醉人涎~"
}).then(function() {
botui.message.add({
delay: 3000,
content: "只是一介无名小卒而已^_^"
})
})
}
}
修改
~\[blogroot]\_config.butterfly.yml
,注意对齐格式。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
26aside:
enable: true
mobile: false # display on mobile
position: right # left or right
card_author:
enable: true
description:
button:
icon:
text:
link:
+ card_botui:
+ enable: true #侧栏聊天窗口
card_announcement:
enable: true
inject:
head:
# 侧栏聊天窗口
+ - <link rel="stylesheet" href="/css/card_botui.css" />
bottom:
+ # vue.js依赖
+ - <script src="https://npm.elemecdn.com/vue@2.6.11"></script>
+ # 侧栏聊天窗
+ - <script src="/js/botui.js"></script>
+ - <script data-pjax src="/js/botui_init.js"></script>在
~\[blogroot]\themes\butterfly\languages\zh-CN.yml
中添加相应译名1
2
3
4
5
6
7
8
9
10
11
12aside:
articles: 文章
tags: 标签
categories: 分类
Link: 友人帐
+ card_botui: 聊天窗
card_announcement: 告示牌
card_categories: 分类
card_tags: 标签
card_archives: 时间轴
card_recent_post: 最新文章
card_webinfo:
可能遇到的bug
无法显示
- botui.js依赖vue.js,添加依赖即可。(教程已更新相关内容)
1
2
3
4
5inject:
head:
bottom:
+ # vue.js依赖
+ - <script src="https://npm.elemecdn.com/vue@2.6.11"></script>
- botui.js依赖vue.js,添加依赖即可。(教程已更新相关内容)
切换页面侧栏就变成空白
- 添加pjax重载(仅限于butterfly主题)
1
2
3
4
5inject:
head:
bottom:
- - <script src="/js/botui_init.js"></script>
+ - <script data-pjax src="/js/botui_init.js"></script>
- 添加pjax重载(仅限于butterfly主题)
Use this card to join the candyhome and participate in a pleasant discussion together .
Welcome to 测试's candyhome,wish you a nice day .