Update: 2024.12.28 尝试引入 Github discussions - Giscus 评论系统.

为什么需要这个系统

  1. 因为 Quartz 暂时还不支持评论功能
  2. 如果有评论的话能够更方便的得到反馈
  3. 依赖评论系统,可以顺便支持访问统计、访客印象等信息。

这里使用的是 Waline 进行评论系统的搭建

为什么要用 Waline ? 因为成本低因为搭建起来方便, 因为目前只找到这个最合适

简要掠过一下搭建过程

Waline接入Quartz博客方法

简单方法

直接粘贴一下代码段到你的markdown笔记中即可。

可以让AI帮忙写个脚本在发布前手动执行一下就能自动插入代码(注意要检测是否已经插入过,不要重复插入,可以过滤是否存在https://unpkg.com/@Waline/client@v3/dist/Waline.js ,存在就不重复插入,并且要事先做好数据备份,防止把文件写坏。)

换成你部署的服务端url 换成你实际上部署的客户端URL即可

并且 dark: 'html[saved-theme=\'dark\']',Quartz 不要写双引号,会被过滤的 ) 要换成你实际使用博客的主题变换样式,具体在这里看描述,以便支持动态跟随系统切换暗黑模式。

 
---
 
<comment>
    <div id="Waline"></div>
    <link
        rel="stylesheet"
        href="https://unpkg.com/@Waline/client@v3/dist/Waline.css"
    />
    <script type="module">
        import { init } from 'https://unpkg.com/@Waline/client@v3/dist/Waline.js';
        init({
            el: '#Waline',
            dark: 'html[saved-theme=\'dark\']',
            serverURL: '{换成你部署的客户端url}',
        });
    </script>
</comment>
 

封装Waline评论成为Quartz组件的方法

因为此博客用的是 Quartz ,因此可以通过编写组件的方式来支持所有页面自动插入对应的 Waline 组件,实现自动展示评论。

Quartz 的框架实现

为了写Quartz的组件, 首先要看下它的实现框架: Architecture (jzhao.xyz)

简单来说就是依赖 bootstrap-cli.mjs 实现了markdown文本解析,并注册了一大堆复杂的回调结构和钩子,在支持Preact框架的情况下,实现在文本转换、对象前和后构建的时候执行一些javascript代码实现对静态页面内容的动态调整,同时依赖 SPA 框架实现只加载部分页面提升访问体验。

基于他可以调用 javascript 代码、写 JSX 这一点,就可以确定,构建 Waline Quartz组件的方案是可行的

Quartz 的组件调用位置

Quartz.layout.ts 里面定义了各个组件的布局,具体参见: Layout (jzhao.xyz)

其中里面有个 Component.Footer() 底部组件是本次要下手(参考)的对象。

// components shared across all pages
export const sharedPageComponents: SharedLayout = {
  head: Component.Head(),
  header: [],
  footer: Component.Footer(),
}

Quartz 的组件集成方式

简单看一下这个 Footer 组件,简单来说就是 传入 了 一些 Options 的结构(这个结构具体看这里 Creating your own Quartz components (jzhao.xyz) ),

然后 在 JSX 的支持下,在函数内实现了组件的编排,通过 return 返回构建的组件 DOM 内容,也就是 Html 内容,然后并且通过 Footer.css = style 方式指定样式,通过 Footer.afterDOMLoaded = script 方式指定组件加载完成后要执行的 javascript 脚本的字符串代码( beforeDOMLoaded 是组件加载前执行的代码 )。

也就是说,本次要改 returnafterDOMLoaded 的部分

interface Options {
  links: Record<string, string>
}
 
export default ((opts?: Options) => {
  const Footer: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
    const year = new Date().getFullYear()
    const links = opts?.links ?? []
    return (
      <>
		(中间掠)
      </>
    )
  }
 
  Footer.css = style
  Footer.afterDOMLoaded = stript
  return Footer
}) satisfies QuartzComponentConstructor

新建 Waline 组件

Html 内容生成

前面讲了为了实现 Waline 要写两部分,首先是 Html 的部分, 直接把 Html 写在一个文件封装好,让外部导入就行,内部通过 Link 引入了评论的 css 样式

  
export default () => {
    return (
        <>
            <div id="waline"></div>
            <link
                rel="stylesheet"
                href="https://unpkg.com/@waline/client@v3/dist/waline.css"
            />
        </>
    );
}

Javascript 代码编写

安装依赖

npm i @waline/client

import 一下然后直接导入 init ,传入 {你的Waline客户端URL} 就能完成初始化,然后刷新下页面就能看到评论的出现。

import { init } from '@waline/client';
init({
  el: '#waline',
  serverURL: '{你的Waline客户端URL}',
});

但是这样会有个问题,就是 Waline 是支持重复调用,自动清理资源的,因为 Quartz SPA 支持的代码实现中,把原来的 Waline 代码会覆盖掉,然后会导致 Waline 清理资源出问题,

简单来说就是你切换一下其他url,不刷新,就只能显示一次,解决方法是主动清理资源,又因为 Quartz 的传参和上下文保存比较麻烦,直接用全局变量保存,然后后面检测释放,代码如下:

// 资源清理
function walineContentExit() {
  if (window.waline_content_global) {
    window.waline_content_global?.destroy()
    window.waline_content_global = null
  }
}
 
import { init } from '@waline/client';
window.waline_content_global = init({
  el: '#waline',
  serverURL: '{你的Waline客户端URL}',
});
 // 通过阅读 Quartz 源码得知,提供的资源清理方式,
window?.addCleanup(() => walineContentExit())

