hexo个人博客优化魔改非插件实现增加说说页面


小波博客好友2broear[1]的说说页面好生热闹,小波也想把廿壴(ganxb2)博客增加一个同样的页面,所以参考他的思路基于hexo+fluid主题纯静态页面非插件实现增加说说页面。

😍廿壴(ganxb2)博客说说页面成品效果

廿壴(ganxb2)博客说说页面成品效果


🙂版本环境

hexo版本:"5.4.2"

hexo-theme-fluid版本:"1.9.0"


🧐思路

新建一个说说分类,然后页面代码就直接循环这个分类的文章,参考2broear的说说页面交互模式,点击回复此片段按钮截取部分内容贴入评论区中引用,然后实现针对当前引用的说说讨论回复。


🤔第一步:命令生成说说页面

1
hexo new page speak

命令会自动在博客根目录source文件夹中增加speak文件夹,然后修改index.md,参考代码如下:

1
2
3
4
5
6
---
title: speak
date: 2022-10-18 17:42:08
layout: speak
lazyload: false
---

注意:

lazyload: false 这是fluid主题为了关闭懒加载,其他主题可删除


🤔第二步:主题languages文件夹中配置多语言

为什么要配置多语言?因为绝大部分主题的导航菜单都是根据多语言这里配置渲染

找到主题文件夹hexo-theme-fluid\languages\zh-CN.yml添加以下代码

1
2
3
4
speak:
menu: '碎碎念'
title: '碎碎念'
subtitle: '碎碎念 - 长夜漫漫,原来晶晶姑娘也睡不着啊?o(*////▽////*)o'

然后去主题配置文件_config.fluid.yml中找到菜单设置增加说说链接,例如小波的设置

1
2
menu:
- { key: "speak", link: "/speak/", icon: "iconfont icon-speakernotes" }

🤔第三步:主题配置文件添加配置

为什么要增加此配置?方便后期扩展,根据配置执行启用或不启用

_config.fluid.yml中增加以下代码

1
2
3
4
5
6
# 碎碎念
speak:
enable: true
banner_img: https://pic.rmb.bdstatic.com/bjh/1ff840ac9cc79a864f42c5da0bc348ff7582.png
banner_img_height: 50
banner_mask_alpha: 0.3

注意:

如果你用的主题没有头图则删除 banner_img,banner_img_height,banner_mask_alpha


🤔第四步:主题layout文件夹中增加同名.ejs

为了避免报错,先建一个空的speak.ejshexo s 确保本地能运行再贴入以下的代码

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
<%
page.layout = "speak"
page.title = theme.speak.title || __('speak.title')
page.subtitle = theme.speak.subtitle || __('speak.subtitle')
page.banner_img = theme.speak.banner_img
page.banner_img_height = theme.speak.banner_img_height
page.banner_mask_alpha = theme.speak.banner_mask_alpha
<!-- 显示条数 -->
let pageSize = 0
<!-- _Query { data: [], length: 0 } -->
let posts = []
let postsDate = []
if(site.categories.length > 0) {
posts = site.categories.sort('-length').filter(function(category,v){
return category.name === "生活" && category.posts
})
postsDate = posts.data[0].posts.sort('-date')
}
%>

<%if (postsDate.length > 0) {%>
<!-- 数组用 for of 对象 for in, 挑出生活的分类,然后再循环不超过10条 -->
<% for (const post of postsDate.data) { %>
<% pageSize++ %>
<% const post_url = url_for(post.path) %>
<% const excerpt = post.categories.data.length >= 2 && post.categories.data[1].name === "碎碎念" ? post.description || post.excerpt || (theme.index.auto_excerpt.enable && post.content) : escape_html(strip_html(post.content).substring(0, 240).trim()).replace(/\n/g, '<br>') %>
<div class="row mx-auto index-card speak-box">
<div class="col-12 col-md-4 m-auto index-img">
<h3 class="index-header">
<a href="<%= post_url %>" target="<%- theme.index.post_url_target %>" rel="bookmark">
<%= post.title %><i></i>
</a>
</h3>
<div class="index-btm post-metas">
<% if(theme.index.post_meta.date ) { %>
<div class="post-meta mr-3 d-flex align-items-center">
<i class="iconfont icon-date"></i>
<time datetime="<%= full_date(post.date, 'YYYY-MM-DD HH:mm') %>" pubdate>
<%= full_date(post.date, 'YYYY-MM-DD HH:mm') %>
</time>
</div>
<% } %>
</div>
<p><a href="javascript: void(0);" role="button" class="btn btn-comment-back" data-title="<%= post.title %>" data-excerpt="<%- escape_html(strip_html(excerpt).substring(0, 80).trim()).replace(/\n/g, ' ') %>">回复此片段</a></p>
</div>
<!-- left end -->

<article class="col-12 col-md-8 mx-auto index-info markdown-body">
<div class="index-excerpt">
<div>
<%- excerpt %>...
</div>
</div>
</article>
<!-- right end -->
</div>

<!-- 第10条跳出 -->
<% if(pageSize > 9) { %>
<% break %>
<% } %>

<% } %>
<% } %>

