v2ex+

auto load item detail when mouseover title

  1. // ==UserScript==
  2. // @name v2ex+
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.19
  5. // @description auto load item detail when mouseover title
  6. // @author Silvio27
  7. // @match https://*.v2ex.com/*
  8. // @match https://v2ex.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=v2ex.com
  10. // @license GPLv3
  11. // @grant GM_addStyle
  12. // @grant GM_registerMenuCommand
  13.  
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18. console.log("script")
  19. topics_node_page(true, true, true, 600)
  20. member_replies_page(true, false, false, 0)
  21. linksToImgs()
  22.  
  23.  
  24. const menu_command_id = GM_registerMenuCommand("加载页面全部内容", function () {
  25. document.querySelectorAll(".triangle").forEach(e => {setTimeout(e.click(), 500)})
  26. }, "a");
  27.  
  28.  
  29. function topics_node_page(show_topic, show_fav_btn, show_reply, defaultHeight) {
  30. let topics
  31. if (document.getElementById("TopicsNode")) {
  32. // alert("TopicsNode")
  33. topics = document.querySelectorAll("#TopicsNode>.cell")
  34. } else {
  35. // alert("No TopicsNode")
  36. topics = document.querySelectorAll(".cell.item")
  37. }
  38. topics.forEach((element, index) => {
  39. let new_item = create_item("td", element.querySelector("tr"), element.querySelector("tr").lastElementChild,
  40. '▼', "triangle", 30)
  41. // 修改cursor_style
  42. cursor_style(new_item)
  43. new_item.onclick = function () {
  44. let url = element.querySelector(".topic-link").href
  45. let id = url.replace("https://www.v2ex.com/t/", "").split("#")[0] + "_topic"
  46. load_item_change_btn(element, id, url, new_item, show_topic, show_fav_btn, show_reply, defaultHeight)
  47.  
  48. }
  49.  
  50. })
  51. }
  52.  
  53. function member_replies_page(show_topic, show_fav_btn, show_reply, defaultHeight) {
  54. let dock_areas = document.querySelectorAll(".dock_area")
  55. dock_areas.forEach((element, index) => {
  56. // 创建新的列
  57. let new_item = create_item("span", element.querySelector("td"), "",
  58. '▼', "triangle", 30)
  59. cursor_style(new_item)
  60. new_item.onclick = function () {
  61. let url = element.querySelectorAll("a")[2].href
  62. let id = url.replace("https://www.v2ex.com/t/", "").split("#")[0] + "_reply"
  63. load_item_change_btn(element, id, url, new_item, show_topic, show_fav_btn, show_reply, defaultHeight)
  64. }
  65. })
  66. }
  67.  
  68. function load_item_change_btn(element, id, url, btn, show_topic, show_fav_btn, show_reply, defaultHeight) {
  69. let content = document.getElementById(id)
  70. if (content) {
  71. if (content.style.display === "none") {
  72. content.style.display = "block"
  73. btn.innerText = "▲"
  74. } else {
  75. content.style.display = "none"
  76. btn.innerText = "▼"
  77. }
  78. } else {
  79. btn.innerText = "加载中"
  80. document.body.style.cursor = "wait";
  81. btn.className = "content-loaded"
  82. load_item_topic_favBtn_reply(element, url, id, btn, show_topic, show_fav_btn, show_reply, defaultHeight)
  83.  
  84. }
  85. }
  86.  
  87. function load_item_topic_favBtn_reply(target_ele, url, id, load_btn, show_topic, show_fav_btn, show_reply, defaultHeight) {
  88. // 创建一个临时容器元素来容纳加载的内容
  89. const tempContainer = document.createElement('div');
  90. // 使用Ajax异步加载下一页的内容
  91. const xhr = new XMLHttpRequest();
  92. xhr.open('GET', url, true);
  93. xhr.onload = function () {
  94. if (xhr.status === 200) {
  95. tempContainer.innerHTML = xhr.responseText
  96. // 创建返回的内容元素
  97. let contents = document.createElement('div')
  98. contents.id = id
  99. contents.className = "woDeStyle"
  100.  
  101. // 绑定Escape折叠contents
  102. mouse_escape(contents)
  103.  
  104. if (show_topic) {
  105. // 获得 topic_content
  106. let topic_contents = tempContainer.querySelectorAll(".topic_content")
  107. contents.innerHTML += '<div class="topic-content-box"></div>'
  108. topic_contents.forEach((e) => {
  109. // 即 .topic-content-box
  110. contents.firstChild.appendChild(e.parentElement)
  111. })
  112. }
  113.  
  114. if (show_fav_btn) {
  115. // 添加topic_buttons
  116. let favorite_btn = tempContainer.querySelector(".topic_buttons")
  117. // 点击收藏,页面在新标签中打开
  118. let fav_ele = favorite_btn.querySelector("a")
  119. fav_ele.onclick = function (e) {
  120. // 阻止原始a标签打开新页面
  121. e.preventDefault()
  122. // 后台发送xhr
  123. mark_favourite(this)
  124. }
  125. contents.firstElementChild.appendChild(favorite_btn)
  126. }
  127.  
  128. if (show_reply) {
  129. // todo 展示不清楚为什么box会出现不同的情况,待研究
  130. // 获得 reply_content
  131. let reply_box = tempContainer.querySelectorAll(".box")
  132. reply_box.forEach((e, index) => {
  133. if (e.innerText.includes("条回复")) {
  134. contents.appendChild(e)
  135. }
  136. })
  137.  
  138. // 去除reply_content中头像及空格
  139. contents.querySelectorAll("tbody>tr").forEach((e) => {
  140. e.removeChild(e.firstElementChild)
  141. e.removeChild(e.firstElementChild)
  142. })
  143.  
  144. // 添加一个折叠按钮 todo 是否可以直接把tr中添加的拿来用?
  145. let hideBtn = document.createElement("div")
  146. hideBtn.innerText = "▲"
  147. hideBtn.className = "content-loaded"
  148. hideBtn.style.textAlign = "right"
  149. hideBtn.onclick = (() => {
  150. // 隐藏主题详情
  151. contents.style.display = "none"
  152. // 切换主题展开为关闭
  153. contents.parentElement.querySelector(".content-loaded").innerText = "▼"
  154. })
  155. cursor_style(hideBtn)
  156. contents.appendChild(hideBtn)
  157. }
  158.  
  159. // content添加到target_ele
  160. target_ele.appendChild(contents)
  161. // 解析markdown图片
  162. linksToImgs()
  163.  
  164. // 设置默认高度
  165. set_content_default_height(contents, defaultHeight)
  166.  
  167. // 修改折叠按钮为展开,切换样式
  168. set_btn_up(load_btn)
  169. }
  170. };
  171. xhr.send();
  172. }
  173.  
  174. function cursor_style(element) {
  175. element.addEventListener("mouseover", function () {
  176. document.body.style.cursor = "pointer";
  177. });
  178. element.addEventListener("mouseout", function () {
  179. document.body.style.cursor = "auto";
  180. });
  181. }
  182.  
  183. function create_item(tagName, element, element_place, inner_text, class_name, width) {
  184. let item = document.createElement(tagName)
  185. item.setAttribute("width", width)
  186. item.setAttribute("align", "center")
  187. item.setAttribute("class", class_name)
  188. item.innerText = inner_text
  189. if (element_place) {
  190. element.insertBefore(item, element_place)
  191. } else {
  192. element.appendChild(item)
  193. }
  194. return item
  195. }
  196.  
  197. function mark_favourite(ele) {
  198. let url = ele.href
  199. const xhr = new XMLHttpRequest();
  200. xhr.open('GET', url, true);
  201. xhr.onload = function () {
  202. if (xhr.status === 200) {
  203. if (ele.innerText !== "取消收藏") {
  204. ele.innerText = "取消收藏"
  205. ele.style.backgroundColor = "gold"
  206. ele.href = url.replace("fav", "unfav")
  207. } else {
  208. // todo 取消收藏,好像有页面重定向的问题
  209. console("目前需要手动处理")
  210. // ele.innerText = "加入收藏"
  211. // ele.style.backgroundColor = ""
  212. // ele.href = url.replace("unfav", "fav")
  213. }
  214. }
  215. }
  216. xhr.send();
  217.  
  218. }
  219.  
  220. function mouse_escape(element) {
  221. element.addEventListener('mouseover', function () {
  222. document.addEventListener('keydown', escKeyPressed);
  223. });
  224.  
  225. element.addEventListener('mouseout', function () {
  226. document.removeEventListener('keydown', escKeyPressed);
  227. });
  228.  
  229. function escKeyPressed(event) {
  230. if (event.key === 'Escape') {
  231. element.style.display = 'none';
  232. // todo 这个位置应该需要结构
  233. element.parentElement.querySelector(".content-loaded").innerText = "▼"
  234. }
  235. }
  236. }
  237.  
  238. function set_content_default_height(element, defaultHeight) {
  239. // 如果没有设置,默认高度600px;如果输入0,完整显示
  240. if (defaultHeight === 0) {
  241. defaultHeight = element.offsetHeight
  242. } else if (!defaultHeight) {
  243. defaultHeight = 600
  244. }
  245.  
  246. if (element.offsetHeight > defaultHeight) {
  247. element.style.height = defaultHeight + "px"
  248. } else {
  249. element.style.height = element.offsetHeight
  250. }
  251. }
  252.  
  253. function set_btn_up(element) {
  254. element.innerText = "▲"
  255. element.className = "content-loaded"
  256. document.body.style.cursor = "auto";
  257. }
  258.  
  259.  
  260. // 链接转图片
  261. // 修改自 https://greasyfork.org/zh-CN/scripts/424246
  262. // size调整为30%;考虑可以点击图片,窗口最大化
  263. // todo 可以考虑把外面的括号进行替换
  264. function linksToImgs() {
  265. let links = document.links;
  266. Array.from(links).forEach(function (_this) {
  267. if (/^https.*\.(?:jpg|jpeg|jpe|bmp|png|gif)/i.test(_this.href) && !(/<img\s/i.test(_this.innerHTML))) {
  268. _this.innerHTML = `<img src="${_this.href}" style="max-width: 30%!important;" />`;
  269. // alert("有图片被替换了")
  270. } else if (/^https:\/\/imgur\.com\/[a-z]+$/i.test(_this.href)) { // 针对没有文件后缀的 imgur 图床链接
  271. _this.innerHTML = `<img src="${_this.href}.png" style="max-width: 30%!important;" />`;
  272. }
  273. });
  274. }
  275.  
  276.  
  277.  
  278.  
  279.  
  280. let css = `
  281. .woDeStyle {
  282. /*height: 600px;*/
  283. padding: 10px;
  284. margin: 10px auto;
  285. border: 1px solid gray;
  286. border-radius: 10px;
  287. overflow: scroll;
  288. }
  289.  
  290. .topic-content-box {
  291. border-bottom: 2px dashed gray;
  292. padding-bottom: 10px;
  293. margin-bottom: 10px;
  294. }
  295.  
  296. .triangle {
  297. color: gray;
  298. padding-left: 10px;
  299. }
  300.  
  301. .content-loaded {
  302. color: greenyellow;
  303. padding-left: 10px;
  304. }
  305.  
  306. #Wrapper {
  307. background-color: var(--box-background-color) !important;
  308. background-image: none !important;
  309. }
  310.  
  311. .toggle-more-nodes {
  312. display: none;
  313. }
  314.  
  315. #nodes-more-children, #nodes-more-related {
  316. display: block !important;
  317. }
  318.  
  319.  
  320. `
  321. GM_addStyle(css)
  322.  
  323.  
  324.  
  325. })();