Halo主题定制——寒山启用DisqusJS
寒山是我迁移到Halo后用的第二款主题(第一款当然就是Halo自带的Anatole啦),也是我目前为止最满意的一款主题。只是美中不足的是这款主题不支持DisqusJS,有点不习惯,于是乎就搞上了这玩意。
这里说的主题是寒山的v1.4.3
项目主页在这里
首先为评论插件创建dsqjs对象 #
将module/comment.ftl原本的<script>和</script>标签之间的内容全部注释或者删掉,然后创建loadDisqusJS()函数,并通过setTimeout延时调用loadDisqusJS()函数。
修改后如下
<#macro comment target,type>
<#if !post.disallowComment!false>
<!-- DisqusJS jsDelivr -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/disqusjs.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/disqus.js"></script>
<script>
function loadDisqusJS() {
var dsqjs = new DisqusJS({
shortname: '',
siteName: '',
identifier: document.location.origin + document.location.pathname + document.location.search,
url: document.location.origin + document.location.pathname + document.location.search,
title: document.title,
api: '',
apikey: '',
admin: '',
adminLabel: ''
});
}
setTimeout(function(){ loadDisqusJS(); }, 2000);
</script>
<div id="disqus_thread" style="margin: 30px;"></div>
</#if>
</#macro>
其中disqus_thread容器的位置决定了Disqus/DisqusJS出现的位置。
至于margin嘛,可以换其他数值,数值越大,评论框旁边的空隙就越大,评论框自然就越小,可以通过这种办法控制评论框大小。
这上面的代码还引入了DisqusJS的资源,不过这些没啥介绍的,我就不多说了。
下面是需要配置的内容 #
这个我是直接在DisqusJS项目主页剽窃的(小声)
shortname {string}
- 你的 Disqus Forum 的 shortname,你可以在 Disqus Admin - Settings - General - Shortname 获取你的 shortname
- 必须,无默认值
siteName {string}
- 你站点的名称,将会显示在「评论基础模式」的 header 中;该配置应该和 Disqus Admin - Settings - General - Website Name 一致
- 非必须,无默认值
identifier {string}
- 当前页面的 identifier,用来区分不同页面
- 建议,默认值为
document.location.origin + document.location.pathname + document.location.search
url {string}
- 当前页面的 URL,Disqus 的爬虫会爬取该 URL 获取页面相关信息
- 建议,默认值为
document.location.origin + document.location.pathname + document.location.search
title {string}
- 当前页面的标题,如果没有设置默认为当前页面的标题。当页面标题中有其他信息(比如站点名称)而不想在 Disqus 中展示时,可以设置此项。
- 非必须,默认值为
document.title
api {string}
- DisqusJS 请求的 API Endpoint,通常情况下你应该配置一个 Disqus API 的反代并填入反代的地址。你也可以直接使用 DISQUS 官方 API 的 Endpoint
https://disqus.com/api/,或是使用我搭建的 Disqus API 反代 Endpointhttps://disqus.skk.moe/disqus/。如有必要可以阅读关于搭建反代的 相关内容 - 建议,默认值为
https://disqus.skk.moe/disqus/
apikey {string || Array}
- DisqusJS 向 API 发起请求时使用的 API Key,你应该在配置 Disqus Application 时获取了 API Key
- DisqusJS 支持填入一个 包含多个 API Key 的 Array,在每次请求时会随机使用其中一个;如果你只填入一个 API Key,可以填入 string 或 Array。
- 必填,无默认值
以下配置和 Disqus Moderator Badge 相关,缺少一个都不会显示 Badge
admin {string}
- 你的站点的 Disqus Moderator 的用户名(也就是你的用户名)。你可以在 Disqus - Settings - Account - Username 获取你的 Username
- 非必须,无默认值
adminLabel {string}
- 你想显示在 Disqus Moderator Badge 中的文字。该配置应和 Disqus Admin - Settings - Community - Moderator Badge Text 相同
- 非必须,无默认值
你看,到这里基本上是可以使用了。

