// ==UserScript== // @name V2EX快捷查看回复 // @namespace http://tampermonkey.net/ // @version 1.1 // @description V2EX快捷查看回复对象 // @author xiyue // @license MIT // @match https://v2ex.com/t/* // @match https://www.v2ex.com/t/* // @icon https://www.google.com/s2/favicons?domain=v2ex.com // @require https://unpkg.com/axios@0.25.0/dist/axios.min.js // @grant none // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 重写回复函数,给@后面增加楼层数 replyOne = function(username) { setReplyBoxSticky(); const replyContent = document.getElementById("reply_content"); const oldContent = replyContent.value; const prefix = "@" + username + " #" + event.target.offsetParent.querySelector(".no").innerText; let newContent = ''; if (oldContent.length > 0) { if (oldContent != prefix) { newContent = oldContent + "\n" + prefix; } } else { newContent = prefix; } replyContent.focus(); replyContent.value = newContent; moveEnd(replyContent); } let style = document.querySelector('style').sheet, replyWidth = 642, // 悬浮窗口大小 svgIcon = ``, bgColor = window.getComputedStyle(document.querySelector("#Main .box"), null).backgroundColor, borderColor = window.getComputedStyle(document.querySelector(".cell"), null).borderBottomColor; style.insertRule(`.fixed-reply { transition: all 300ms; transform: translateY(-10px); pointer-events: none; opacity: 0; padding: 12px 20px; width: ${replyWidth}px; box-sizing: border-box; position: absolute; bottom: 30px; left: -14px; background: ${bgColor}; border-radius: 8px; box-shadow: 0 0 18px rgb(0 0 0 / 10%); border: solid 1px ${borderColor}; user-select: auto; }`, 1); style.insertRule(`.show-reply { position: relative; display: inline-flex; align-items: center; justify-content: flex-start; }`, 1); style.insertRule(` .show-reply:hover>.fixed-reply{ transition: all 300ms; transform: translateY(0); pointer-events: auto; opacity: 1; }`, 1); style.insertRule(`.show-reply:hover:before { content: ""; position: absolute; width: 160px; height: 10px; left: 0; bottom: 100%; }`, 1); style.insertRule(`.cell { transition:all 300ms; }`, 1); style.insertRule(`.cell.highlight { background-color: #FFE97F; }`, 1); let replyList = []; let lastNum = 0 function linkReply() { replyList = document.querySelectorAll(".reply_content"); // 遍历回复列表 replyList.forEach((el, index) => { let texts = el.parentNode.parentNode.querySelector(".no").innerText * 1; if (lastNum > texts) { console.error("评论顺序加载错误!", lastNum, texts) } else { lastNum = texts } // 获取所有@ el.querySelectorAll(".reply_content a").forEach((atEl) => { let quoteIndex = getIndex(el, atEl, index); let replyEl = getContent(atEl.innerText, quoteIndex, index); if (replyEl) { let tempNode = document.createElement("div"); tempNode.className = "show-reply"; tempNode.appendChild(replyEl); tempNode.appendChild(document.createRange().createContextualFragment(svgIcon)); atEl.parentNode.insertBefore(tempNode, atEl); replyEl.parentNode.insertBefore(atEl, replyEl); } }) }) // 添加监听事件,点击楼层号跳转到对应楼层 document.querySelectorAll(".reply_content").forEach(el => { el.parentNode.parentNode.querySelector(".no").addEventListener("click", function(event) { event.preventDefault(); event.stopPropagation(); gotoReply(this.innerText); }) }) } // 搜索楼层 function getContent(userId, quoteIndex, maxIndex) { let lastContent = null; // 先尝试搜索引用楼层 if (replyList[quoteIndex - 1].parentNode.querySelector("strong a").innerText === userId) { lastContent = replyList[quoteIndex - 1].parentNode.parentNode; } // 如果第一个范围没有找到则搜索第二范围 if (!lastContent) { for (var i = 0; i < maxIndex; i++) { if (replyList[i].parentNode.querySelector("strong a").innerText === userId) { lastContent = replyList[i].parentNode.parentNode; } } } if (lastContent) { var tempNode = document.createElement("div"); tempNode.className = "fixed-reply"; tempNode.addEventListener("click", function(event) { event.preventDefault(); }) tempNode.appendChild(lastContent.cloneNode(true)).querySelector("td:last-child").width = replyWidth - 48 - 10; tempNode.querySelector(".no").addEventListener("click", function(event) { event.preventDefault(); event.stopPropagation(); gotoReply(this.innerText); }) return tempNode; } else { return false; } } // 判断是否含有楼层号 function getIndex(el, atEl, index) { let elStr = el.innerHTML, atElStr = atEl.innerHTML, newReg = new RegExp(`@${atElStr}\\s+#\\d+`), regMatch = newReg.exec(elStr); if (regMatch) { let nums = regMatch[0].split("#")[1] * 1; return nums; } else { // 避免有人@自己导致引用出错 return index - 1 >= 0 ? index - 1 : 0; } } // 跳转到对应楼层 function gotoReply(index, page = 1) { for (var i = replyList.length - 1; i >= 0; i--) { let el = replyList[i]; // 如果没有找到指定楼层则跳转到最后一楼 if (el.parentNode.querySelector(".no").innerText == index || index === replyList.length - 1) { var replaceTop = el.parentNode.parentNode.parentNode.parentNode.parentNode.getBoundingClientRect().top, repId = el.parentNode.parentNode.parentNode.parentNode.parentNode.id; window.scrollTo({ top: window.scrollY + replaceTop, behavior: "smooth" }); document.querySelectorAll("#Main .cell[id]")[index - 1].className += " highlight"; (() => { setTimeout(() => { document.querySelectorAll("#Main .cell[id]")[index - 1].className = "cell"; }, 1500); })() history.pushState(null, null, `${window.location.origin}${window.location.pathname}${page === '1' ? '' : `?p=${page}`}#${repId}`); break; } } } // 处理评论翻页 let switchPage = document.querySelector(".page_normal"); if (switchPage) { autoLoadNextPage(switchPage.parentNode.querySelectorAll(".page_normal")); } else { linkReply(); } async function autoLoadNextPage(pages) { // 楼层跳转链接添加监听事件 let pageLink = document.querySelectorAll(".page_normal"), pageCurrent = document.querySelectorAll(".page_current"); var allEl = []; allEl.push.apply(allEl, pageLink); allEl.push.apply(allEl, pageCurrent); allEl.forEach(el => { el.addEventListener("click", function(event) { event.preventDefault(); let clickPageNum = event.target.innerText document.querySelectorAll(".page_current").forEach(el => { el.className = "page_normal"; }); document.querySelectorAll(".page_normal").forEach(el => { if (el.innerText === clickPageNum) { el.className = "page_current"; } }); let noNum = (clickPageNum - 1) * 100 + 1; gotoReply(noNum, clickPageNum); console.log(noNum); }) }) // 异步加载其他回复页面数据 for (let el of pages) { let req = await axios.get(el.getAttribute("href")), domData = req.data, template = document.createElement("template"), mainReplyEl = document.querySelectorAll("#Main .box .cell:last-child")[0]; template.innerHTML = domData; // 判断插入位置 let tempFirstId = template.content.querySelector("#Main .box .cell[id]").getAttribute("id").replace(/\D/g, "") * 1, cellEl = document.querySelectorAll("#Main .box .cell[id]"); for (const el of cellEl) { let listId = el.getAttribute("id").replace(/\D/g, "") * 1; if (listId > tempFirstId) { mainReplyEl = el; break; } } template.content.querySelectorAll("#Main .box .cell[id]").forEach((el, index) => { mainReplyEl.parentNode.insertBefore(el, mainReplyEl); }) } linkReply(); } })();