Comments Widget 的自动主题和双评论系统

目录

起因

在初步完成个人 GitHub Pages 的搭建后,我就一直在考察除 giscus1 外的其他评论系统。虽说 giscus 托管在 GitHub 上、而且免费开源、不包含广告,看上去全是优点,但是我忽略了一个重要的因素:用户来源。

正如我在关于中所说,这个博客是我的动画 Telegram 频道2的一种扩展,主要发布的内容也是动画相关的观后感。至于技术博文或是其他内容,只有在我认为有必要时才起身写作。

而 GitHub Pages 和 giscus,还是用在技术博客居多。在技术博客上要求用户使用 GitHub 登录,是一件非常自然、非常顺理成章的事;而在一篇动画观后感的评论区要求使用 GitHub 登录,显得有些离题万里、不合逻辑,这极有可能打击了用户的评论欲望。

于是,我选择接入 Comments Widget3,与 giscus 组成博客的双评论系统,以缓解用户来源差异的问题。

代码展示

代码大致能分为切换按钮、giscus 和 Comments Widget 三部分:

<div class="comment-toggle-buttons">
    <button id="show-giscus" class="active" onclick="showGiscus()">
        GitHub Discussions
    </button>
    <button id="show-telegram" onclick="showTelegram()">
        Telegram Comments
    </button>
</div>

<div id="github-discussions" class="comment-box"></div>
<div id="telegram-comments" class="comment-box"></div>

<style>
    .comment-toggle-buttons {
        display: flex;
        justify-content: center;
        margin-top: 50px;
        margin-bottom: 10px;
        gap: 10px;
    }

    #telegram-comments {
        min-height: 378.2px;
        display: none;
    }

    .comment-toggle-buttons button {
        display: block;
        padding: 0 14px;
        color: var(--primary);
        font-size: 14px;
        line-height: 34px;
        background: var(--code-bg);
        border-radius: var(--radius);
        border: 1px solid var(--border);
    }

    .comment-toggle-buttons button:hover {
        background: var(--border);
    }

    .comment-toggle-buttons button.active {
        pointer-events: none;
        background-color: transparent;
    }
</style>

<script>
    let telegramLoaded = false;

    function showGiscus() {
        document.getElementById("show-giscus").classList.add("active");
        document.getElementById("show-telegram").classList.remove("active");
        document.getElementById("github-discussions").style.display = "block";
        document.getElementById("telegram-comments").style.display = "none";
        setGiscusTheme();
    }

    function showTelegram() {
        document.getElementById("show-giscus").classList.remove("active");
        document.getElementById("show-telegram").classList.add("active");
        document.getElementById("github-discussions").style.display = "none";
        document.getElementById("telegram-comments").style.display = "block";
        if (!telegramLoaded) {
            loadTelegramWidget();
            telegramLoaded = true;
        }
    }

    const getStoredTheme = () =>
        localStorage.getItem("pref-theme") === "dark" ? "dark" : "light";

    const setGiscusTheme = () => {
        const sendMessage = (message) => {
            const iframe = document.querySelector("iframe.giscus-frame");
            if (iframe) {
                iframe.contentWindow.postMessage(
                    { giscus: message },
                    "https://giscus.app"
                );
            }
        };
        sendMessage({
            setConfig: {
                theme:
                    getStoredTheme() === "dark"
                        ? "noborder_dark"
                        : "noborder_light",
            },
        });
    };

    const loadTelegramWidget = () => {
        const telegramDiv = document.getElementById("telegram-comments");
        telegramDiv.innerHTML = "";
        const telegramScript = document.createElement("script");
        telegramScript.src = "https://comments.app/js/widget.js?3";
        telegramScript.defer = true;
        telegramScript.setAttribute("data-comments-app-website", "i7gjmkOw");
        telegramScript.setAttribute("data-limit", "100");
        telegramScript.setAttribute("data-height", "371");
        telegramScript.setAttribute("data-dislikes", "1");
        telegramScript.setAttribute("data-colorful", "1");
        telegramScript.setAttribute(
            "data-dark",
            getStoredTheme() === "dark" ? "1" : "0"
        );
        telegramDiv.appendChild(telegramScript);
    };

    const setTelegramTheme = () => {
        loadTelegramWidget();
    };

    document.addEventListener("DOMContentLoaded", () => {
        const giscusAttributes = {
            src: "https://giscus.app/client.js",
            "data-repo": "Xeonzilla/Xeonzilla.github.io",
            "data-repo-id": "R_kgDOL6BvNQ",
            "data-category": "Announcements",
            "data-category-id": "DIC_kwDOL6BvNc4Cfb8m",
            "data-mapping": "pathname",
            "data-strict": "1",
            "data-reactions-enabled": "1",
            "data-emit-metadata": "0",
            "data-input-position": "top",
            "data-theme":
                getStoredTheme() === "dark"
                    ? "noborder_dark"
                    : "noborder_light",
            "data-lang": "zh-CN",
            crossorigin: "anonymous",
        };

        const giscusScript = document.createElement("script");
        Object.entries(giscusAttributes).forEach(([key, value]) =>
            giscusScript.setAttribute(key, value)
        );
        giscusScript.defer = true;
        document.querySelector("#github-discussions").appendChild(giscusScript);

        if (
            document.getElementById("telegram-comments").style.display ===
            "block"
        ) {
            loadTelegramWidget();
            telegramLoaded = true;
        }

        const themeSwitcher = document.querySelector("#theme-toggle");
        if (themeSwitcher) {
            themeSwitcher.addEventListener("click", () => {
                setGiscusTheme();
                setTelegramTheme();
            });
        }
    });
