全站加了个「最近评论」:数据直连 Waline,主题文件一个没改

1772 字
9 分钟
全站加了个「最近评论」:数据直连 Waline,主题文件一个没改
全站加了个「最近评论」:数据直连 Waline,主题文件一个没改

留言板的互动全都藏在各个页面的评论区里。有人留了言,除了当场那一下,别处再也看不到 —— 新访客点进来,完全感知不到「这个站是活的」。

右栏本来就有个「最新动态」,把站长的更新摆在台面上。那就再挂一个「最近评论」,把访客的互动也摆在台面上 —— 而且全站都要有,不止某一个页面。

落在右栏「最新动态」下方的「最近评论」
落在右栏「最新动态」下方的「最近评论」

参考站长什么样#

拿来的参考图是一个标准的右栏小组件:顶部一行小字眉题加一枚「Waline · 最新 8 条」徽章,大标题「最近评论」,右边一个「去留言板」链接;下面是评论列表,每条 = 圆形头像 + 昵称 + 相对时间 + 「评论于 /某路径」+ 两行摘要;卡片底部一行小字「已展示 8 条公开评论 · 内容来自 Waline」。

我的版本把眉题和徽章去掉(跟本站其它侧栏组件对齐,只留标题条 + 右侧链接),列表结构和参考基本一致。

我的做法:只动两个文件#

要挂载的页面是上游文件,侧栏的 SideBar.astro 也是 —— 它的 componentMap 写死了支持的组件类型,想加一种新 widget 就得改它。所以还是走本站用惯了的页脚注入:

文件状态干什么
src/config/FooterConfig.html改(追加)组件外壳 DOM + 全部样式 + PAGE_SCRIPTS 全站加载条目
public/assets/js/recent-comments.js新增拉 Waline 接口、渲染列表、挂载/隐藏、监听 swup 事件

页脚本身就在 #swup-container 之外,整页加载才跑一次,正好用来挂 swup:page:view。PAGE_SCRIPTS 是页脚里现成的机制:一个 pathname 正则数组,匹配上了才去 document.head 插对应脚本。其它条目都锚定具体页面(友链页、书签页),我这条要全站生效,正则直接放开:

{
// 「最近评论」:全站加载 —— 桌面挂右栏、移动端挂底部栏,挂载逻辑在 js 里
path: /.*/,
id: "ff-recent-comments-js",
src: "/assets/js/recent-comments.js?v=2",
},

脚本第一个页面进来就装上,之后切页不重跑;每次 swup:page:view 只重新决策挂载点。

挂载点按视口分两套,这也是主题自己的布局规则:≥768px 进右栏 top 组件容器,插在「最新动态」(widget-layout[data-id="latest-dynamics"])之后;<768px 主题会把左右侧栏整块隐藏、改用页面底部的 #mobile-bottom-sidebar,组件就挂进里面的 #bottom-sidebar。两类容器都在 swup 的替换范围外,搬一次就永久有效,切页不会丢;再挂一个 matchMedia("(max-width: 767px)") 的 change 监听,跨断点时把节点搬到另一侧,缩放窗口也不会丢。

function findMountPoint() {
if (isMobileNow()) {
var bar = document.getElementById("bottom-sidebar");
if (bar) return { parent: bar, before: null };
}
var sidebar = document.getElementById("right-sidebar");
if (!sidebar) return null;
var wrapper = sidebar.firstElementChild; // top 组件容器(flex flex-col gap-4)
if (!wrapper) return null;
var latest = sidebar.querySelector('widget-layout[data-id="latest-dynamics"]');
return { parent: wrapper, before: latest ? latest.nextSibling : null };
}

核心机制#

数据:Waline 自带一个「最近评论」公开接口,前端直连即可:

GET https://waline.9ll.uk/api/comment?type=recent&count=8
→ { errno: 0, data: [{ nick, avatar, comment, orig, url, time, status, ... }] }

