// ==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();
}
})();