</script>

其中,giscus 的自动主题切换有现成的方案,具体可以参考:

我的代码在他们的基础上小修小改,就不在此拾人牙慧了。

下面的代码是从完整代码中分离出来的,他们极有可能无法独立工作,仅作解析思路用。

Comments Widget

官方页面会在你填入所需信息和所选项后自动生成代码,和 giscus 类似,如无特殊需求,直接复制代码至 layouts/partials/comments.html 即可使用。

但是为了实现功能,我们需要对代码进行改造。

<div id="telegram-comments" class="comment-box"></div>

<style>
    #telegram-comments {
        min-height: 378.2px;
        display: none;
    }
</style>

<script>
    let telegramLoaded = false;

    const getStoredTheme = () =>
        localStorage.getItem("pref-theme") === "dark" ? "dark" : "light";

    const loadTelegramWidget = () => {
        const telegramDiv = document.getElementById("telegram-comments");
        telegramDiv.innerHTML = "";
        const telegramScript = document.createElement("script");
        telegramScript.src = "https://comments.app/js/widget.js?3";
        telegramScript.defer = true;
        telegramScript.setAttribute("data-comments-app-website", "i7gjmkOw");
        telegramScript.setAttribute("data-limit", "100");
        telegramScript.setAttribute("data-height", "371");
        telegramScript.setAttribute("data-dislikes", "1");
        telegramScript.setAttribute("data-colorful", "1");
        telegramScript.setAttribute(
            "data-dark",
            getStoredTheme() === "dark" ? "1" : "0"
        );
        telegramDiv.appendChild(telegramScript);
    };

    const setTelegramTheme = () => {
        loadTelegramWidget();
    };

    document.addEventListener("DOMContentLoaded", () => {
        if (
            document.getElementById("telegram-comments").style.display ===
            "block"
        ) {
            loadTelegramWidget();
            telegramLoaded = true;
        }
    });
</script>

代码最核心的部分其实就是一个 loadTelegramWidget(),以加载 Comments Widget,其他的代码都依赖于此。

我们需要监听页面主题的切换按钮,当页面主题切换时,触发 setTelegramTheme() 的操作。虽说这个行为的目的是主题设置,但是实际上是重新加载 Comments Widget,从代码上也可以看出来。

为什么是重载而不是直接切换主题呢?因为我似乎没有找到通过通信使 Comments Widget 切换主题的方法,Telegram 也没有提供相关文档。我曾尝试将 giscus 的代码逻辑套用到 Comments Widget 上,但是并不生效。以重载完成主题切换,是一种无奈之举。

等我找到切换主题的方法,或是 Telegram 提供了相关文档,又或是有人给出了教程,这套方案就会被弃用。不管我如何完善和优化重载的逻辑,它在性能消耗和可靠性上还是不如原生的主题切换。

重载能完成主题切换,但是我们不需要让它在任何操作后都重载,所以需要设置一个标志 let telegramLoaded = false,当 Comments Widget 完成首次加载后将标志设置为 true,避免用户多次切换评论区导致重复加载。

在样式上,min-height: 378.2px 起占位作用,在 Comments Widget 加载前维持页面元素的相对位置。378.2 px 是 giscus 没有评论时的高度,也是所有情况下的最小高度,与默认高度保持一致,使用户在切换评论系统时,页面元素不会位移。

切换按钮

相比之下,切换按钮的逻辑就较为简单,主要是样式上的调整。

<div class="comment-toggle-buttons">
    <button id="show-giscus" class="active" onclick="showGiscus()">
        GitHub Discussions
    </button>
    <button id="show-telegram" onclick="showTelegram()">
        Telegram Comments
    </button>
</div>

