Waline评论的设置及相关优化

增加文章评论及邮件通知,评论数的显示

Waline的安装

数据库与服务端按照官方指引就可以:快速上手 | Waline

环境变量的设置如下:注意下 key 的名称 Pasted image 20241007112424.png

迁移到 V3 版本:修改 stack 模板,文件位置1 \themes\hugo-theme-stack\layouts\partials\comments\provider\waline.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<link rel="stylesheet" href="https://unpkg.com/@waline/client@v3/dist/waline.css"/>
<div id="waline" class="waline-container"></div>
<style>
    .waline-container {
        background-color: var(--card-background);
        border-radius: var(--card-border-radius);
        box-shadow: var(--shadow-l1);
        padding: var(--card-padding);
        --waline-font-size: var(--article-font-size);
    }
    .waline-container .wl-count {
        color: var(--card-text-color-main);
    }
</style>

{{- $showReaction := (default true .Params.reaction) -}}
{{- with .Site.Params.comments.waline -}}
{{- $config := dict "el" "#waline" "dark" `html[data-scheme="dark"]` -}}
{{- $replaceKeys := dict "serverurl" "serverURL" "requiredmeta" "requiredMeta" "wordlimit" "wordLimit" "pagesize" "pageSize" "imageuploader" "imageUploader" "texrenderer" "texRenderer" "commentsorting" "commentSorting" "recaptchav3key" "recaptchaV3Key" "turnstilekey" "turnstileKey" -}}
{{- $replaceLocaleKeys := dict "reactiontitle" "reactionTitle" "gifsearchplaceholder" "gifSearchPlaceholder" "nickerror" "nickError" "mailerror" "mailError" "wordhint" "wordHint" "cancellike" "cancelLike" "cancelreply" "cancelReply" "uploadimage" "uploadImage" -}}

{{- range $key, $val := . -}}
    {{- if ne $val nil -}}  
        {{- $replaceKey := index $replaceKeys $key -}}
        {{- $k := default $key $replaceKey -}}

        {{- if eq $k "locale" -}}
            {{- $locale := dict -}}
            {{- range $lkey, $lval := $val -}}
                {{- if ne $lval nil -}}  
                    {{- $replaceLKey := index $replaceLocaleKeys $lkey -}}
                    {{- $lk := default $lkey $replaceLKey -}}

                    {{- $locale = merge $locale (dict $lk $lval) -}}
                {{- end -}}
            {{- end -}}
            {{- $config = merge $config (dict $k $locale) -}}
        {{- else if eq $k "reaction" -}}
            {{- $config = merge $config (dict $k (cond $showReaction $val false)) -}}
        {{- else -}}
            {{- $config = merge $config (dict $k $val) -}}
        {{- end -}}
    {{- end -}}
{{- end -}}

<script type="module">
    import { init } from 'https://unpkg.com/@waline/client@v3/dist/waline.js';
    init({{ $config | jsonify | safeJS }});
</script>
{{- end -}}

Hugo的设置

在主目录 hugo.yaml 的文件内修改,相关设置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 article:
        math: false
        # 这里如果是true,评论内容加载不了,不知道是什么原因
        toc: true
        readingTime: true
        license:
            enabled: true
            default: Licensed under CC BY-NC-SA 4.0

    comments:
        enabled: true
        provider: waline

# Waline client configuration see: https://waline.js.org/en/reference/component.html
        waline:
            serverURL: https://waline.xsen.net
            lang: zh-CN
            search: false
            pageview: true
            comment: true
            emoji:
                - https://npm.elemecdn.com/@waline/emojis@1.2.0/qq
                - https://npm.elemecdn.com/@waline/emojis@1.2.0/bilibili
                - https://npm.elemecdn.com/@waline/emojis@1.0.1/weibo                
            requiredMeta:
                - nick
            locale:
                admin: 博主
                sofa: 还没有人评论哦!快来抢沙发吧~
                placeholder: 欢迎留下宝贵的评论!请留下正确的邮箱以便有回复时进行邮箱提醒,请勿发布无关内容。

注意修改项: math: true # 这里修改成false,如果是true,评论内容加载不了,不知道是什么原因,暂且修改。2

配置邮箱提醒

阿里云邮箱设置

由于域名商没办法修改NS,所以用了阿里云企业邮箱(登入后用这个链接可以购买企业邮箱),否则推荐用 cf 邮箱。

配置 Vercel 上的环境变量:

  • SMTP_HOST: smtp.qiye.aliyun.com # SMTP 服务器地址,一般可以在邮箱的设置中找到。
  • SMTP_PORT: 465 # SMTP 服务器端口,一般可以在邮箱的设置中找到。
  • SMTP_USER:填邮箱
  • SMTP_PASS:填三方客户端安全密码,记得删除空格
  • SITE_NAME:填博客的名字
  • SITE_URL:要带 https://,不带最后一个斜杠,例如 https://xsen.net
  • AUTHOR_EMAIL:填自己常用的邮箱,用来接受评论通知

