实现功能:
标签筛选 Waline 评论区 显示每条 moment 评论数量
Step 1 - 添加 moments 页面模板
layouts/moments/list.html
{{ define "main" }}
<!-- 如果需要引入 moments.css,请保持路径一致或根据自己项目结构调整 -->
{{ $css := resources.Get "css/extended/moments.css" | minify | fingerprint }}
<link
crossorigin="anonymous"
href="{{ $css.RelPermalink }}"
integrity="{{ $css.Data.Integrity }}"
rel="stylesheet"
/>
{{ $dateformat := .Params.DateFormat }}
<article class="post-single">
<header class="page-header">
<!-- 可根据需求添加页面标题、描述等 -->
<!-- <h1>{{ .Title }}</h1> -->
</header>
<div class="tags-filter">
<ul>
<li><a href="#" class="tag-filter all-tags">全部</a></li> <!-- "全部"选项 -->
{{ $tags := slice }} <!-- 用于存储所有标签 -->
{{ range .Pages }}
{{ range .Params.tags }}
{{ if not (in $tags .) }}
{{ $tags = $tags | append . }}
{{ end }}
{{ end }}
{{ end }}
<!-- 按字母顺序排序标签 -->
{{ $tags = $tags | sort }}
{{ range $tags }}
<li><a href="#" class="tag-filter">{{ . }}</a></li> <!-- 标签项 -->
{{ end }}
</ul>
</div>
<div class="post-content">
<div class="moments-list">
{{ range .Pages }}
{{ if .Content }}
<!-- 卡片容器 -->
<div class="moment-card">
<!-- 头部:头像 + 作者名 -->
<div class="moment-header">
<div class="left-content">
<img
src="{{ site.Params.label.avatar }}"
alt="{{ site.Params.author }}"
class="moment-avatar"
>
<span class="moment-author">
{{ site.Params.author }}
</span>
</div>
</div>
<!-- 动态主体内容(Hugo 渲染后的 .Content) -->
<div class="moment-body">
<div class="moment-content-wrapper">
{{ .Content | safeHTML }}
</div>
<div class="moment-loading">
<div class="loading-spinner"></div>
<div class="skeleton-content">
<div class="skeleton-line" style="width: 90%"></div>
<div class="skeleton-line" style="width: 75%"></div>
<div class="skeleton-line" style="width: 60%"></div>
</div>
</div>
<div class="moment-error" style="display: none;">
<span>图片加载失败</span>
<button onclick="retryLoad(this)">重试</button>
</div>
</div>
<!-- 标签(如果有) -->
{{ if .Params.tags }}
<div class="moment-tags">
{{ range $tag := .Params.tags }}
<span class="moment-tag">{{ $tag }}</span>
{{ end }}
</div>
{{ end }}
<!-- 底部:时间 + 评论按钮 -->
<div class="moment-bottom">
<div class="moment-time">
<span>
{{ .Param "date" | time.Format (default site.Params.DateFormat $dateformat) }}
</span>
</div>
<!-- 如果没有 hideComment 参数,则显示评论按钮 -->
{{ if not (.Param "hideComment") }}
<button
class="moment-comment-btn"
onclick="showComment(this)"
data-slug="{{ .Param "slug" }}"
data-path="{{ .Param "slug" }}"
>
<!-- 评论图标 SVG -->
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M281.535354 387.361616c-31.806061 0-57.664646 26.763636-57.664647 59.733333 0 32.969697 25.858586 59.733333 57.664647 59.733334s57.664646-26.763636 57.664646-59.733334c0-33.09899-25.858586-59.733333-57.664646-59.733333z m230.529292 0c-31.806061 0-57.664646 26.763636-57.664646 59.733333 0 32.969697 25.729293 59.733333 57.664646 59.733334 31.806061 0 57.535354-26.763636 57.535354-59.733334 0-33.09899-25.858586-59.733333-57.535354-59.733333z m230.4 0c-31.806061 0-57.664646 26.763636-57.664646 59.733333 0 32.969697 25.858586 59.733333 57.664646 59.733334s57.664646-26.763636 57.664647-59.733334c-0.129293-33.09899-25.858586-59.733333-57.664647-59.733333z m115.2-270.222222H166.335354c-63.612121 0-115.2 53.527273-115.2 119.59596v390.981818c0 65.939394 52.751515 126.836364 117.785858 126.836363h175.579798c30.513131 32.581818 157.220202 149.979798 157.220202 149.979798 5.559596 5.818182 14.739394 5.818182 20.29899 0 0 0 92.832323-91.410101 153.212121-149.979798h179.717172c65.034343 0 117.785859-60.89697 117.785859-126.836363V236.606061c0.129293-65.939394-51.458586-119.466667-115.070708-119.466667z m57.535354 510.577778c0 32.969697-27.668687 67.620202-60.250505 67.620202H678.335354c-21.462626 0-40.727273 21.979798-40.727273 21.979798l-124.121212 114.941414-124.121212-114.941414s-23.660606-21.979798-43.830303-21.979798H168.921212c-32.581818 0-60.250505-34.650505-60.250505-67.620202V236.606061c0-32.969697 25.729293-59.733333 57.664647-59.733334h691.329292c31.806061 0 57.535354 26.763636 57.535354 59.733334v391.111111z m0 0"></path></svg>
<!-- 评论按钮文字 -->
<span class="comment-text">评论 ({{ .Params.comments_count | default "0" }})</span>
</button>
{{ end }}
</div>
<!-- 评论容器:点击按钮后会在这里渲染 Waline 评论 -->
<div
class="waline-container"
id="waline-{{ .Param "slug" }}"
data-path="{{ .Param "slug" }}"
></div>
</div>
{{ end }}
{{ end }}
</div>
</div>
</article>
<!-- JavaScript 代码 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// 图片懒加载和预加载处理
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const wrapper = img.closest('.moment-content-wrapper');
const loadingEl = wrapper.nextElementSibling;
const errorEl = loadingEl.nextElementSibling;
// 显示加载状态
loadingEl.style.display = 'flex';
errorEl.style.display = 'none';
// 创建新的Image对象用于预加载
const tempImg = new Image();
tempImg.onload = function() {
img.src = img.dataset.src;
img.classList.add('loaded');
loadingEl.style.display = 'none';
observer.unobserve(img);
};
tempImg.onerror = function() {
loadingEl.style.display = 'none';
errorEl.style.display = 'block';
};
tempImg.src = img.dataset.src;
}
});
}, {
rootMargin: '50px 0px',
threshold: 0.1
});
// 处理所有图片元素
document.querySelectorAll('.moment-content-wrapper').forEach(wrapper => {
const loadingEl = wrapper.nextElementSibling;
const images = wrapper.querySelectorAll('img');
if (images.length === 0) {
loadingEl.style.display = 'none';
return;
}
let loadedImages = 0;
images.forEach(img => {
if (img.src) {
img.dataset.src = img.src;
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; // 透明占位图
imageObserver.observe(img);
// 监听图片加载完成
img.onload = () => {
loadedImages++;
if (loadedImages === images.length) {
loadingEl.style.display = 'none';
}
};
}
});
});
// 重试加载功能
window.retryLoad = function(button) {
const errorEl = button.closest('.moment-error');
const loadingEl = errorEl.previousElementSibling;
const wrapper = loadingEl.previousElementSibling;
const img = wrapper.querySelector('img');
errorEl.style.display = 'none';
loadingEl.style.display = 'flex';
const tempImg = new Image();
tempImg.onload = function() {
img.src = img.dataset.src;
img.classList.add('loaded');
loadingEl.style.display = 'none';
};
tempImg.onerror = function() {
loadingEl.style.display = 'none';
errorEl.style.display = 'block';
};
tempImg.src = img.dataset.src;
};
// 新增分页相关变量
let currentPage = 1;
const pageSize = {{ .Site.Params.moments.pageSize | default 8 }};
let filteredMoments = [];
let isLoading = false;
let isAllLoaded = false;
let loadingTimeout = null;
const loadingHint = document.createElement('div');
loadingHint.className = 'scroll-hint-container';
loadingHint.innerHTML = '<div class="loading-hint">加载更多...</div>';
document.querySelector('.moments-list').after(loadingHint);
// 显示分页内容的方法
function displayMoments() {
const end = currentPage * pageSize;
const totalItems = filteredMoments.length;
// 优化显示逻辑,只处理新增的内容
const start = (currentPage - 1) * pageSize;
filteredMoments.slice(start, end).forEach(moment => {
moment.style.display = 'block';
// 触发图片懒加载重新检查
moment.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
});
// 更新底部提示
const hasMore = end < totalItems;
isAllLoaded = !hasMore;
if (totalItems === 0) {
loadingHint.innerHTML = '<div class="end-divider">暂无内容</div>';
} else if (isAllLoaded) {
loadingHint.innerHTML = '<div class="end-divider">———— · 已到底部 · ————</div>';
} else {
loadingHint.innerHTML = '<div class="loading-hint">加载更多...</div>';
}
loadingHint.style.display = 'block';
}
// 优化的滚动事件处理
function checkScroll() {
if (isLoading || isAllLoaded || filteredMoments.length === 0) return;
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
const threshold = 200; // 增加阈值,提前开始加载
const end = currentPage * pageSize;
if (end < filteredMoments.length && scrollTop + clientHeight >= scrollHeight - threshold) {
isLoading = true;
loadingHint.innerHTML = '<div class="loading-hint"><div class="loading-spinner"></div>加载中...</div>';
// 清除之前的超时
if (loadingTimeout) {
clearTimeout(loadingTimeout);
}
// 使用 requestAnimationFrame 和防抖优化性能
requestAnimationFrame(() => {
loadingTimeout = setTimeout(() => {
currentPage++;
displayMoments();
isLoading = false;
loadingTimeout = null;
}, 300);
});
}
}
// 点击标签时筛选逻辑
const tags = document.querySelectorAll('.tag-filter'); // 获取所有标签
const moments = document.querySelectorAll('.moment-card'); // 获取所有 moment 卡片
const allTags = document.querySelector('.all-tags'); // 获取"全部"按钮
const momentTags = document.querySelectorAll('.moment-tag'); // 获取所有卡片内的标签
// 默认选中"全部"标签
if (allTags) {
allTags.classList.add('selected');
}
// 点击标签时进行筛选
tags.forEach(tag => {
tag.addEventListener('click', function(e) {
e.preventDefault();
const selectedTag = tag.textContent.trim();
filteredMoments = Array.from(moments).filter(moment => {
const momentTags = moment.querySelectorAll('.moment-tag');
return selectedTag === '全部' ||
Array.from(momentTags).some(t => t.textContent === selectedTag);
});
currentPage = 1;
displayMoments();
window.scrollTo(0, 0); // 筛选后回到顶部
tags.forEach(t => t.classList.remove('selected'));
tag.classList.add('selected');
});
});
// 为卡片内的标签添加点击事件
momentTags.forEach(tag => {
tag.style.cursor = 'pointer';
tag.addEventListener('click', function() {
const tagText = this.textContent.trim();
// 找到对应的顶部标签并触发点击
tags.forEach(headerTag => {
if (headerTag.textContent.trim() === tagText) {
headerTag.click();
}
});
});
});
// 初始加载
filteredMoments = Array.from(moments);
displayMoments();
// 确保提示容器正确插入
const existingHint = document.querySelector('.scroll-hint-container');
if (!existingHint) {
const hintContainer = document.createElement('div');
hintContainer.className = 'scroll-hint-container';
document.querySelector('.moments-list').after(hintContainer);
}
window.addEventListener('scroll', checkScroll);
});
</script>
<!-- 在页面底部引入 Waline 评论脚本并初始化 -->
<script type="module">
import { init } from 'https://unpkg.com/@waline/client@v3/dist/waline.js';
const walineParams = {
/* 这里根据你自己的 Waline 配置进行调整 */
serverURL: '{{ .Site.Params.waline.serverURL }}',
lang: '{{ .Site.Params.waline.lang | default "zh-CN" }}',
visitor: '{{ .Site.Params.waline.visitor | default "匿名者" }}',
emoji: [
{{- range .Site.Params.waline.emoji }}
'{{ . }}',
{{- end }}
],
requiredMeta: [
{{- range .Site.Params.waline.requiredMeta }}
'{{ . }}',
{{- end }}
],
locale: {
admin: '{{ .Site.Params.waline.locale.admin | default "作者本人" }}',
placeholder: '{{ .Site.Params.waline.locale.placeholder | default "🍗所以我配有一条评论吗!" }}',
},
dark: '{{ .Site.Params.waline.dark | default "html.dark" }}',
};
// 点击"添加评论"按钮时,显示对应卡片下的评论区
window.showComment = function(element) {
const slug = element.getAttribute('data-slug');
const path = element.getAttribute('data-path');
const commentElement = document.getElementById('waline-' + slug);
// 如果已激活则清空
if (commentElement.classList.contains('active')) {
commentElement.classList.remove('active');
commentElement.innerHTML = '';
return;
}
// 移除其它所有已激活评论区
const allComments = document.querySelectorAll('.waline-container');
allComments.forEach(el => {
el.classList.remove('active');
el.innerHTML = '';
});
// 激活当前评论区
commentElement.classList.add('active');
// 初始化 Waline
init({
el: commentElement,
serverURL: walineParams.serverURL,
lang: walineParams.lang,
visitor: walineParams.visitor,
emoji: walineParams.emoji,
requiredMeta: walineParams.requiredMeta,
locale: walineParams.locale,
path: path,
dark: walineParams.dark,
});
}
</script>
<!-- 获取评论数 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// 获取所有评论按钮
const commentBtns = document.querySelectorAll('.moment-comment-btn');
// 确保有找到评论按钮
if (commentBtns.length > 0) {
commentBtns.forEach(button => {
const slug = button.getAttribute('data-slug'); // 获取按钮对应的 slug
const commentText = button.querySelector('.comment-text'); // 获取按钮中的评论文本
const serverURL = '{{ .Site.Params.waline.serverURL }}'; // 获取 Waline 服务器地址
// 输出调试信息,查看是否有多个按钮
console.log(`Processing button with slug: ${slug}`);
if (slug && commentText) {
// 假设你有一个获取评论数量的 API 或接口
fetch(`${serverURL}/api/comment?type=count&url=${slug}`)
.then(response => response.json())
.then(data => {
if (commentText) {
// 从 API 返回的数据中提取评论数
const commentCount = data.data && data.data[0] ? data.data[0] : 0; // 如果没有数据或评论数,默认为 0
// 输出调试信息,查看评论数
console.log(`Fetched comment count: ${commentCount}`);
// 更新评论按钮上的评论数量
commentText.textContent = `评论 (${commentCount})`; // 更新评论数
}
})
.catch(error => {
console.error('Error fetching comment count:', error);
if (commentText) {
// 如果 API 请求失败,保持评论数为 0
commentText.textContent = `评论 (0)`;
}
});
} else {
console.error('Slug or commentText missing for button:', button);
}
});
} else {
console.error('No comment buttons found');
}
});
</script>
{{ end }}
Step2 - Build options
content/mements/_index.md
---
title: 瞬间
build:
render: always
cascade:
- build:
list: local
publishResources: false
render: never
menu: main
weight: 30
---
Step3 - 修改页面样式
assets/css/extended/moments.css
/*
全局:亮色模式下的默认值
说明:PaperMod 会在 <html> 或 <body> 上添加 .dark 类切换暗色,
所以这里只需定义默认(亮色)和暗色时的变量即可。
*/
:root {
--card-bg: #fff;
--card-text: #333;
--tag-bg: #f2f4f5;
--tag-text: #333;
--comment-btn-color: #999;
--time-color: #999;
--tag-filter-bg: #fff;
--tag-filter-text: #333;
--tag-filter-hover-bg: #55ac68;
--gradient-mask: linear-gradient(180deg, rgba(255, 255, 255, 0), #ffffff 100%);
}
/* 暗色模式下的变量 */
.dark {
--card-bg: rgb(46, 46, 51);
--card-text: rgb(196, 196, 197); /* 对比度够高的浅色文字 */
--tag-bg: rgb(65, 66, 68); /* 比卡片再浅一些,或自己喜欢的颜色 */
--tag-text: #eee;
--comment-btn-color: #aaa;
--time-color: #ccc;
--tag-filter-bg: #555;
--tag-filter-text: #ddd;
--tag-filter-hover-bg: #55ac68;
--gradient-mask: linear-gradient(180deg, rgba(46, 46, 51, 0), rgb(46, 46, 51) 100%);
--tag-filter-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); /* 新增暗色阴影变量 */
}
/* 单列容器:依旧保持 vertical 布局 */
.moments-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin-top: 1.5rem;
}
/* 卡片:使用CSS变量 */
.moment-card {
width: 100%;
max-width: 800px; /* 每张卡片最多800px */
margin: 0 auto; /* 居中 */
position: relative;
background: var(--card-bg);
color: var(--card-text);
border-radius: 8px;
padding: 1rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
display: none; /* 初始隐藏,由JS控制显示 */
flex-direction: column;
justify-content: space-between;
}
.moment-card.hidden {
display: none; /* 隐藏元素并且不占据空间 */
}
/* 头像 & 作者 */
.moment-header {
position: relative;
display: flex; /* 水平排列头像、昵称和按钮 */
align-items: center; /* 垂直居中 */
justify-content: space-between; /* 头像和昵称左对齐,按钮右对齐 */
}
.moment-header .left-content {
display: flex;
align-items: center;
gap: 1rem; /* 固定间距,控制头像和昵称之间的距离 */
}
.moment-avatar{
width: 40px;
height: 40px;
object-fit: cover; /* 保证图片不变形 */
border-radius: 4px;
transition: transform 1s ease; /* 平滑的旋转过渡效果 */
cursor: pointer; /* 鼠标悬停时显示为可点击 */
}
.moment-avatar:active {
transform: rotate(360deg); /* 点击时旋转一圈 */
}
.moment-author {
font-weight: 600;
font-size: 16px;
/* 跟随卡片文字颜色 */
color: var(--card-text);
}
/* 主体内容 */
.moment-body {
font-size: 16px;
color: var(--card-text);
padding: 0;
line-height: 1.5;
}
.moment-body p {
margin-bottom: 0.6rem; /* 默认值可能是 1rem,改为 0.75rem 或更小 */
line-height: 1.6; /* 确保行间距仍然易读 */
}
.moment-body ol {
padding-left: 1rem; /* 调整左侧内边距,避免序号超出范围 */
margin-left: 0; /* 确保整体左对齐 */
list-style-position: inside; /* 确保序号在内容外部显示 */
}
.image-row {
display: grid;
gap: 0.4rem; /* 默认图片之间的间距 */
margin: 1rem 0; /* 上下与其他内容的间距 */
}
.image-row-1col {
grid-template-columns: repeat(1, 1fr); /* 单列布局 */
}
.image-row-2col {
grid-template-columns: repeat(2, 1fr); /* 双列布局 */
gap: 0.4rem; /* 自定义两列间距 */
}
.image-row-3col {
grid-template-columns: repeat(3, 1fr); /* 三列布局 */
gap: 0.2rem; /* 自定义三列间距 */
}
.image-row img {
width: 100%; /* 图片宽度占满格子 */
aspect-ratio: 1 / 1; /* 强制为正方形 */
object-fit: cover; /* 裁切图片内容,居中显示 */
display: block; /* 避免周围多余间隙 */
border-radius: 2px; /* 可选:增加圆角 */
}
/* 标签 */
.moment-tags {
margin-bottom: 1rem;
}
.moment-tag {
display: inline-block;
padding: 0.3em 0.6em;
margin-right: 0.5em;
margin-top: 1.2em;
background: var(--tag-bg);
color: var(--tag-text);
font-size: 14px;
border-radius: 4px;
}
/* 底部:时间 + 评论按钮 */
.moment-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
.moment-time span {
font-size: 14px;
color: var(--time-color); /* 比主体更淡一点 */
}
/* 评论按钮 */
.moment-comment-btn {
background: none;
border: none;
cursor: pointer;
padding: 0;
display: inline-flex;
align-items: center;
/* 使用变量 color */
color: var(--comment-btn-color);
transition: color 0.2s;
font-size: 14px;
}
.moment-comment-btn:hover {
/* 可在暗色下也进行不同程度的 hover 颜色变化 */
color: var(--card-text);
}
.moment-comment-btn svg {
width: 18px;
height: 18px;
fill: currentColor;
}
.moment-comment-btn .comment-text {
margin-left: 0.3em;
}
/* 评论容器激活时 */
.waline-container.active {
display: grid; /* 使用 flex 布局确保子元素正确排列 */
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px dashed rgb(223, 223, 223);
font-weight: normal;
box-sizing: border-box;
}
.waline-container .wl-avatar {
display: flex;
justify-content: center;
align-items: center;
}
.waline-container .wl-avatar img {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
border-radius: 50%;
}
.waline-container .wl-cards .wl-user .wl-user-avatar {
margin-top: 1em;
margin-right: 0.25em;
}
.waline-container .wl-cards .wl-user .verified-icon{
display: none;
}
/* 顶部筛选标签样式 */
.tags-filter {
margin-bottom: 20px;
}
.tags-filter ul {
list-style: none;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.tags-filter li {
margin: 0;
}
.tags-filter .tag-filter {
text-decoration: none;
color: var(--tag-filter-text);
cursor: pointer;
padding: 8px 16px;
background-color: var(--tag-filter-bg);
border-radius: 20px;
font-size: 14px;
transition: all 0.3s ease;
font-weight: normal;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); /* 缩小垂直偏移,降低透明度 */
}
.tag-filter.selected {
background-color: var(--tag-filter-hover-bg);
color: #fff;
font-weight: bold;
box-shadow: 0 2px 4px rgba(85, 172, 104, 0.1); /* 减少扩散半径和透明度 */
}
.tag-filter:hover {
background-color: var(--tag-filter-hover-bg);
color: #fff;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(85, 172, 104, 0.15); /* 保持微移效果但降低阴影强度 */
}
go.moment-loading {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
gap: 0.5rem;
}
.loading-spinner {
width: 24px;
height: 24px;
border: 2px solid var(--card-text);
border-top-color: transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-hint {
text-align: center;
padding: 0.5rem 0;
color: var(--card-text);
margin: 0 auto;
max-width: 600px;
opacity: 0.7;
font-size: 0.9em;
transition: opacity 0.3s ease;
}
.loading-hint[style*="block"],
.end-divider {
display: block !important;
}
.end-hint {
text-align: center;
padding: 2rem 0;
position: relative;
}
.end-divider {
color: #dcdcdc;
font-size: 0.9em;
text-align: center;
width: 100%;
padding: 2rem 0;
margin: 0 auto;
}
/* 修复提示容器样式(新增) */
.scroll-hint-container {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 1rem;
font-size: 0.95em;
}
.skeleton-content {
width: 100%;
padding: 0.5rem 0;
}
.skeleton-line {
height: 16px;
background: linear-gradient(90deg, var(--card-bg) 25%, rgba(128, 128, 128, 0.1) 50%, var(--card-bg) 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
margin-bottom: 0.5rem;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
Step 4 - 添加 Moments
在layouts/moments/
文件夹下添加 markdown 文件,每个 md 文件就是一条 moments。注意每个文件都需要包含以下 frontmatter:
---
date:
slug: {{date:YYMMDD-HHmmss}}
tags:
---
- date:即每条 moment 的发布时间,显示在卡片左下角
- slug:waline 评论区的唯一标识,注意每条的 slug 必须唯一,所以我直接使用精确到时分的时间自动生成
- tags:可添加多个,添加后会自动显示在上方的筛选区域