Utterances 实现博客评论功能
Utterances利用github上issue的功能,来完成对评论的存储和分类,映射到不同的博客文章url上。
在标准的html-js网站中,只需要在对应的github仓库安装 utterances GitHub app ,再在需要评论的页面引入下面脚本即可。
<script src="https://utteranc.es/client.js"
repo="[ENTER REPO HERE]"
issue-term="pathname"
label="comment"
theme="github-light"
crossorigin="anonymous"
async>
</script>
mdbook 覆盖主题
由于mdbook是用handlebars来写模板页面的,想要评论主题随着博客主题同时变化所以还需进一步操作。
mdbook有theme覆盖的功能,即可以用同名的文件来覆盖原有的前端代码。
使用mdbook init --theme
生成包含theme文件夹的初始工程,之后把其中的theme文件夹复制到当前的博客目录中,在book.toml
中指定用此文件夹来覆盖原有的theme。我们只需变动index.hbs
文件,所以theme目录中的其他文件可以删除了。再创建一个用于增加评论的脚本文件comments.js
。
[output.html]
theme = "theme"
additional-js = ["theme/comments.js"]
js实现
comments.js
主要根据当前的博客主题动态地生成引入utterances的<script>
标签。loadComments函数实现了这个功能。
function loadComments() {
// console.log("loading comments.");
const page = document.querySelector(".page");
const isLight = document.querySelector('html').getAttribute('class').indexOf('light') != -1;
const commentScript = document.createElement('script')
const commentsTheme = isLight ? 'github-light' : 'github-dark'
commentScript.async = true
commentScript.src = 'https://utteranc.es/client.js'
commentScript.setAttribute('repo', 'Sugar-Coder/Sugar-Coder.github.io')
commentScript.setAttribute('issue-term', 'pathname')
commentScript.setAttribute('id', 'utterances')
commentScript.setAttribute('label', 'comment')
commentScript.setAttribute('theme', commentsTheme)
commentScript.setAttribute('crossorigin', 'anonymous')
page.appendChild(commentScript);
}
loadComments();
为了监听用户改变博客主题,使用 MutationObserver 来监听html的class属性变动。如果发生了从明亮主题到暗色主题的变动,那么就重新加载comments。
function removeComments() {
const page = document.querySelector(".page");
page.removeChild(page.lastChild);
}
(function observeChange() {
const html=document.querySelector('html')
const options={
attributes:true,//观察node对象的属性
attributeFilter:['class']//只观察class属性
}
let prevIsLight = document.querySelector('html').getAttribute('class').indexOf('light') != -1;
var mb=new MutationObserver(function(mutationRecord,observer){
let isLight = document.querySelector('html').getAttribute('class').indexOf('light') != -1;
// console.log(`prevIsLight:${prevIsLight}, isLight:${isLight}`)
if (prevIsLight != isLight) {
removeComments();
loadComments();
prevIsLight = isLight;
}
})
mb.observe(html,options)
})();
这样就实现了动态评论主题。
使用基于React的方式增加utterances
Note: 这个方法现在已经不用了,多引入了很多依赖,我现在使用上面的纯js方法来完成评论的生成。
向基于react构建的博客加入utterances可以参考这片文章。
Step1: Add a DOM Container to the HTML
在index.hbs
中增加一个空的 <div>
容器,来放React生成的元素。
<div id="content" class="content">
<!-- rendering post content -->
</div>
<!-- react DOM container -->
<div id="react-app"></div>
我把上面这个DOM Container放到了#content
的同级位置,让评论能在文章内容底部出现。
Step2: Add the Script Tags
为了使用React,就需要一些依赖脚本,首先是react和react-dom。
babel是为了编译包含JSX语法的js文件(post_footer.js),如果不加babel,就会出现unexpected token的报错。
第四个script就是引入自己写的脚本,这个地方用了handlebars的语法来增加所有在book.toml中配置的additional_js文件。
{{!-- The react --}}
<!-- Load React. -->
<!-- Note: when deploying, replace "development.js" with "production.min.js". -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<!-- Babel Script -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- Custom JS scripts -->
{{#each additional_js}}
<script type="text/jsx" src="{{ ../path_to_root }}{{this}}"></script>
{{/each}}
Step3: Create a React Component
由于使用<script>
方式引入的React在全局作用域中,在post_footer.js中就可以直接使用React了。
首先找到要用React的<div>
容器,在这个容器中渲染要加入的元素PostFooter。
const e = React.createElement;
const domContainer = document.querySelector('#react-app');
const root = ReactDOM.createRoot(domContainer);
root.render(e(PostFooter));
接着来定义PostFooter这个React Component。 首先定义组成PostFooter的每一个comment,用React的forwardRef来定义,似乎是为了组件复用,在父组件中引用。
const Comment = React.forwardRef((props, commentBox) => {
return <div ref={commentBox} className="comments" />
});
之后就生成引入utterances的<script>
标签。该标签的属性可以根据当前的theme改变,我这边是用html标签的class属性是否包含light关键字来判断的。
因为希望评论的主题和博客的主题保持一致,所以希望在这个react组建加载的时候进行判断,完成对应的评论主题生成。使用useEffect来完成组件加载时的执行逻辑。 useEffect函数最后返回的是用于清空当前渲染出来的组件的。
这种方法现在只能通过切换url来完成评论主题的更改,不能在更改博客主题时马上更改评论主题。
const PostFooter = () => {
const commentBox = React.createRef();
const isLight = document.querySelector('html').getAttribute('class').indexOf('light') != -1;
React.useEffect(() => {
const commentScript = document.createElement('script')
const commentsTheme = isLight ? 'github-light' : 'github-dark'
commentScript.async = true
commentScript.src = 'https://utteranc.es/client.js'
commentScript.setAttribute('repo', 'Sugar-Coder/Sugar-Coder.github.io')
commentScript.setAttribute('issue-term', 'pathname')
commentScript.setAttribute('id', 'utterances')
commentScript.setAttribute('label', 'comment')
commentScript.setAttribute('theme', commentsTheme)
commentScript.setAttribute('crossorigin', 'anonymous')
if (commentBox && commentBox.current) {
commentBox.current.appendChild(commentScript)
} else {
console.log(`Error adding utterances comments on: ${commentBox}`)
}
const removeScript = () => {
commentScript.remove();
document.querySelectorAll(".utterances").forEach(el => el.remove());
};
return () => {
removeScript();
};
}, [])
return (
<>
<Comment ref={commentBox} />
</>
)
}