这样子就能随意切换切换文件也能展示评论了

然后还有两个地方需要注意一下,404 页面的时候检测一下,不需要添加评论组件

var is_404 = false
var bodyTag = document.querySelector('body');
var dataSlugValue = bodyTag.getAttribute('data-slug');
if (dataSlugValue === "404") {
  is_404 = true
}
 
if (!is_404) {
  try {
  // 你的组件加载代码
  } catch (err) {
  // pass
  }
}

通过组件方式接入Quartz 框架

components/Waline.tsx

import WalineComment from "{刚刚导出的DOM文件}"
import WalineScript from "{刚刚定义的Javascript文件字符串方式导入}"
(略)
export default ((opts?: Options) => {
    return (
      <>
      <footer (略)>
	      <WalineComment />
      </footer>
      </>
    )
  }
  Footer.afterDOMLoaded = WalineScript
  return Footer
}) satisfies QuartzComponentConstructor

将组件布局到界面中展示

quartz.layout.ts

  
// components shared across all pages
export const sharedPageComponents: SharedLayout = {
  (略)
  footer: Component.Waline(),
}

Waline 的附加功能

文章反应、浏览量统计等功能在这里查看: 功能 | Waline

整体 Waline Quartz 代码实现在这里

  1. 组件调用处: quartz.layout.tsquartz.layout.ts (github.com)
  2. 组件定义: components/Waline.tsxapp_blog/quartz/components/Waline.tsx at blog · observerkei/app_blog (github.com)
  3. 组件 DOM 和实现代码:见下面

评论的实现代码

comment-DOM

export default () => {
    return (
        <>
            <div id="waline"></div>
        </>
    );
}

comment-Javascript

import { init } from '@waline/client';
import { local } from 'd3';
// 资源清理
function walineContentExit() {
  if (window.waline_content_global) {
    window.waline_content_global?.destroy()
    window.waline_content_global = null
  }
}
// 资源初始化
function walineContentInit() {
  // 不在主页渲染
  if (window.location.pathname !== '/') {
    window.waline_content_global = init({
      el: '#waline',
      dark: 'html[saved-theme=\'dark\']',
      serverURL: '{你的URL客户端}',
      reaction: [
        "你想显示的文章反应图片"
      ],
      locale: {
        reactionTitle: ""
      }
    });
    
    window?.addCleanup(() => walineContentExit())
  }
}
 
export default () => {
  // 首次执行
  walineContentInit()
  // SPA操作通知的时候执行
  document.addEventListener("nav", walineContentInit)
}

特别的,如果你想通过 afterDOMLoaded /beforeDOMLoaded 同时执行多个脚本,在其他脚本是 export default 情况下, 可以这样使用,然后在另一个地方直接引入这个文件赋值给 afterDOMLoaded /beforeDOMLoaded 属性([[#quartz-的组件集成方式|Quartz 的组件集成方式]]):

  
import XX from 'xxx.inline'
import BB from 'bbb.inline'
 
XX()
BB()

设置邮件通知

具体设置方法在: 评论通知 | Waline

这里就不重复赘述了。

搭建的时候碰到的问题

  • Quartz 并不是使用 React 实现的 ,不直接支持 React 语法和组件,
  • 也不能直接使用 Vue,不支持直接写 Vue虽然我也不打算写
  • 需要通过 Javascript 字符串代码导入方式来写专用组件才能够被调用😂。直接写函数不行,直接 export 也不行,Quartz 框架组件代码执行方式限制了使用方法。上面给出了解决方法。
  • 你不能直接在他写的 Html 导入框架里面直接导入 Waline
  • Waline 在组件还在的时候能重复初始化,但是组件被清理之后(Html 的部分),不能重复调用,会报找不到 DOM 的异常,因为使用了 SPA , 需要主动取释放资源。
  • Quartz 的组件不能方便的互相调用,主要是脚本的部分,他是直接以文件方式导入成字符串文本,然后以文本方式解析JavaScript的脚本,再去执行,这样你写多个模块的时候,如果有多个脚本的话,就得再写个脚本,调用其他脚本之后,再提供给 Quartz 组件调用,因为是多个文件,封装起来不太优雅。
  • Waline 渲染组件默认用URL路径,如果后续文件路径变更,那么评论就会对不上,,这个问题可以通过 .obsidian 插件给笔记计算唯一 SHA256 作为ID,然后让Waline初始化时候path 传入这个ID解决;
  • 如果访问 404 页面,其实也会显示评论组件,需要调整显示策略,具体怎么做见上面代码。

~~原本不想折腾这个插件的,~~从 Quartz 的复杂插件传递方式来看他限制住了客户端编写 JSX 代码的的灵活性(目前必须通过字符串引入代码),这是后续可以优化的地方(就像上面提供的放在一个脚本里面的方法其实就可以在内部进行封装), 以及 DOM 和他配套的 Javascript 代码分开存放就会导致项目文件变得很多,不易阅读(实际上你要改相关代码也得两个部分一起看才能下手),可能是出于性能考虑所以没有用 React 吧(随着设备性能的提高好像也不会差多少),

虽说如此,Quartz 确实是个好的开源项目,前面说的只是一些细节上可以优化的点,使用 Javascript 在浏览器的支持下可以通过B/S架构提供良好的跨平台特性,它能灵活的设定移动端和桌面的显示组件,支持暗黑模式,并且提供了插件和组件的自定义拓展方式,增强了拓展性和功能性,更易于延长项目的生命周期,实现代码复用,在组件化的支持下可以实现很多功能拓展( 比如评论功能 )。

*****