响应头带 Access-Control-Allow-Origin: *,CORS 是放开的,不需要代理。url 就是评论所在页面的路径(/guestbook/、/friends/、/posts/...),正好当「评论于」和跳转目标。拿到的数据按 status === "approved" 再过滤一遍,宁滥毋缺。

渲染:头像用接口返回的 avatar;字段为空时垫一个「昵称首字 + 哈希色相」的文字头像,img 的 error 事件一到就移除图片露出文字。时间显示成相对时间(N 分钟前 / N 小时前 / N 天前)。摘要统一截到 120 字,CSS 再 -webkit-line-clamp: 2 收两行。

缓存:localStorage 存 2 分钟。全站任何页面之间来回跳都不会重复请求,停留超过 2 分钟再打开才重新拉。

样式:全部写在页脚的 <style> 里,只用主题 CSS 变量(--primary、--content-meta、--btn-plain-bg-hover)和 card-base,不碰 Tailwind 工具类 —— 注入 HTML 里的工具类一旦没被内容探测扫到就是静默失效,这个坑本站以前踩过。几何复刻 WidgetLayout:标题条 ml-8/mt-4/mb-2 + 主题色竖条、内容区 px-4/pt-4、卡片 pb-4,视觉上和其它侧栏组件完全一致。

必要的技术坑#

一、orig 字段不是纯文本。 Waline 返回里有两个内容字段:comment 是渲染后的 HTML,orig 是渲染前的原始 HTML —— 名字看着像「原文」,实际含 <span class="wl-badge">AI</span> 这种标签。直接把 orig 当文本塞进去,摘要里就会露出一排 HTML 标签。正解是用渲染后的 comment,过一遍 DOM 取 textContent:

function htmlToText(html) {
var box = document.createElement("div");
box.innerHTML = String(html || "");
return box.textContent || "";
}

只借 innerHTML 解析、马上读 textContent,不把用户内容插回页面,没有 XSS 面。

二、类名撞车,白吃 32px 内边距。 条目里的文字容器图省事叫了 ff-rc-body,跟组件内容外包装同一个类 —— 外包装那条 padding: 1rem 规则把每条评论的正文区也吃了,meta 行从 194px 缩到 162px,昵称被挤得只剩一个字。改名 ff-rc-main 隔离。注入样式和 JS 生成的类名在同一个命名空间里,下手前先 grep 一遍。

三、窄栏 flex:不设 grow,宽度就按 max-content 算。 摘要是两行 clamp 的段落,但它的 max-content 是一整行不换刀的宽(含长 URL,轻松 600px+)。文字容器没有 flex-grow 时,flex 基准取这个值,行内怎么分都不对。补 flex: 1 1 auto; min-width: 0 让容器稳定吃掉剩余宽度。另外meta 行里昵称设 flex: 0 0 auto 永不收缩 —— 窄栏放不下时截断「评论于 /路径」,身份比路径重要。

四、内联脚本一个语法错误,整个增强静默失效。 调试中途 PAGE_SCRIPTS 整段没执行,脚本压根没被请求,查了半天才发现是编辑工具把数组某行的缩排弄坏了,var PAGE_SCRIPTS = [...] 语法错误 → 整个 IIFE 不跑 → 既没有报错也没有任何效果。改完 FooterConfig.html 先用 new Function(scriptText) 把所有内联脚本校验一遍再验证,这个文件里已经躺着四段脚本,坏任何一段都可能连带一片。

