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








友链页一条评论下面挂了十几条你来我往,读起来像在拼拼图:
@朝朝听雨:好简短的域名,是因为UK不能备案吗 @HY:uk是英国顶级域,备不了一点 @朝朝听雨:哈哈哈,我备案等了15天了
谁在回谁,一眼看得出;回的是对方哪句话,全靠猜。人一多、话一长,@昵称 就彻底不够用了 —— 你能定位到人,定位不到「他那句话里的哪一句」。
Waline 是 Vue 渲染的,DOM 里翻一圈,每条回复的正文第一行是这样的:
1<p class="wl-reply-to">2 <a href="#4508715">@朝朝听雨</a><span>: </span>3</p>那个 href 是 #4508715 —— 被回复那条评论的 objectId。而页面里每条评论的节点,id 正好也是它:
1<div id="4508715" class="wl-card-item">…</div>2<div id="4508716" class="wl-card-item">…</div>也就是说,Waline 早就把「我在回哪一条」写在 DOM 里了,只是它自己没用(浏览器原生锚点跳转一跳就不知道跳哪去了,不如不用)。那我来用。
流程很短:拿到 href 里的 id → getElementById 找到目标评论 → 摘出昵称和正文 → 插一个引用块在 @昵称: 下面。
摘正文时有一件事必须做:把 @昵称: 那行前缀剥掉。因为目标本身可能也是一条回复,它自己的正文里也带着 回复 @某某:,直接取会让引用里再套一层 @,像回音。
1// 只剥副本里的 .wl-reply-to(那是「回复 @某某:」前缀,不是他说的话)2var clone = content.cloneNode(true);3var prefix = clone.querySelector(".wl-reply-to");4if (prefix) prefix.remove();5
6var text = (clone.textContent || "").replace(/\s+/g, " ").trim();cloneNode(true) 这个 true 是关键,下面会讲我漏了它的下场。
引用块本身是个 <button>,点了就滚过去并高亮:
1target.scrollIntoView({ block: "center", behavior: "smooth" });2target.classList.remove(FLASH);3void target.offsetWidth; // 强制回流,让动画能连着重放4target.classList.add(FLASH);void target.offsetWidth 那行是为了连点同一条时动画还能重放 —— 不强制一次回流,浏览器会把「移除类再立刻加回来」合并掉,第二次点就没反应了。

第一版 quoteOf 是这么写的:
1var prefix = content.querySelector(".wl-reply-to");2if (prefix) prefix.remove(); // ← content 是真实节点content 是从真实 DOM 上取的,所以这行 remove() 删的不是副本,是页面上那行 @朝朝听雨: 本身。
而我要引用的正是它旁边的内容 —— 结果就是:每被引用一次,那条评论的 @昵称: 就消失一次。写的时候完全没意识到,直到跑完一看统计对不上:17 条回复插了 17 个引用块,页面上却只剩 8 个 .wl-reply-to。
这类 bug 的危险在于它不报错、页面也照常渲染 —— 只是少了点东西,而且少得很自然,不专门去数根本发现不了。
修法就一个字:cloneNode(true)。
上一篇给回复做了折叠(默认展示 2 条,多出来的收起来)。这两个功能会撞车:
一条回复的引用块指向的目标,很可能正被折叠收着 —— 它 display: none,scrollIntoView 对它是没有任何反应的,点了就像没点一样。
所以折叠脚本那边开了个全局钩子:
1window.__ffReplyCollapseExpand = function (item) {2 var quote = item.parentElement;3 if (!quote || !quote.classList.contains("wl-quote")) return;4 var items = directItems(quote);5 var index = items.indexOf(item);6 if (index < 0) return;7 if (index < Number(quote.dataset.ffShown || 0)) return;8 apply(quote, Math.min(items.length, index + 1)); // 展开到这一条为止9};引用脚本点下去之前先问一句:
1if (target.classList.contains("ff-rp-hidden")) {2 window.__ffReplyCollapseExpand(target);3}两个脚本各自独立加载、互不依赖,钩子没挂上就退化成「只滚不展开」,不会报错。展开后折叠按钮的计数也会跟着更新(实测:默认 2 条时点一个指向第 4 条的引用,会展开到 4 条,按钮同步变成「展开更多(还剩 9 条)」)。
| 情况 | 处理 |
|---|---|
| 目标不在当前页(分页翻走了、评论被删) | 找不到节点,静默跳过,不插引用块,Waline 原样显示 |
| 目标是顶层评论 | 正常引用 |
| 目标本身也是回复 | 取其正文,但先剥掉它自己的 @ 前缀,避免套娃 |
| 目标被折叠收着 | 点引用时先自动展开到那一条 |
| 引用的是自己 | 跳过(target === item) |
| 正文太长 | 摘 120 字,CSS 再截到两行 —— 引用是提示「在回哪句」,不该跟正文抢长度 |
考虑到本机首页贴纸、相册那些页面的经验,取子元素我一律走直接子而不是 querySelector:一条顶层评论的 DOM 里还嵌着十几条回复,item.querySelector('.wl-content') 捞到谁都全看文档顺序。同一个道理,判断「这条是不是已经加过引用块」也要看直接子,不然会把自己递归判断成「已经处理过」。
实测友链页:17 条回复全部带上引用块,@昵称: 一行不少、位置也对(引用块紧跟在它后面),点引用能滚到原评论并高亮。
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!
部分内容可能已过时
分享你的想法,与大家交流讨论