但如果你开启了全站PJAX,你会惊奇的发现,评论框在切换页面时并不会刷新。怎么办呢?难道只能关掉PJAX了吗?
PJAX支持 #
我们可以在module/script.ftl增加刷新的内容
找到// 重新加载 评论那一部分代码,将以下内容全部注释或删掉
// 重新加载 评论
$('script[data-pjax-comment]').each(function () {
$(this).parent().append($(this).remove());
});
if ($("#page").find('.post-page').length > 0) {
window.removeEventListener('scroll', post.tocScroll, false);
上面这些代码原本是为原生评论系统服务的,目的就是为了在PJAX刷新时可以自动重载评论框。
当然我们使用DisqusJS的话那就用不着上面这些代码了,直接调用一个函数就够了
loadDisqusJS();
修改后的module/script.ftl如下
<#include "mermaid.ftl">
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<script src="${theme_base!}/assets/media/scripts/plugins.min.js?ver=${.now?long}"></script>
<script src="${theme_base!}/assets/media/scripts/main.min.js?ver=${.now?long}"></script>
<script src="//cdn.jsdelivr.net/npm/[email protected]/velocity.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/[email protected]/velocity.ui.min.js"></script>
<#if settings.auto_night_mode>
<script src="//cdn.jsdelivr.net/gh/hshanx/[email protected]/dist/halo-comment.min.js"></script>
<#else>
<script src="${options.comment_internal_plugin_js!'//cdn.jsdelivr.net/gh/hshanx/[email protected]/dist/halo-comment.min.js'}"></script>
</#if>
<#if settings.Aplayer?? && settings.Aplayer != ''>
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/APlayer.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script>
<#else>
<script type="text/javascript">
// Smooth scroll to anchors
var scroll = new SmoothScroll('[data-scroll]', {
speed: 300,
updateURL: false,
})
</script>
</#if>
<#-- 暗夜模式 -->
<#if settings.auto_night_mode!true>
<script type="text/javascript">
var nightModeStartTime = ${settings.night_mode_start_time?default('18')};
var nightModeEndTime = ${settings.night_mode_end_time?default('6')};
</script>
<script src="${theme_base!}/assets/media/scripts/night-mode.min.js?ver=${.now?long}"></script>
</#if>
<#if settings.visit_statistics!false>
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
</#if>
<#-- katex-->
<#if settings.enabled_mathjax!true>
<script defer src="//cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js"></script>
<script defer src="//cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js"
onload="if (document.getElementById('post-content') ) {renderMathInElement(document.getElementById('post-content'),katex_config)}"></script>
</#if>
<#-- gallery -->
<#--<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/js/lightgallery.min.js"></script>-->
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/js/jquery.justifiedGallery.min.js"></script>
<!--图片预览插件-->
<script data-pjax-viewer src="//cdn.jsdelivr.net/npm/[email protected]/dist/viewer.min.js"></script>
<script data-gallery src="${theme_base!}/assets/media/scripts/gallery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/highlightjs-line-numbers.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/js/social-share.min.js"></script>
<div class="qr-code-wrap" role="dialog">
<div role="document" class="qr-code" style="transform-origin: 201px 294px;">
<span class="closinglayer"><svg viewBox="64 64 896 896" focusable="false" class="" data-icon="close" width="1em"
height="1em" fill="currentColor" aria-hidden="true"><path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg>
</span>
<div style="text-align: center;padding: 10px 0;">
<#if settings.QR_code_zfb??>
<img class="qr_code_zfb" src="${settings.QR_code_zfb!}"/>
</#if>
<#if settings.QR_code_wx??>
<img class="qr_code_wx" src="${settings.QR_code_wx!}"/>
</#if>
</div>
<#if settings.QR_code_zfb?? && settings.QR_code_wx??>
<div class="switch-btn">
<span class="zfb-btn">支付宝</span>
<span class="wx-btn">微信</span>
</div>
</#if>
</div>
</div>
<#--目录-->
<#if settings.post_toc!true>
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/tocbot.min.js"></script>
</#if>
<script type="application/javascript">
var displayReadProgress = <#if (settings.open_read_progress)??>${settings.open_read_progress?c}<#else>true</#if>;
</script>
<script src="${theme_base!}/assets/media/scripts/post.min.js?ver=${.now?long}"></script>
<style>
/* 阅读进度的进度条颜色 */
#readProgress .read-progress-bar {
background: ${settings.progress_color?default('#2474b5')} !important;
height: 0.1875rem;
}
</style>
<#if settings.TimeStatistics??>
<script type="text/javascript">
// 建站时间统计
function show_date_time() {
if ($("#span_dt_dt").length > 0) {
window.setTimeout("show_date_time()", 1000);
BirthDay = new Date("${settings.TimeStatistics!}");
today = new Date();
timeold = (today.getTime() - BirthDay.getTime());
sectimeold = timeold / 1000;
secondsold = Math.floor(sectimeold);
msPerDay = 24 * 60 * 60 * 1000;
e_daysold = timeold / msPerDay;
daysold = Math.floor(e_daysold);
e_hrsold = (e_daysold - daysold) * 24;
hrsold = Math.floor(e_hrsold);
e_minsold = (e_hrsold - hrsold) * 60;
minsold = Math.floor((e_hrsold - hrsold) * 60);
seconds = Math.floor((e_minsold - minsold) * 60);
span_dt_dt.innerHTML = daysold + "天" + hrsold + "小时" + minsold + "分" + seconds + "秒";
}
}
show_date_time();
</script>
</#if>
<#if settings.Custom_js_foot??>
<script type="text/javascript">
${settings.Custom_js_foot!}
</script>
</#if>
<#if settings.Custom_js_foot_src??>
${settings.Custom_js_foot_src!}
</#if>
<#if settings.pjax_enabled!false>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/pjax.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/nprogress.min.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/[email protected]/nprogress.min.css">
<script>
var socialDisabled = '${settings.share_disabeld?default('')}';
var pjax = new Pjax({
elements: 'a[href]:not([href^="#"]):not([data-not-pjax]), form', // default is "a[href], form[action]"
cacheBust: false,
debug: false,
selectors: [
'title',
'#page'
]
});
//在Pjax请求开始后触发
document.addEventListener('pjax:send', function () {
NProgress.start();
});
//在Pjax请求完成后触发
document.addEventListener('pjax:complete', function (e) {
NProgress.done();
// 加载相册
if ($("#page").find('.photos-page').length > 0) {
photo.loadGallery();
// $('script[data-pjax-viewer]').each(function () {
// $(this).remove()
// });
}
han.initLazyLoad();
// 整个页面延迟加载
han.lazyLoad();
// card 延迟加载
han.lazyLoadCardItem()
//重载
if (typeof _hmt !== 'undefined') {
// support 百度统计
_hmt.push(['_trackPageview', location.pathname + location.search]);
}
if (typeof ga !== 'undefined') {
// support google analytics
ga('send', 'pageview', location.pathname + location.search);
}
// 菜单高亮
han.highlightMenu();
// 小屏幕菜单隐藏
han.makeMenuInvisible();
// 关闭搜索框
$(".search-popup").velocity("transition.expandOut", { duration: 300 });
// 重新加载 评论
loadDisqusJS();
// 赞赏
post.appreciate();
// 初始化toc
post.initToc()
// 删除文章第一个 <ul>
post.removeFirstUL()
// 目录事件
post.scrollTocFixed();
// 搞一个阅读进度,为了提高准确度,数据都要实时获取
post.readProgress();
// 代码块
post.loadHighlight();
// 按钮事件
post.appreciateModel()
// 分享
post.toggleSocialShare()
// 图片预览
post.initViewer()
// 目录悬浮时间
post.tocHover();
try {
post.shareIcon()
if (renderMathInElement && typeof renderMathInElement !== 'undefined') {
renderMathInElement(document.getElementById('post-content'), katex_config);
}
if (mermaid && typeof mermaid !== 'undefined') {
mermaid.initialize();
}
} catch (e) {
console.log("error");
}
// 刷新
han.refreshLazyLoad();
} else {
han.initLazyLoad()
}
});
document.addEventListener('pjax:end', function () {
});
//Pjax请求失败后触发,请求对象将作为一起传递event.options.request
document.addEventListener('pjax:error', function () {
NProgress.done();
bar('系统出现问题,请手动刷新一次', '3000');
});
</script>
</#if>
<script type="text/javascript">
//console.clear();
console.log("%c 有朋自远方来, 不亦说乎.", "background:#24272A; color:#ffffff", "");
console.log("%c Github %c", "background:#24272A; color:#ffffff", "", "https://github.com/hshanx");
console.log("%c 版本号: %c", "background:#24272A; color:#ffffff", "", "1.4.2");
</script>
这样,PJAX只需通过重新加载loadDisqusJS()函数,就可以在加载新页面时刷新评论框。
当然,我上面说的“重新加载”,并不是真正意义上的重载。JavaScript并没有真正意义上的重载函数。我上面的操作主要是用loadDisqusJS()来覆盖loadDisqusJS(),即后面的会覆盖前面的函数。老实说其实还有其他更优雅的方法。但是对于这种需求,上面的方案就足够了,为了避免跑题这里就不展开说了。
嘛,还有个小Bug #