上手步骤#

  • 换 Waline 地址或显示条数:改 recent-comments.js 顶部的 API_URL。
  • 改外观:样式全在 FooterConfig.html 末尾的 .ff-rc-* 段,标题文案和「去留言板」链接在外壳 HTML 里。
  • 改了 JS 记得把 ?v= 往上加:vercel.json 给 /assets/* 配了 30 天强缓存,不加版本号老访客拿到的还是旧文件。
  • 验证:astro dev 起服务,用 puppeteer 量 clientWidth/scrollWidth 判断截断(肉眼会漏),再分别验亮/暗主题、1280px 窄栏、桌面/移动端两种挂载点、swup 往返,以及「每个页面类型都有」。

最后是落地效果,亮色和暗色都跟着主题走:

亮色主题下的「最近评论」
亮色主题下的「最近评论」

暗色主题下的「最近评论」
暗色主题下的「最近评论」

移动端(375px)挂在页面底部的组件栏里,卡片拉满内容宽,meta 行空间充裕,「评论于」不再需要截断:

移动端底部组件栏里的「最近评论」
移动端底部组件栏里的「最近评论」

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
全站加了个「最近评论」:数据直连 Waline,主题文件一个没改
https://www.9ll.uk/posts/blog/recentcomments/
作者
HY
发布于
2026-10-12
许可协议
CC BY-NC-SA 4.0
相关文章智能推荐
1
首页加了一条会滚的动态:参考楠枝小筑,一个主题文件都没改
博客在楠枝小筑首页看到一条会一直横滚的「瞬间」条,想在自己站上也挂一个。他用的是 Ethereal 主题,代码没法照抄,所以只搬机制:Memos 数据在客户端拉,滚动靠 CSS 把轨道复制成两份,DOM 走页脚注入再加一点点搬运。最后改动落在两个文件里,主题源码一个没碰。
2
全站加了个「天气预报」:接口免费,而且按访客 IP 定位
博客刷参考站时看到他右栏挂着一张天气预报卡,从标题竖条到「收起」按钮整个搬了过来。接口在 uapis.cn,免费、不用 key,而且不传参数时按访客 IP 定位 —— 谁来串门看到的就是谁头顶上的天。踩了两个坑:参考站那套图标是 Astro 构建期渲染的 symbol、动态插进去取不到;指标网格的 auto-fit 在 280px 右栏只排得出一列,卡片被顶到 925px。
3
全站鼠标指针换成《俺妹》主题:亮色桐乃、深色黑猫
博客这次更新把整站的鼠标指针换了一遍:亮色主题配高坂桐乃、深色主题配五更琉璃(黑猫),各 28 枚共 56 枚,正常选择、链接、文本、禁用、拖拽这些场景全部覆盖。素材来自两张总览图,切图时发现它们不是普通白底图 —— 人物衣服和箭头内部都是纯白,跟我页面的白同色,按我处理贴纸那套「全局去白底」会把图形整个掏空。CSS 那边我把「用哪张图 + 热点坐标」全塞进了自定义属性,28 条规则只写一遍,切换主题只换一组变量。
4
评论区回复默认展开 2 条,长串收在「展开更多」后面
博客友链页有条评论下面挂了 13 条回复,往下翻要滑过整屏;可全收起来又太狠,访客第一眼什么都看不到。最后定成默认展示 2 条,多出来的才收着,还有剩就给个「展开更多」,摊完能收回去。做法照旧只加文件不动上游。踩坑集中在一点上:Waline 是 Vue 渲染的,DOM 会重绘、会随 swup 换页被整体替换,所以折叠状态不能只靠一次性的 DOM 操作。
5
回复带上引用:别再让读者猜「@昵称」在回哪一句
博客Waline 的二级回复只渲染一行「@昵称:」,谁在回谁很清楚,回的是哪一句全靠猜。翻 DOM 发现它其实留了线索 —— 回复里那个 @昵称 的链接 href 就是被回复评论的 objectId。顺着锚点把目标评论捞回来,摘成引用块插在下面,点它还能滚过去高亮。踩了个坑:剥 @ 前缀时忘了 clone,把页面上那行整个删了。
随机文章随机推荐

评论区

Profile Image of the Author
HY
Hello, I'm HY.
公告
迷茫.... 为了访问速度,本站将会在11月上旬至中旬迁移到0dm.cn并完成备案
分类
标签
最新动态
站点统计
文章
74
分类
8
标签
192
总字数
161,979
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Vercel
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录