<!-- site.posts是个对象 -->
<% if(pageSize >= 10) { %>
<p class="text-center"><a href="/categories/生活/">more...</a></p>
<% } %>

<!-- Comments -->
<article id="comments">
<% var type %>
<% if (typeof page.comment === 'string' && page.comment !== '') { %>
<% type = '_partials/comments/' + page.comment %>
<% } else { %>
<% type = '_partials/comments/' + theme.post.comments.type %>
<% } %>
<%- partial(type) %>
</article>

<!-- 主题fluid自带的图片灯箱插件 -->
<% if (theme.post.image_zoom.enable && page.image_zoom !== false) { %>
<%- partial('_partials/plugins/fancybox.ejs') %>
<% } %>

注意:

fluid主题依赖了bootstrap来实现布局,如果你的主题未使用则需要自己写相关布局样式

代码解析:(非fluid主题必须看)

  1. 核心关键是使用hexo的site.categories拿到说说分类下的文章
    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
    // categories对象的结构
    _Query {
    data: [
    _Document {
    name: '生活',
    _id: 'cl9jrrsc10004wkd57jpgh1xt',
    slug: [Getter],
    path: [Getter],
    permalink: [Getter],
    posts: [Getter],
    length: [Getter]
    },
    _Document {
    name: '碎碎念',
    parent: 'cl9jrrsc10004wkd57jpgh1xt',
    _id: 'cl9jrrslr00newkd5a6go4efi',
    slug: [Getter],
    path: [Getter],
    permalink: [Getter],
    posts: [Getter],
    length: [Getter]
    }
    ],
    length: 2
    }
  2. 然后循环.posts渲染说说
    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
    // posts对象的结构
    _Document {
    title: '终话ending,能有多少个5年呢?',
    keywords: '小波生活随笔,廿壴(ganxb2),廿壴(ganxb2)博客',
    index_img: '',
    abbrlink: 46663,
    _content: '',
    source: '_posts/suisuinian/suisuin3.md',
    raw: '',
    slug: 'suisuinian/suisuin3',
    published: true,
    date: Moment<2022-10-22T00:09:40+08:00>,
    updated: Moment<2022-10-22T00:26:41+08:00>,
    comments: true,
    layout: 'post',
    photos: [],
    link: '',
    _id: 'cl9jrrsls00niwkd55hxn2qho',
    content: '',
    site: { data: {} },
    wordcount: 107,
    excerpt: '',
    more: '',
    path: [Getter],
    permalink: [Getter],
    full_source: [Getter],
    asset_dir: [Getter],
    tags: [Getter],
    categories: [Getter],
    prev: _Document {
    __post: true
    },
    next: <ref *2> _Document {
    __post: true
    },
    __post: true
    }

    注意:

    小波因为说说是作为生活分类的2级分类,所以内容变量增加了以下判断代码,如果你说说直接1级分类则不需要再判断截取

    1
    <% const excerpt = post.categories.data.length >= 2 && post.categories.data[1].name === "碎碎念" ? post.description || post.excerpt || (theme.index.auto_excerpt.enable && post.content) : escape_html(strip_html(post.content).substring(0, 240).trim()).replace(/\n/g, '<br>') %>

    修改为以下

    1
    <% const excerpt = post.description || post.excerpt || (theme.index.auto_excerpt.enable && post.content)%>
  3. 评论引入

    其他主题的评论代码可以从关于页面或者友情链接页面找到对应代码贴入替换小波的<!-- coments -->处代码

  4. 图片灯箱放大查看插件

    如果主题已经依赖了fancybox.js,则自己定义json变量把以下代码贴入调用,否则必须先引入fancybox库的js和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
    fn = {
    ...
    // 图片灯箱放大
    fancyBox(selector) {
    if (!CONFIG.image_zoom.enable || !('fancybox' in jQuery)) { return; }

    jQuery(selector || '.markdown-body :not(a) > img, .markdown-body > img').each(function() {
    var $image = jQuery(this);
    var imageUrl = $image.attr('data-src') || $image.attr('src') || '';
    if (CONFIG.image_zoom.img_url_replace) {
    var rep = CONFIG.image_zoom.img_url_replace;
    var r1 = rep[0] || '';
    var r2 = rep[1] || '';
    if (r1) {
    if (/^re:/.test(r1)) {
    r1 = r1.replace(/^re:/, '');
    var reg = new RegExp(r1, 'gi');
    imageUrl = imageUrl.replace(reg, r2);
    } else {
    imageUrl = imageUrl.replace(r1, r2);
    }
    }
    }
    var $imageWrap = $image.wrap(`
    <a class="fancybox fancybox.image" href="${imageUrl}"
    itemscope itemtype="http://schema.org/ImageObject" itemprop="url"></a>`
    ).parent('a');
    if ($imageWrap.length !== 0) {
    if ($image.is('.group-image-container img')) {
    $imageWrap.attr('data-fancybox', 'group').attr('rel', 'group');
    } else {
    $imageWrap.attr('data-fancybox', 'default').attr('rel', 'default');
    }

    var imageTitle = $image.attr('title') || $image.attr('alt');
    if (imageTitle) {
    $imageWrap.attr('title', imageTitle).attr('data-caption', imageTitle);
    }
    }
    });

    jQuery.fancybox.defaults.hash = false;
    jQuery('.fancybox').fancybox({
    loop : true,
    helpers: {
    overlay: {
    locked: false
    }
    }
    });
    },
    ...
    }
    // 调用
    fn.fancyBox();

