V2EX.AT

快速查看@的回复

  1. // ==UserScript==
  2. // @name V2EX.AT
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.6.1
  5. // @description 快速查看@的回复
  6. // @author cinhoo
  7. // @license GPL-3.0 License
  8. // @match https://*.v2ex.com/t/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=v2ex.com
  10. // @require https://unpkg.com/axios@1.4.0/dist/axios.min.js
  11. // @grant GM_addStyle
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. // 重写回复函数,给@后面增加楼层数
  18. replyOne = function (username) {
  19. setReplyBoxSticky();
  20. const replyContent = document.getElementById("reply_content");
  21. const oldContent = replyContent.value;
  22. const prefix = `@${username} #${event.target.offsetParent.querySelector(".no").textContent} `;
  23. let newContent = '';
  24. if (oldContent.length > 0) {
  25. if (oldContent != prefix) {
  26. newContent = `${oldContent}\n${prefix}`;
  27. }
  28. } else {
  29. newContent = prefix;
  30. }
  31. replyContent.focus();
  32. replyContent.value = newContent;
  33. moveEnd(replyContent);
  34. }
  35.  
  36. GM_addStyle(`
  37. #myDialog {
  38. display: none;
  39. position: absolute;
  40. min-width: 30vw;
  41. max-width: 60vw;
  42. padding: 20px;
  43. background-color: var(--box-background-color);
  44. border: 1px solid var(--box-border-color);
  45. box-shadow: 2px 2px 1px var(--box-border-color);
  46. }
  47. #myDialog strong a,
  48. #myDialog .thank {
  49. font-size: 14px;
  50. }
  51. `);
  52.  
  53. let myDialog = `<div id="myDialog"></div>`;
  54. document.body.insertAdjacentHTML("beforeend", myDialog);
  55. let dialog = document.querySelector("#myDialog");
  56.  
  57. let closeDialogId;
  58. let closeDialog = (e) => {
  59. closeDialogId = setTimeout(() => {
  60. dialog.style.display = "none";
  61. }, 300);
  62. };
  63.  
  64. let openDialog = (e, atMember, anchor, innerHTML) => {
  65. clearTimeout(closeDialogId);
  66.  
  67. let scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
  68. let scrollY = document.documentElement.scrollTop || document.body.scrollTop;
  69.  
  70. let layerX = e.layerX || e.clientX + scrollX;
  71. let layerY = e.layerY || e.clientX + scrollX;
  72.  
  73. let atMemberRect = atMember.getBoundingClientRect();
  74. let offsetLeft = layerX - (atMemberRect.left + scrollX);
  75. let offsetTop = layerY - (atMemberRect.top + scrollY);
  76. let offsetRight = (atMemberRect.right + scrollX) - layerX;
  77. let offsetBottom = (atMemberRect.bottom + scrollY) - layerY;
  78.  
  79. dialog.innerHTML = innerHTML;
  80. dialog.style.display = "block";
  81. let dialogRect = dialog.getBoundingClientRect();
  82. if (e.clientY >= dialogRect.height + 5) {
  83. dialog.style.left = `${e.pageX + offsetRight}px`;
  84. dialog.style.top = `${e.pageY - offsetTop - dialogRect.height - 5}px`;
  85. } else {
  86. dialog.style.left = `${e.pageX + offsetRight}px`;
  87. dialog.style.top = `${e.pageY + offsetBottom + 5}px`;
  88. }
  89. dialog.querySelector(".no").onclick = () => window.location.href = anchor;
  90. };
  91.  
  92. dialog.onmouseenter = (e) => clearTimeout(closeDialogId);
  93. dialog.onmouseleave = (e) => closeDialog(e);
  94.  
  95. let allCells = [];
  96.  
  97. let linkReply = async (doc, atMembers, page) => {
  98. let cells = [...doc.querySelectorAll("div.cell[id^='r_']")].reverse();
  99. allCells.push(...cells.flatMap(cell => ({ page: page, cell: cell })));
  100.  
  101. atMembers = atMembers.filter(atMember => {
  102. let cell;
  103. if (atMember.atNo) {
  104. cell = cells.find(cell => atMember.atNo == +cell.querySelector(".no").textContent && cell.querySelector("strong a").textContent == atMember.atMember.textContent);
  105. } else {
  106. cell = cells.find(cell => atMember.no > +cell.querySelector(".no").textContent && cell.querySelector("strong a").textContent == atMember.atMember.textContent);
  107. }
  108. if (!cell) return true;
  109.  
  110. let anchor = atMember.page != page ? `?p=${page}#${cell.id}` : `#${cell.id}`;
  111.  
  112. atMember.atMember.onmouseenter = (e) => openDialog(e, atMember.atMember, anchor, cell.innerHTML);
  113. atMember.atMember.onmouseleave = (e) => closeDialog(e);
  114.  
  115. return false;
  116. });
  117.  
  118. if (atMembers.length > 0) {
  119. if (page == 1) {
  120. atMembers.forEach(atMember => {
  121. let item = allCells.find(item => {
  122. return atMember.no > +item.cell.querySelector(".no").textContent && item.cell.querySelector("strong a").textContent == atMember.atMember.textContent
  123. });
  124. if (item) {
  125. let anchor = atMember.page != item.page ? `?p=${item.page}#${item.cell.id}` : `#${item.cell.id}`;
  126.  
  127. atMember.atMember.onmouseenter = (e) => openDialog(e, atMember.atMember, anchor, item.cell.innerHTML);
  128. atMember.atMember.onmouseleave = (e) => closeDialog(e);
  129. }
  130. });
  131.  
  132. allCells.length = 0;
  133. } else {
  134. let prevPage = doc.querySelector(`a[href^="?p=${page - 1}"]`);
  135. if (!prevPage) return;
  136.  
  137. page = +prevPage.textContent;
  138.  
  139. let resp = await axios.get(prevPage.href)
  140. let template = doc.createElement("template");
  141. template.innerHTML = resp.data;
  142.  
  143. linkReply(template.content, atMembers, page);
  144. }
  145. }
  146. };
  147.  
  148. let currentPage = document.querySelector("a.page_current");
  149. let page = currentPage && +currentPage.textContent || 1
  150.  
  151. let cells = [...document.querySelectorAll("div.cell[id^='r_']")].reverse();
  152. let atMembers = cells.flatMap(cell => [...cell.querySelectorAll(".reply_content a")]
  153. .filter(atMember => atMember.getAttribute("href").startsWith("/member/"))
  154. .map(atMember => {
  155. let atNo = cell.textContent.match(new RegExp(`@${atMember.textContent} #(\\d+)`));
  156. return {
  157. atMember: atMember,
  158. atNo: atNo && +atNo[1],
  159. no: +cell.querySelector(".no").textContent,
  160. page: page
  161. };
  162. }));
  163.  
  164. linkReply(document, atMembers, page);
  165. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址