V2EXcellent.js

A Better V2EX

当前为 2017-11-07 提交的版本,查看 最新版本

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