
HY
折腾进行时
Hello, I'm HY.








留言板的互动全都藏在各个页面的评论区里。有人留了言,除了当场那一下,别处再也看不到 —— 新访客点进来,完全感知不到「这个站是活的」。
右栏本来就有个「最新动态」,把站长的更新摆在台面上。那就再挂一个「最近评论」,把访客的互动也摆在台面上 —— 而且全站都要有,不止某一个页面。

拿来的参考图是一个标准的右栏小组件:顶部一行小字眉题加一枚「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 插对应脚本。其它条目都锚定具体页面(友链页、书签页),我这条要全站生效,正则直接放开:
1{2 // 「最近评论」:全站加载 —— 桌面挂右栏、移动端挂底部栏,挂载逻辑在 js 里3 path: /.*/,4 id: "ff-recent-comments-js",5 src: "/assets/js/recent-comments.js?v=2",6},脚本第一个页面进来就装上,之后切页不重跑;每次 swup:page:view 只重新决策挂载点。
挂载点按视口分两套,这也是主题自己的布局规则:≥768px 进右栏 top 组件容器,插在「最新动态」(widget-layout[data-id="latest-dynamics"])之后;<768px 主题会把左右侧栏整块隐藏、改用页面底部的 #mobile-bottom-sidebar,组件就挂进里面的 #bottom-sidebar。两类容器都在 swup 的替换范围外,搬一次就永久有效,切页不会丢;再挂一个 matchMedia("(max-width: 767px)") 的 change 监听,跨断点时把节点搬到另一侧,缩放窗口也不会丢。
1function findMountPoint() {2 if (isMobileNow()) {3 var bar = document.getElementById("bottom-sidebar");4 if (bar) return { parent: bar, before: null };5 }6 var sidebar = document.getElementById("right-sidebar");7 if (!sidebar) return null;8 var wrapper = sidebar.firstElementChild; // top 组件容器(flex flex-col gap-4)9 if (!wrapper) return null;10 var latest = sidebar.querySelector('widget-layout[data-id="latest-dynamics"]');11 return { parent: wrapper, before: latest ? latest.nextSibling : null };12}数据:Waline 自带一个「最近评论」公开接口,前端直连即可:
1GET https://waline.9ll.uk/api/comment?type=recent&count=82→ { 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:
1function htmlToText(html) {2 var box = document.createElement("div");3 box.innerHTML = String(html || "");4 return box.textContent || "";5}只借 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) 把所有内联脚本校验一遍再验证,这个文件里已经躺着四段脚本,坏任何一段都可能连带一片。
recent-comments.js 顶部的 API_URL。FooterConfig.html 末尾的 .ff-rc-* 段,标题文案和「去留言板」链接在外壳 HTML 里。?v= 往上加:vercel.json 给 /assets/* 配了 30 天强缓存,不加版本号老访客拿到的还是旧文件。astro dev 起服务,用 puppeteer 量 clientWidth/scrollWidth 判断截断(肉眼会漏),再分别验亮/暗主题、1280px 窄栏、桌面/移动端两种挂载点、swup 往返,以及「每个页面类型都有」。最后是落地效果,亮色和暗色都跟着主题走:


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

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!
部分内容可能已过时
分享你的想法,与大家交流讨论