我只是演示而已,蝉時雨大佬应该不会介意吧(害怕.webp)。
标签的文字和背景颜色融在一起,导致评论里啥都看不清了,这很显然是不行的。解决办法也很简单,在切换模式后重载Disqus就好,我们接下来就解决这个问题。
修改night-mode.js #
在assets/scripts/night-mode.js里面将nightMode.click函数里面这些代码注释或者删掉,这些代码是为Halo原生评论系统服务的,目的是为了在切换模式时重载评论框避免上面的情况发生。
if (typeof renderComment === 'function') {
renderComment();
}
基于上面的代码逻辑,我们用DisqusJS的话,可以做得更简单,只需换成
// 重新加载DisqusJS,延时500ms
setTimeout(function(){ loadDisqusJS(); }, 500);
即可。
修改后的night-mode.js如下
var nightModeId = 'nightMode';
var darkMode = {
autoNightMode: function () {
var nightModes = $('.night-mode');
var day = new Date();
var D = day.getHours();
var isNightMode = hanUtils.getLocalStorage(nightModeId);
if (D <= nightModeStartTime && D > nightModeEndTime) {
// 白天
if (isNightMode === true) {
// 是暗黑模式
darkMode.changeNightMode(nightModes);
return;
}
darkMode.changeLightMode(nightModes);
} else {
// 晚上
if (isNightMode === false) {
// 不是暗黑模式
darkMode.changeLightMode(nightModes);
return;
}
darkMode.changeNightMode(nightModes);
}
if (typeof renderComment === 'function') {
renderComment();
}
},
changeLightMode: function (nightModes) {
$(document.body).removeClass('night');
for (var i = 0; i < nightModes.length; i++) {
var nightMode = $(nightModes[i]);
nightMode.addClass('fa-moon-o');
nightMode.removeClass('fa-lightbulb-o');
}
hanUtils.setLocalStorage(nightModeId, false)
},
changeNightMode: function (nightModes) {
$(document.body).addClass('night');
for (var i = 0; i < nightModes.length; i++) {
var nightMode = $(nightModes[i]);
nightMode.addClass('fa-lightbulb-o');
nightMode.removeClass('fa-moon-o');
}
hanUtils.setLocalStorage(nightModeId, true)
},
nightModeFuc: function () {
var nightModes = $('.night-mode');
if (!nightModes) {
return;
}
for (var i = 0; i < nightModes.length; i++) {
var nightMode = $(nightModes[i]);
darkMode.doFuncNightMode(nightMode);
}
},
doFuncNightMode: function (nightMode) {
var nightModeBtn = $('.night-mode');
if ($(document.body).hasClass('night')) {
nightModeBtn.addClass('fa-lightbulb-o');
nightModeBtn.removeClass('fa-moon-o');
} else {
nightModeBtn.addClass('fa-moon-o');
nightModeBtn.removeClass('fa-lightbulb-o');
}
nightMode.click(function (e) {
if (nightMode.hasClass('fa-moon-o')) {
$(document.body).addClass('night');
nightModeBtn.addClass('fa-lightbulb-o');
nightModeBtn.removeClass('fa-moon-o');
hanUtils.setLocalStorage(nightModeId, true);
} else if (nightMode.hasClass('fa-lightbulb-o')) {
$(document.body).removeClass('night');
nightModeBtn.addClass('fa-moon-o');
nightModeBtn.removeClass('fa-lightbulb-o');
hanUtils.setLocalStorage(nightModeId, false);
}
$(document.body).removeClass('sidebar-opened');
// 重新加载DisqusJS
setTimeout(function(){ loadDisqusJS(); }, 500);
})
}
}
$(function () {
// 自动暗黑模式
darkMode.autoNightMode();
// 暗黑模式
darkMode.nightModeFuc();
})
修改好后记得压缩代码并覆盖night-mode.min.js里面的内容,不然是不会生效的。因为页面引入的是night-mode.min.js,而不是night-mode.js。night-mode.js只是编辑用的而已。
保存后刷新就好了 #

也是演示
为什么是500毫秒? #
主要是为了在页面变色完成之前避免遇到DisqusJS抢先加载的情况,当然这里的时间可以调得更短,或者用事件检测器也行。还有开头module/comment.ftl那里的setTimeout(function(){ loadDisqusJS(); }, 2000);也是为了应对这种情况。
关于DisqusJS的夜间模式 #
又是一个大坑,过几天等我有时间再填吧(咕)。
现在DisqusJS的功能是齐全的,包括Disqus的夜间模式。但美中不足的就是DisqusJS的夜间模式还是不行,目前还没有解决办法。暂时只能先禁用夜间模式。