<style>
    .comment-toggle-buttons {
        display: flex;
        justify-content: center;
        margin-top: 50px;
        margin-bottom: 10px;
        gap: 10px;
    }

    .comment-toggle-buttons button {
        display: block;
        padding: 0 14px;
        color: var(--primary);
        font-size: 14px;
        line-height: 34px;
        background: var(--code-bg);
        border-radius: var(--radius);
        border: 1px solid var(--border);
    }

    .comment-toggle-buttons button:hover {
        background: var(--border);
    }

    .comment-toggle-buttons button.active {
        pointer-events: none;
        background-color: transparent;
    }
</style>

<script>
    let telegramLoaded = false;

    function showGiscus() {
        document.getElementById("show-giscus").classList.add("active");
        document.getElementById("show-telegram").classList.remove("active");
        document.getElementById("github-discussions").style.display = "block";
        document.getElementById("telegram-comments").style.display = "none";
        setGiscusTheme();
    }

    function showTelegram() {
        document.getElementById("show-giscus").classList.remove("active");
        document.getElementById("show-telegram").classList.add("active");
        document.getElementById("github-discussions").style.display = "none";
        document.getElementById("telegram-comments").style.display = "block";
        if (!telegramLoaded) {
            loadTelegramWidget();
            telegramLoaded = true;
        }
    }

    const themeSwitcher = document.querySelector("#theme-toggle");
    if (themeSwitcher) {
        themeSwitcher.addEventListener("click", () => {
            setGiscusTheme();
            setTelegramTheme();
        });
    }
</script>

大部分的样式都遵循主题的设定,使按钮与主题看上去更和谐统一。

通过 class="active"display: blockdisplay: none 间的切换、隐藏与显示,我们得以用 <button> 控制两个评论区的显示。

pointer-events: none 应用在当前激活的评论区按钮上,能够使用户无法再次选中,杜绝了很多奇怪 Bug 触发的可能性。

为什么不是 Discussion Widget

Telegram 除了提供 Comments Widget 外,还提供了 Discussion Widget4 将频道的评论区接入网页,同样可以作为评论系统,但是它存在着挺多问题:

  • 博客评论区与频道评论区完全关联
  • Telegram 无法识别经编辑的链接

完全关联这一点很好理解,毕竟 Discussion Widget 就是起到一种“桥接”的作用,将博客与频道绑定起来。如果我发布了技术博文,但我不想将链接广播到我的动画频道,这时的 Discussion Widget 就无法起到评论系统的作用。

第二点问题不知是 Telegram 设计上的问题还是特性,当第一次发送链接时,Telegram 能够主动识别;而如果是一条经过编辑的消息,即使它是链接,Telegram 也不会识别。

这样的特性不仅为 Discussion Widget 带来不便,还在广告投放者手中“被妙用”:他们往往会先在群组中发送一条正常的消息,随后将消息编辑为广告文案,以此躲过反广告机器人的识别与检测。

总体而言,Discussion Widget 并不是一套完善、高度可用的评论系统,它更适用于那些日常社交活动与个人 Telegram 频道高度相关的用户,否则,Discussion Widget 在功能性上并没有与 Waline 一较高下的能力。

如果像我一样已经有一个公开频道,此时接入 Discussion Widget 将会非常不便,需要为旧博文手动关联评论区,影响频道的内容展示;而如果没有公开频道,创建并使用 Discussion Widget 也未尝不可。

或许是 Waline

在第二个评论系统的选择上,我也有短暂考虑过 Waline5,它被广泛使用、口碑良好且没有广告,给我的第一印象不错。

唯一的问题就是,它需要部署。虽说 Waline 也可以免费部署到 Vercel6,但是我个人还是倾向于使用一些现成的服务,例如 giscus 的使用 GitHub Discussion,Comments Widget 的接入 Telegram。

使用额外部署的评论系统、调用一套第三方服务,就像引入新的外部依赖,它不一定会让博客变得不稳定,但是会让我感到复杂、不安定。相比之下,使用 Comments Widget,将评论系统接入日常使用的社交软件,给我的感觉更合理、更无缝。

当然,结论也并非一成不变,当有需要再次添加评论系统,或是博客重构的时候,Waline(或是它的继任者)会是一个好选择。

脚注

  1. giscus

  2. Xeon的Anime观察日志

  3. Comments for websites

  4. Discussion Widget

  5. Waline | Waline

  6. Vercel: Build and deploy the best web experiences with the Frontend Cloud

评论

有新的想法?欢迎向我发送邮件,或使用下方留言板进行留言。

留言板
留言可见性

公开留言会整理后展示,私人消息仅站长可见。

必填。最多 2000 字。支持 Markdown 语法,但不支持预览。

必填。公开展示时将使用这个昵称。

如需回复某条评论,请填写其序号。

可填写个人站点 URL,公开展示时会附加于昵称之上。

页首