V2EXcellent.js

A Better V2EX

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