还可以配置内容版面等,可看1

接入浏览量与评论数统计

文章页

hugo.yamlwaline 配置中将 pageviewcomment 都设置为 true3

并且修改模板下的 layouts\partials\article\components\details.html,内容修改为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<div class="article-details">
    {{ if .Params.categories }}
    <header class="article-category">
        {{ range (.GetTerms "categories") }}
            <a href="{{ .RelPermalink }}" {{ with .Params.style }}style="background-color: {{ .background }}; color: {{ .color }};"{{ end }}>
                {{ .LinkTitle }}
            </a>
        {{ end }}
    </header>
    {{ end }}

    <div class="article-title-wrapper">
        <h2 class="article-title">
            <a href="{{ .RelPermalink }}">
                {{- .Title -}}
            </a>
        </h2>
    
        {{ with .Params.description }}
        <h3 class="article-subtitle">
            {{ . }}
        </h3>
        {{ end }}
    </div>

    {{ $showReadingTime := .Params.readingTime | default (.Site.Params.article.readingTime) }}
    {{ $showDate := not .Date.IsZero }}
    {{ $showFooter := or $showDate $showReadingTime }}
    {{ if $showFooter }}
    <footer class="article-time">
    {{ if not .Date.IsZero }}
        <div>
            {{ partial "helper/icon" "date" }}
            <time class="article-time--published">
                {{- .Date.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
            </time>
        </div>
    {{ end }}

    {{ if .Site.Params.article.readingTime }}
        <div>
            {{ partial "helper/icon" "clock" }}
            <time class="article-words">
                {{ $fixedWordCount := add .WordCount 224}}
                {{ $ReadingTime := div $fixedWordCount 225 }}
                {{ T "article.readingTime" $ReadingTime }}
            </time>
        </div>
    {{ end }}

    <div>
        {{ partial "helper/icon" "view" }}
        <time class="article-pageview">
            <span class="waline-pageview-count" data-path="{{.RelPermalink}}">0</span>
        </time>
    </div>

    <div>
        {{ partial "helper/icon" "comment" }}
        <time class="article-comment">
            <span class="waline-comment-count" data-path="{{.RelPermalink}}">0</span>
        </time>
    </div>
</footer>

    {{ end }}

    {{ if .IsTranslated }}
        <footer class="article-translations">
            {{ partial "helper/icon" "language" }}
            <div>
                {{ range .Translations }}
                    <a href="{{ .Permalink }}" class="link">{{ .Language.LanguageName }}</a>
                {{ end }}
            </div>
        </footer>
    {{ end }}
</div>

首页

修改网站底部模板: layouts/partials/footer/footer.html

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
{{- $ThemeVersion := "3.27.0" -}}
<footer class="site-footer">
    <section class="copyright">
        &copy; 
        {{ if and (.Site.Params.footer.since) (ne .Site.Params.footer.since (int (now.Format "2006"))) }}
            {{ .Site.Params.footer.since }} - 
        {{ end }}
        {{ now.Format "2006" }} {{ default .Site.Title .Site.Copyright }}
    </section>
visits <span class="waline-index-count breathe" waline-url="{{ .Site.Params.comments.waline.serverURL }}" data-path="{{ .RelPermalink }}" style="font-weight: bold;">0</span>
    <section class="powerby">
        {{ with .Site.Params.footer.customText }}
            {{ . | safeHTML }} <br/>
        {{ end }}                
        {{- $Generator := `<a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a>` -}}        

        {{- $Theme := printf `<b><a href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener" data-version="%s">Stack</a></b>` $ThemeVersion -}}
        {{- $DesignedBy := `<a href="https://jimmycai.com" target="_blank" rel="noopener">Jimmy</a>` -}}

        {{ T "footer.builtWith" (dict "Generator" $Generator) | safeHTML }} <br />
        {{ T "footer.designedBy" (dict "Theme" $Theme "DesignedBy" $DesignedBy) | safeHTML }}
    </section>
<script>
    (async ()=>{
      let indexCountEle = document.querySelector('.waline-index-count')
      let walineUrl = indexCountEle.getAttribute('waline-url')

      let pageCountEles = document.querySelectorAll('.waline-pageview-count')
      let pagePathNames = Array.from(pageCountEles).map(item => item.getAttribute('data-path'))

      let commentCountEles = document.querySelectorAll('.waline-comment-count')
      let commentPathNames = Array.from(commentCountEles).map(item => item.getAttribute('data-path'))

      // 获取访问统计函数
      function getViewCount (urlList) {
        let url = walineUrl + '/api/article/?path='
        urlList.forEach((item,index) => {
          url += index === 0 ? item :  (',' + item)
        })
        return fetch(url)
        .then(res => res.json())
        .then(res => {
          if(res.errno == 0) {
            return res.data.map(item => item.time)
          } else {
            throw new Error('获取失败')
          }
        })
        .catch(err => {
          return new Promise((resolve,reject) => {
            reject(err)
          })
        })
      }
      // 添加访问统计函数
      function addViewCount (pathname) {
        let data = { path: pathname}
        return fetch(`${walineUrl}/api/article`,{
            method:'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body:JSON.stringify(data)
        })
        .then(res => res.json())
        .then(res => {
          if(res.errno == 0) {
            return res.data[0].time
          } else {
            throw new Error('获取失败')
          }
        })
        .catch(err => {
          return new Promise((resolve,reject) => {
            reject(err)
          })
        })
      }
      // 获取评论统计函数
      function getCommentCount (urlList) {
        let url = walineUrl + '/api/comment?type=count&lang=en&url='
        urlList.forEach((item,index) => {
          url += index === 0 ? item :  (',' + item)
        })
        return fetch(url)
        .then(res => res.json())
        .then(res => {
          if(res.errno == 0) {
            return res.data
          } else {
            throw new Error('获取失败')
          }
        })
        .catch(err => {
          return new Promise((resolve,reject) => {
            reject(err)
          })
        })
      }

      // 获取列表页的访问统计和评论统计
      if(window.location.pathname == '/' || window.location.pathname.includes('/page/')){
        getViewCount(pagePathNames).then(pageCounts => {
          pageCounts.forEach((item,index) => {
            pageCountEles[index].innerText = item
          })
        })
        getCommentCount(commentPathNames).then(commentCounts => {
          console.log('commentCounts',commentCounts)
          commentCounts.forEach((item,index) => {
            commentCountEles[index].innerText = item
          })
        })
      }

      // 更新文章页访问统计,获取评论统计
      if(window.location.pathname.includes('/post/')){
        addViewCount(pagePathNames[0]).then(pageCount => {
          pageCountEles[0].innerText = pageCount
        })
        getCommentCount(commentPathNames).then(commentCounts => {
          commentCounts.forEach((item,index) => {
            commentCountEles[index].innerText = item
          })
        })
      }

      // 更新页面底部访问统计,localStorage记录访问时间和访问统计
      // 没有访问过,往api添加访问记录并更新访问统计
      // 在1小时内访问过,从本地存储拿
      // 超过1小时,重新获取
      let indexCount = localStorage.getItem('indexCount')
      let lastVisitTime = localStorage.getItem('lastVisitTime')
      if (!!lastVisitTime == false) {
        addViewCount('/').then(indexCount => {
          localStorage.setItem('indexCount',indexCount)
          localStorage.setItem('lastVisitTime', Date.now())
          indexCountEle.innerHTML = indexCount
        })
      }
      if(!!lastVisitTime && !!indexCount && Date.now() - lastVisitTime < 3600000 ){
        indexCount = localStorage.getItem('indexCount')
        indexCountEle.innerHTML = indexCount
      }
      if(!!lastVisitTime && Date.now() - lastVisitTime >= 3600000){
        getViewCount(["/"]).then(res => {
          indexCount = res[0]
          localStorage.setItem('indexCount',indexCount)
          localStorage.setItem('lastVisitTime', Date.now())
          indexCountEle.innerHTML = indexCount
        })
      }

      // 样式
      setInterval(() => {
        new Date().getSeconds() % 2 === 0 ? indexCountEle.style.color = '' : indexCountEle.style.color = ''
      }, 1000)
    })()
</script>
<style>
    .wl-action[title="GIF"] {
      display: none;
    }
    footer.site-footer {
      padding-bottom: 20px;
    }
</style>

</footer>

说明:以上设置包括浏览量与评论的显示,由于测试发现在首页列表时如果是中文链接名会获取不到浏览量,但评论量是正常的,所以本博客只展示了评论量。

  • 解决文章链接尾部有无/评论不一致问题,在 footer.html 中某处 script 内添加脚本:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function ensureTrailingSlash() {
  var currentPath = window.location.pathname;
  if (currentPath.length > 1 && currentPath.substr(-1) !== '/') {
    var newUrl = window.location.protocol + '//' + window.location.host + currentPath + '/' + window.location.search + window.location.hash;
    window.location.replace(newUrl);
  }
}

window.addEventListener('load', ensureTrailingSlash);
window.addEventListener('beforeunload', ensureTrailingSlash);

首页最新评论

设置按1不成功

取消链接大小写转换

Hugo 默认会将网站的 url 中的大写字符改为小写,所以为了大小写统一,在 hugo.yaml 页面添加如下设置:

1
disablePathToLower: true

结语

目前基本配置了大概,均按照各教程来,本人代码小白,所以博客只是够用就行,就是 Hugo 的安装使用也有一些门槛,暂且如此。

参考:

使用 Hugo 构建
主题 StackJimmy 设计