😚第五步:绑定回复此片段按钮事件

注意:

此方法中变量wlEdit根据使用的评论插件不同替换textareaid,小波用的waline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn = {
...
// 碎碎念回复
commentBack() {
const commentBackBtn = document.querySelectorAll('.btn-comment-back'),
wlEdit = document.querySelector('#wl-edit');

if(commentBackBtn && wlEdit) {
commentBackBtn.forEach(element => {
element.addEventListener("click", (e) => {
wlEdit.focus();
wlEdit.value = ` \n > ${element.getAttribute('data-title')} \n > ${element.getAttribute('data-excerpt')}...`;
// 光标选中位置
wlEdit.setSelectionRange(0,0);
}, false);
});
}
},
...
}
// 调用
// 碎碎念回复
fun.commentBack();

注意:

因为小波用的querySelector获取元素,当评论登录帐号则需要再调用一次,可改成jq获取元素则不用执行以下代码

1
2
3
4
5
// 监控登录再调用碎碎念回复
Fluid.utils.waitElementVisible('#waline .wl-login-info', () => {
// 碎碎念回复
Fluid.plugins.commentBack();
});
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
Fluid.utils = {
listenScroll: function(callback) {
var dbc = new Debouncer(callback);
window.addEventListener('scroll', dbc, false);
dbc.handleEvent();
return dbc;
},
unlistenScroll: function(callback) {
window.removeEventListener('scroll', callback);
},
elementVisible: function(element, offsetFactor) {
offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0;
var rect = element.getBoundingClientRect();
var height = window.innerHeight || document.documentElement.clientHeight;
var top = rect.top;
return (top >= 0 && top <= height * (offsetFactor + 1))
|| (top <= 0 && top >= -(height * offsetFactor) - rect.height);
},
// 等待元素显示
waitElementVisible: function(selectorOrElement, callback, offsetFactor) {
var runningOnBrowser = typeof window !== 'undefined';
var isBot = (runningOnBrowser && !('onscroll' in window))
|| (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent));
if (!runningOnBrowser || isBot) {
return;
}

offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0;

function waitInViewport(element) {
if (Fluid.utils.elementVisible(element, offsetFactor)) {
callback();
return;
}
if ('IntersectionObserver' in window) {
var io = new IntersectionObserver(function(entries, ob) {
if (entries[0].isIntersecting) {
callback();
ob.disconnect();
}
}, {
threshold : [0],
rootMargin: (window.innerHeight || document.documentElement.clientHeight) * offsetFactor + 'px'
});
io.observe(element);
} else {
var wrapper = Fluid.utils.listenScroll(function() {
if (Fluid.utils.elementVisible(element, offsetFactor)) {
Fluid.utils.unlistenScroll(wrapper);
callback();
}
});
}
}

if (typeof selectorOrElement === 'string') {
this.waitElementLoaded(selectorOrElement, function(element) {
waitInViewport(element);
});
} else {
waitInViewport(selectorOrElement);
}
},
waitElementLoaded: function(selector, callback) {
var runningOnBrowser = typeof window !== 'undefined';
var isBot = (runningOnBrowser && !('onscroll' in window))
|| (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent));
if (!runningOnBrowser || isBot) {
return;
}

if ('MutationObserver' in window) {
var mo = new MutationObserver(function(records, ob) {
var ele = document.querySelector(selector);
if (ele) {
callback(ele);
ob.disconnect();
}
});
mo.observe(document, { childList: true, subtree: true });
} else {
document.addEventListener('DOMContentLoaded', function() {
var waitLoop = function() {
var ele = document.querySelector(selector);
if (ele) {
callback(ele);
} else {
setTimeout(waitLoop, 100);
}
};
waitLoop();
});
}
},
}

🙂hexo博客非插件实现增加说说页面相关链接

THE END
hexo个人博客优化魔改非插件实现增加说说页面
https://blog.ganxb2.com/11442.html
作者
chopin gump chopin gump
许可协议
小尾巴
Stay Hungry, Stay Foolish「求知若饥, 虚心若愚」 — 廿壴(ganxb2)
微信

微信

支付宝

支付宝

🕐如果评论显示“刷新”无法加载,过一会再来康康~🕢 _(≧∇≦」∠)_ (填写常用邮箱即可快速收到回复通知~)