hexo个人博客优化魔改非插件实现增加说说页面
🙂前言
小波博客好友2broear的说说页面好生热闹,小波也想把廿壴博客增加一个同样的页面,所以参考他的思路基于hexo+fluid主题纯静态页面非插件实现增加说说页面。
😍廿壴博客说说页面成品效果
🙂版本环境
hexo版本:"5.4.2"
hexo-theme-fluid版本:"1.9.0"
🧐思路说明
新建一个说说分类,然后页面代码就直接循环这个分类的文章,参考2broear的说说页面交互模式,点击回复此片段按钮截取部分内容贴入评论区中引用,然后实现针对当前引用的说说讨论回复。
🤔第一步:命令生成说说页面
命令会自动在博客根目录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.ejs
,hexo 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>
<article class="col-12 col-md-8 mx-auto index-info markdown-body"> <div class="index-excerpt"> <div> <%- excerpt %>... </div> </div> </article> </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主题必须看)
核心关键是使用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
| _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 }
|
然后循环.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
| _Document { title: '终话ending,能有多少个5年呢?', keywords: '小波生活随笔,廿壴(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)%>
|
评论引入
其他主题的评论代码可以从关于页面或者友情链接页面找到对应代码贴入替换小波的<!-- coments -->
处代码
图片灯箱放大查看插件
如果主题已经依赖了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
根据使用的评论插件不同替换textarea
的id
,小波用的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博客非插件实现增加说说页面相关链接
『旅行者』,帮小波关注一波公众号吧。
小波需要100位关注者才能申请红包封面设计资格,万分感谢!
关注后可微信小波,前66的童鞋可以申请专属红包封面设计。
微信
支付宝