第三方评论系统

存在许多第三方评论系统,下面简要介绍下各评论系统集成方式。

第三方评论系统之我见

Waline ​

Waline 是一个依赖服务端实现的评论系统,它可以托管在 Vercel 等平台上。

使用 valaxy-addon-waline 集成。

valaxy-addon-waline 是基于 Waline 的一个 Valaxy 插件。 除此之外,我们推荐您可以使用 kotodama 进行评论管理,它是一个基于 Waline 服务端实现的评论管理系统。

安装 ​

bash
npm i valaxy-addon-waline
# pnpm add valaxy-addon-waline
valaxy.config.ts
ts
import { defineValaxyConfig } from 'valaxy'
import { addonWaline } from 'valaxy-addon-waline'

export default defineValaxyConfig({
  // or write it in site.config.ts
  siteConfig: {
    // 启用评论
    comment: {
      enable: true
    },
  },
  // 设置 valaxy-addon-waline 配置项
  addons: [
    addonWaline({
      // Waline 配置项,参考 https://waline.js.org/reference/client/props.html
      serverURL: 'https://your-waline-url',
    }),
  ],
})

Utterances ​

Utterances 是一个基于 GitHub Issues 实现的评论系统。

它可以直接通过挂载 JS 脚本集成。

在博客根目录下新建 App.vue,添加挂载脚本:

App.vue
vue
<!-- You can mount anything here -->

<script lang="ts" setup>
import { useUtterances } from './composables/use-utterances'

useUtterances({
  /**
   * use your repo
   */
  repo: 'YunYouJun/valaxy',
  issueTerm: 'pathname',
  label: 'utterances',
})
</script>

<template>
  <!-- try it -->
  <div />
</template>
use-utterances.ts
ts
import { isClient } from '@vueuse/core'
import { onContentUpdated, useAppStore } from 'valaxy'
import { nextTick, onBeforeUnmount, watch } from 'vue'
import { useRoute } from 'vue-router'

const utterancesClientSrc = 'https://utteranc.es/client.js'

/**
 * @see https://utteranc.es/
 */
export function useUtterances(options: {
  repo: string
  issueTerm: 'pathname' | 'title'
  label: string
}) {
  // Guard: only run on client side
  if (!isClient) {
    return
  }

  const app = useAppStore()
  const route = useRoute()

  let script: HTMLScriptElement | undefined
  let mountedContainer: Element | null = null
  let mountedKey = ''
  let contentPath: string | undefined
  let disposed = false

  function cleanup() {
    script?.remove()
    mountedContainer?.querySelector('.utterances')?.remove()
    script = undefined
    mountedContainer = null
    mountedKey = ''
  }

  /**
   * mount utterances
   * @see https://utteranc.es/
   */
  function createUtterancesScript() {
    if (disposed || contentPath !== route.path)
      return
    const commentContainer = document.querySelector('.comment')
    const key = `${route.path}:${app.isDark}`
    // Content updates (for example a language change) must not reload the same iframe.
    if (commentContainer === mountedContainer && key === mountedKey)
      return

    cleanup()
    if (!commentContainer)
      return

    script = document.createElement('script')
    script.src = utterancesClientSrc
    script.async = true
    script.crossOrigin = 'anonymous'
    script.setAttribute('repo', options.repo)
    script.setAttribute('issue-term', options.issueTerm)
    script.setAttribute('label', options.label)
    script.setAttribute('theme', app.isDark ? 'github-dark' : 'github-light')
    commentContainer.appendChild(script)
    mountedContainer = commentContainer
    mountedKey = key
  }

  function scheduleMount() {
    nextTick(createUtterancesScript)
  }

  // UserApp can mount before the async route hydrates. Wait for its Markdown
  // content, then finish that render before inserting third-party DOM.
  onContentUpdated(() => {
    contentPath = route.path
    scheduleMount()
  })

  watch(() => app.isDark, scheduleMount, { flush: 'post' })
  watch(() => route.path, () => {
    contentPath = undefined
    cleanup()
  })

  onBeforeUnmount(() => {
    disposed = true
    cleanup()
  })
}

贡献者