1 问题

评论系统是博客的关键组件,Hugo 支持若干个评论系统,包括流行的 Disqus, 基于 GitHub 的 GiscusUtteranc, 以及其他评论系统

博客使用的评论系统是 Utteranc, 主题是 PaperMod, PaperMod 支持 dark 和 light 两种主题, 在初始化 Utteranc 时可以指定 theme, 如 Github-Light

1
2
3
4
5
6
7
<script src="https://utteranc.es/client.js"
        repo="ramsayleung/comment"
        issue-term="title"
        theme="github-light"
        crossorigin="anonymous"
        async>
</script>

但是 theme 在初始化时就指定好了,那么在博客切换到 dark theme 的时候, Utteranc 也不会自适应 dark theme,博客的theme与评论theme就不一致:

2 解决思路

解决思路其实很简单,就获取当前的 theme, 然后再初始化 Utteranc 对应的 theme; 再在用户切换 theme 之后,再重新初始化 Utteranc.

2.1 获取当前Theme

Hugo 并没有提供标准接口来获取当前主题, 虽然可以通过以下的方式来获取的 theme:

1
2
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
console.log(isDarkMode ? 'dark' : 'light');

但是这个是通过编译出来的CSS来判断当前theme,用户一旦手动切换了 theme, 上面的代码就无法生效了.

每个Hugo主题定义的方式可能还不一样, 以 PaperMod 为例,观察之后发现,light theme的时候body的html 为:

1
2
<body id = "top" class="">
</body>

dark theme 的时候 class 就变为了 class="dark":

1
2
<body id = "top" class="">
</body>

所以可以通过判断 body 是否包含 dark 的 class 来判断当前是否为 dark theme.

1
2
3
4
// 不同的Hugo theme可能会需要不同的判断方式
function getCurrentTheme() {
    return document.body.classList.contains('dark') ? 'dark' : 'light';
}

2.2 初始化评论系统

Utteranc 文档提供的启用评价系统代码如下:

1
2
3
4
5
6
7
<script src="https://utteranc.es/client.js"
        repo="[ENTER REPO HERE]"
        issue-term="pathname"
        theme="github-light"
        crossorigin="anonymous"
        async>
</script>

因为我们需要在切换 Theme 时重新加载 Utteranc, 所以就需要通过 Javascript 来实现上面的HTML功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function loadUtterances(darkMode=false) {
    const commentContainer = document.getElementById("comments-utteranc");
    if (commentContainer !== null) {
        commentContainer.innerHTML = ''
        const commentScript = document.createElement("script");
        commentScript.setAttribute("id", "utteranc");
        commentScript.setAttribute("src", "https://utteranc.es/client.js");
        commentScript.setAttribute("data-repo", "ramsayleung/comment");
        commentScript.setAttribute("data-theme", darkMode ? "github-dark" : "github-light");
        commentScript.setAttribute("data-issue-term", "title");
        commentScript.setAttribute("crossorigin", "anonymous");
        commentScript.setAttribute("async", "true");
        commentContainer.appendChild(commentScript);
    }
}

2.3 监听主题变动

用户可以在博客界面手动选择他们喜欢的主题,可以从 dark -> light, light -> dark, 我们需要做的就是监听主题的变动,在切换主题之后,重新加载评论系统。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Watch for theme changes
const themeObserver = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
        if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
            const isDarkMode = getCurrentTheme() === 'dark';
            loadUtterances(isDarkMode);
            console.log(`changing theme`);
        }
    });
});

// Start observing the body element for class changes
themeObserver.observe(document.body, {
    attributes: true,
    attributeFilter: ['class']
});

3 总结

自适应评论系统的代码在这里, 实现的效果如下: