V2EXcellent.js

A Better V2EX

当前为 2022-02-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name V2EXcellent.js
  3. // @namespace http://vitovan.github.io/v2excellent.js/
  4. // @version 1.1.10
  5. // @description A Better V2EX
  6. // @author VitoVan
  7. // @include http*://*v2ex.com/*
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/markdown-it/8.4.2/markdown-it.min.js
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. $('document').ready(function() {
  13. window.loaded = true;
  14. });
  15.  
  16. var POST_PROCESS_FUNCS = [
  17. function done() {
  18. console.log('V2EXcellented!');
  19. },
  20. ];
  21.  
  22. // markdown-it 初始化
  23. var md = window.markdownit({
  24. html: true,
  25. linkify: true,
  26. breaks: true,
  27. langPrefix: "hljs ",
  28. highlight: function (str, lang) {
  29. if (lang && hljs.getLanguage(lang)) {
  30. try {
  31. return hljs.highlight(lang, str).value;
  32. } catch (__) { }
  33. }
  34. return ''; // use external default escaping
  35. }
  36. });
  37.  
  38. // 图片链接自动转换成图片 代码来自caoyue@v2ex
  39. POST_PROCESS_FUNCS.push(function linksToImgs() {
  40. var links = document.links;
  41. for (var x in links) {
  42. var link = links[x];
  43. if (
  44. /^http.*\.(?:jpg|jpeg|jpe|bmp|png|gif)/i.test(link.href) &&
  45. !/<img\s/i.test(link.innerHTML)
  46. ) {
  47. link.innerHTML =
  48. "<img title='" + link.href + "' src='" + link.href + "' style='max-width:100%' />";
  49. }
  50. }
  51. });
  52.  
  53.  
  54. // 回复内容做markdown渲染
  55. POST_PROCESS_FUNCS.push(function mdRender() {
  56. $(".reply_content").each(function(index, item) {
  57. var replyContent = $(item).html();
  58. replyContent = replyContent.replace(/<br\s*\/?>/gi,"\r\n");
  59. $(item).html(md.render(replyContent));
  60. });
  61. });
  62.  
  63. function postProcess() {
  64. $(POST_PROCESS_FUNCS).each(function(i, f) {
  65. if (typeof f === 'function') {
  66. f();
  67. console.log('V2EXcellent Post Processing: ' + f.name);
  68. }
  69. });
  70. }
  71.  
  72. var language = (
  73. window.navigator.userLanguage || window.navigator.language
  74. ).toLowerCase();
  75.  
  76. var currentLocation = location.href;
  77. //If this is the thread page
  78. if (currentLocation.match(/\/t\/\d+/g)) {
  79. //Enable Reply Directly Feature
  80. $('div.topic_buttons').append(
  81. ' &nbsp;<a " href="#;" onclick="$(\'#reply_content\').focus();" class="tb">回复</a>',
  82. );
  83. //Enable Img Uploader Feature
  84. enableUploadImg();
  85. var comments = [];
  86. //loading
  87. showSpinner();
  88. //Get comments from current page
  89. fillComments($('body'));
  90. //Get other pages comments
  91. var CURRENT_PAGE_URLS = [];
  92. $('a[href].page_normal').each(function(i, o) {
  93. if (CURRENT_PAGE_URLS.indexOf(o.href) === -1) {
  94. CURRENT_PAGE_URLS.push(o.href);
  95. }
  96. });
  97. var LEFT_PAGES_COUNT = CURRENT_PAGE_URLS.length;
  98. var CURRENT_PAGE = 0;
  99. var DOMS = [$(document)];
  100. if (LEFT_PAGES_COUNT > 0) {
  101. $(CURRENT_PAGE_URLS).each(function(i, o) {
  102. $.get(o, function(result) {
  103. var resultDom = $('<output>').append($.parseHTML(result));
  104. DOMS.push(resultDom);
  105. fillComments(resultDom);
  106. // 替换收藏的链接
  107. var collectUrl = resultDom
  108. .find('.topic_buttons .tb:contains("收藏")')
  109. .attr('href');
  110. $('.topic_buttons .tb:contains("收藏")').attr('href', collectUrl);
  111. CURRENT_PAGE++;
  112. //if all comments are sucked.
  113. if (CURRENT_PAGE === LEFT_PAGES_COUNT) {
  114. //stack'em
  115. stackComments();
  116. //reArrange
  117. reArrangeComments();
  118. // post process functions
  119. postProcess();
  120. }
  121. });
  122. });
  123. } else {
  124. stackComments();
  125. //reArrange
  126. reArrangeComments();
  127. // post process functions
  128. postProcess();
  129. }
  130. // Clear Default Pager
  131. $('a[href^="?p="]')
  132. .parents('div.cell')
  133. .remove();
  134. } else if (currentLocation.match(/\/new/)) {
  135. $(
  136. '<a href="https://imgur.com/upload" target="_blank" style="padding:0 5px;">上传图片</a>',
  137. ).insertAfter($('button[onclick="previewTopic();"]'));
  138. }
  139.  
  140. function jumpToReply() {
  141. var floorSpecArr = currentLocation.match(/#reply\d+/g);
  142. var floorSpec = floorSpecArr && floorSpecArr.length ? floorSpecArr[0] : false;
  143. if (floorSpec) {
  144. floorSpec = floorSpec.match(/\d+/g)[0];
  145. var specFloor = $('span.no').filter(function() {
  146. return $(this).text() === floorSpec;
  147. });
  148. var scrollFunc = function() {
  149. window.scrollTo(0, specFloor.offset().top - $('body').offset().top);
  150. };
  151. if (window.loaded) {
  152. scrollFunc();
  153. } else {
  154. window.onload = function() {
  155. setTimeout(function() {
  156. scrollFunc();
  157. }, 1);
  158. };
  159. }
  160. }
  161. }
  162.  
  163. //Remove #reply42 from index
  164. $('span.item_title>a').attr('href', function(i, val) {
  165. return val.replace(/#reply\d+/g, '');
  166. });
  167.  
  168. function fillComments(jqDom) {
  169. jqDom.find('div[id^="r_"]').each(function(i, o) {
  170. var cmno = parseInt(
  171. $(o)
  172. .find('span.no')
  173. .text(),
  174. );
  175. comments[cmno] = {
  176. id: $(o).attr('id'),
  177. no: cmno,
  178. user: $(o)
  179. .find('strong>a')
  180. .text(),
  181. content: $(o)
  182. .find('div.reply_content')
  183. .text(),
  184. mentioned: (function() {
  185. var mentionedNames = [];
  186. $(o)
  187. .find('div.reply_content>a[href^="/member/"]:not("dark")')
  188. .each(function(i, o) {
  189. mentionedNames.push(o.innerHTML);
  190. });
  191. return mentionedNames;
  192. })(),
  193. subComments: [],
  194. };
  195. });
  196. }
  197.  
  198. //Enable Floor Specification Feature
  199. $('a[href="#;"]:has(img[alt="Reply"])').click(function(e) {
  200. var floorNo = $(e.currentTarget)
  201. .parent()
  202. .find('span.no')
  203. .text();
  204. replyContent = $('#reply_content');
  205. oldContent = replyContent.val().replace(/^#\d+ /g, '');
  206. postfix = ' ' + '#' + floorNo + ' ';
  207. newContent = '';
  208. if (oldContent.length > 0) {
  209. if (oldContent != postfix) {
  210. newContent = oldContent + postfix;
  211. }
  212. } else {
  213. newContent = postfix;
  214. }
  215. replyContent.focus();
  216. replyContent.val(newContent);
  217. moveEnd($('#reply_content'));
  218. });
  219.  
  220. //Enable Gift ClickOnce Feature
  221. $('a[href="/mission/daily"]')
  222. .attr('id', 'gift_v2excellent')
  223. .attr('href', '#')
  224. .click(function() {
  225. $('#gift_v2excellent').text('正在领取......');
  226. $.get('/mission/daily', function(result) {
  227. var giftLink = $('<output>')
  228. .append($.parseHTML(result))
  229. .find('input[value^="领取"]')
  230. .attr('onclick')
  231. .match(/\/mission\/daily\/redeem\?once=\d+/g)[0];
  232. $.get(giftLink, function(checkResult) {
  233. var okSign = $('<output>')
  234. .append($.parseHTML(checkResult))
  235. .find('li.fa.fa-ok-sign');
  236. if (okSign.length > 0) {
  237. $.get('/balance', function(result) {
  238. var amount = $('<output>')
  239. .append($.parseHTML(result))
  240. .find('table>tbody>tr:contains("每日登录"):first>td:nth(2)')
  241. .text();
  242. $('#gift_v2excellent').html(
  243. '已领取 <strong>' + amount + '</strong> 铜币。',
  244. );
  245. setTimeout(function() {
  246. $('#Rightbar>.sep20:nth(1)').remove();
  247. $('#Rightbar>.box:nth(1)').remove();
  248. }, 2000);
  249. });
  250. }
  251. });
  252. });
  253. return false;
  254. });
  255.  
  256. //Get comment's parent
  257. function findParentComment(comment) {
  258. var parent;
  259. if (comment) {
  260. var floorRegex = comment.content.match(/#\d+ /g);
  261. if (floorRegex && floorRegex.length > 0) {
  262. var floorNo = parseInt(floorRegex[0].match(/\d+/g)[0]);
  263. parent = comments[floorNo];
  264. } else {
  265. for (var i = comment.no - 1; i > 0; i--) {
  266. var cc = comments[i];
  267. if (cc) {
  268. if (
  269. $.inArray(cc.user, comment.mentioned) !== -1 &&
  270. parent === undefined
  271. ) {
  272. parent = cc;
  273. }
  274. //If they have conversation, then make them together.
  275. if (
  276. comment.mentioned.length > 0 &&
  277. cc.user === comment.mentioned[0] &&
  278. cc.mentioned[0] === comment.user
  279. ) {
  280. parent = cc;
  281. break;
  282. }
  283. }
  284. }
  285. }
  286. }
  287. return parent;
  288. }
  289.  
  290. //Stack comments, make it a tree
  291. function stackComments() {
  292. for (var i = comments.length - 1; i > 0; i--) {
  293. var parent = findParentComment(comments[i]);
  294. if (parent) {
  295. parent.subComments.unshift(comments[i]);
  296. comments.splice(i, 1);
  297. }
  298. }
  299. }
  300.  
  301. function getCommentDom(id) {
  302. var commentDom;
  303. $.each(DOMS, function(i, o) {
  304. var result = o.find('div[id="' + id + '"]');
  305. if (result.length > 0) {
  306. commentDom = result;
  307. }
  308. });
  309. return commentDom;
  310. }
  311.  
  312. function moveComment(comment, parent) {
  313. if (comment) {
  314. var commentDom = getCommentDom(comment.id);
  315. $.each(comment.subComments, function(i, o) {
  316. moveComment(o, commentDom);
  317. });
  318. commentDom.appendTo(parent);
  319. }
  320. }
  321.  
  322. function getCommentBox() {
  323. var commentBox = $('#Main>div.box:nth(1)');
  324. if (commentBox.length === 0) {
  325. // Maybe using mobile
  326. commentBox = $('#Wrapper>div.content>div.box:nth(1)');
  327. if ($('#v2excellent-mobile-tip').length === 0) {
  328. $(
  329. '<div class="cell" id="v2excellent-mobile-tip" style="background: #CC0000;font-weight: bold;text-align: center;"><span><a style="color:white;text-decoration:underline;" target="_blank" href="https://github.com/VitoVan/v2excellent.js/issues/7#issuecomment-304674654">About V2EXcellent.js on Mobile</a></span></div>',
  330. ).insertBefore('#Wrapper>div.content>div.box:nth(1)>.cell:first');
  331. }
  332. }
  333. return commentBox;
  334. }
  335.  
  336. function showSpinner() {
  337. var commentBox = getCommentBox();
  338. $('body').append(
  339. '<style>.spinner{width:40px;height:40px;position:relative;margin:100px auto}.double-bounce1,.double-bounce2{width:100%;height:100%;border-radius:50%;background-color:#333;opacity:.6;position:absolute;top:0;left:0;-webkit-animation:sk-bounce 2.0s infinite ease-in-out;animation:sk-bounce 2.0s infinite ease-in-out}.double-bounce2{-webkit-animation-delay:-1.0s;animation-delay:-1.0s}@-webkit-keyframes sk-bounce{0%,100%{-webkit-transform:scale(0.0)}50%{-webkit-transform:scale(1.0)}}@keyframes sk-bounce{0%,100%{transform:scale(0.0);-webkit-transform:scale(0.0)}50%{transform:scale(1.0);-webkit-transform:scale(1.0)}}</style>',
  340. );
  341. $(
  342. '<div class="spinner"><div class="double-bounce1"></div><div class="double-bounce2"></div></div>',
  343. ).insertBefore(commentBox);
  344. commentBox.hide();
  345. }
  346.  
  347. function reArrangeComments() {
  348. $('div.inner:has(a[href^="/t/"].page_normal)').remove();
  349. var commentBox = getCommentBox();
  350. $.each(comments, function(i, o) {
  351. moveComment(o, commentBox);
  352. });
  353. $('div[id^="r_"]>table>tbody>tr>td:first-child').attr('width', '20');
  354. $('td img.avatar').css('width', '');
  355. $('body').append(
  356. '<style>.cell{background-color: inherit;}.cell .cell{padding-bottom:0;border-bottom:none;min-width: 250px;padding-right:0;}div[id^="r_"] img.avatar{width:20px;height:20px;border-radius:50%;}div[id^="r_"]>div{margin-left: 5px;}</style>',
  357. );
  358. commentBox.show();
  359. //removeSpinner
  360. $('.spinner').remove();
  361. jumpToReply();
  362. }
  363.  
  364. function enableUploadImg() {
  365. $('div.cell:contains("添加一条新回复")').append(
  366. '<div class="fr"><a href="https://imgur.com/upload" target="_blank"> 上传图片</a> - </div>',
  367. );
  368. }