Reddit visited link remover

hides links you already visited on reddit and offers keyboard navigatin

当前为 2019-07-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Reddit visited link remover
  3. // @version 0.9.5
  4. // @namespace sxxe@gmx.de
  5. // @description hides links you already visited on reddit and offers keyboard navigatin
  6. // @include *.reddit.com/*
  7. // @exclude *.reddit.com/r/*/comments/*
  8. // @exclude *.reddit.com/user/*
  9. // @exclude *.reddit.com/message/*
  10. // @exclude *.reddit.com/reddits/*
  11. // @exclude *.reddit.com/prefs/*
  12. // @require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
  13.  
  14. // ==/UserScript==
  15.  
  16. // Keyboard Shortcuts:
  17. //
  18. // - h: Open next unread post in new tab
  19. // - j: Jump to next unread post and mark it as read (+minimize)
  20. // - shift + j: Jump to next post (even minimized)
  21. // - k: Jump to previous unread post and mark it as read (+minimize)
  22. // - shift + k: Jump to previous post (even minimized)
  23. //
  24. // - l: Open comments of active post
  25. // - u: up vote active post
  26. // - m: down vote active post
  27. //
  28.  
  29. var cached_links_time = 60*60*24*2; // Two days - how long should visited links be saved
  30. var cached_links_count = 1000; // Only save this amount of links (due to performance reasons)
  31. var fade_time = 750; // time it takes until the visited link is completely invisible
  32. var show_footer = true; // show how many links are currently cached at the buttom of reddit.com
  33. var toggle_links_button = true; // show button to show / hide links (hide_links has to be true)
  34. var hide_links = false; // removes links completely (after the next page reload)
  35. var add_manual_hide_link = true; // adds a link to the frontpage to manually hide posts
  36. var activate_popup = false; // activate popup functionality
  37.  
  38. // ----------------------------------------------------------
  39.  
  40. // Add config style
  41. function addGlobalStyle(css) {
  42. var head, style;
  43. head = document.getElementsByTagName('head')[0];
  44. if (!head) { return; }
  45. style = document.createElement('style');
  46. style.type = 'text/css';
  47. style.innerHTML = css;
  48. head.appendChild(style);
  49. }
  50. addGlobalStyle('.rvlr_marker { color: #FFF !important; background-color: #FF4500; border-radius: 50%; text-align: center !important; padding: 2px; }');
  51.  
  52. if (toggle_links_button && hide_links) {
  53. $("body").append ('<center><div id="show_links" class="show_links"><button>Toggle links</button></div> <br></center>');
  54. }
  55.  
  56. if (add_manual_hide_link) {
  57. $('ul.flat-list.buttons').append('<li><a class="manHideLink" href="#">mark as read</a></li>');
  58. }
  59.  
  60. var stateMachine;
  61. window.addEventListener ("load", function () {
  62.  
  63. stateMachine = new GM_LinkTrack();
  64.  
  65. if (show_footer) {
  66. var numLinks = stateMachine.GetVisitedLinkCount();
  67. $("body").append ('<center><p>' + numLinks + ' links cached. (max '+ cached_links_count + ')</p><br></center>');
  68. }
  69.  
  70. },
  71. false
  72. );
  73.  
  74. var activeElement;
  75. document.addEventListener("keypress", function(e) {
  76.  
  77. if(!e) e=window.event;
  78.  
  79. var isShift = e.shiftKey;
  80.  
  81. var key = e.which;
  82.  
  83. //console.log('key: '+key+' isShift: '+isShift);
  84.  
  85. // http://www.w3schools.com/jsref/event_key_which.asp
  86.  
  87. if ((key == 106) || (key == 74)) { // j key
  88. // shrink next unshrinked link
  89. var element;
  90. if (isShift){
  91. element = getElementAfterActive();
  92. } else {
  93. element = getCorrectElement();
  94. }
  95. //console.log("element: " + element + "e0: " + element[0]);
  96. if ( typeof element === "undefined" ) {
  97. nextPage();
  98. } else if ( typeof element[0] === "undefined" ) {
  99. nextPage();
  100. }
  101.  
  102. stateMachine.LinkIsNewPub(element);
  103. setActiveElement(element);
  104.  
  105. } else if (key == 104) { // h key
  106. // pop disable, open link in new tab
  107. var element = getCorrectElement();
  108. stateMachine.LinkIsNewPub(element);
  109. setActiveElement(element);
  110. openLink(element);
  111.  
  112. } else if ((key == 107) || (key == 75)) { // k key
  113. // open previous link in popup
  114.  
  115. var element;
  116. if (isShift){
  117. element = getElementBeforeActive();
  118. } else {
  119. element = getPrevUnreadLink();
  120. }
  121.  
  122. if(element.length === 0) {
  123. return 0;
  124. }
  125.  
  126. stateMachine.LinkIsNewPub(element);
  127. setActiveElement(element);
  128.  
  129. } else if (key == 117) { // u key
  130. // up vote active post
  131. upVote(getActiveElement());
  132. } else if (key == 109) { // m key
  133. // down vote active post
  134. downVote(getActiveElement());
  135. } else if (key == 108) { // l key
  136. // open comments of active post
  137. openLinkComments(getActiveElement());
  138. }
  139.  
  140. }, true);
  141.  
  142. function setActiveElement(elm) {
  143. resetActiveElement();
  144. activeElement = elm;
  145. activeElement.addClass('rvlr_active');
  146. activeElement.closest('div.thing:visible:not(.promoted)').find(".rank").addClass('rvlr_marker');
  147. }
  148.  
  149. function resetActiveElement() {
  150. if (activeElement) {
  151. activeElement.removeClass('rvlr_active');
  152. activeElement.closest('div.thing:visible:not(.promoted)').find(".rank").removeClass('rvlr_marker');
  153. };
  154. }
  155.  
  156. function getActiveElement() {
  157. return activeElement;
  158. }
  159.  
  160. function isActiveElement(elm) {
  161. if (elm.hasClass('rvlr_active')) {
  162. return true;
  163. }
  164. return false;
  165. }
  166.  
  167. function getCorrectElement() {
  168. var element;
  169.  
  170. var aElement = getActiveElement();
  171. if (typeof aElement !== "undefined") {
  172. element = getNextUnreadLink();
  173. }
  174.  
  175. if (typeof element === "undefined") {
  176. element = getFirstUnreadLink();
  177. }
  178.  
  179. return element;
  180. }
  181.  
  182.  
  183. function nextPage() {
  184. $('a[rel*=next]')[0].click();
  185. }
  186.  
  187. function getNextUnreadLink() {
  188.  
  189. var element = getActiveElement();
  190. if (typeof element !== "undefined") {
  191. var elem = element.closest('div.thing:not(.promoted)').nextAll('div.thing:not(.shrinked):not(.promoted)').eq(0).find('a.title');
  192. return elem;
  193. }
  194. return 0;
  195. }
  196.  
  197. function getPrevUnreadLink() {
  198.  
  199. var element = getActiveElement();
  200. if (typeof element !== "undefined") {
  201. var elem = element.closest('div.thing:not(.promoted)').prevAll('div.thing:not(.shrinked):not(.promoted)').eq(0).find('a.title');
  202. console.log(elem);
  203. return elem;
  204. }
  205. return 0;
  206. }
  207.  
  208. function getFirstUnreadLink() {
  209. var currentElement;
  210. $('#siteTable a.title').each(function() {
  211. if ( ($(this).closest('div.thing').hasClass('promoted') === false) && ($(this).closest('div.thing').hasClass('shrinked') === false) && ($(this).closest('div.thing').is(":hidden") === false) ) {
  212. currentElement = $(this);
  213. return false;
  214. }
  215. });
  216. //console.log(currentElement);
  217. return currentElement;
  218. }
  219.  
  220. function getLastShrinkedLink() {
  221. var element;
  222. $('#siteTable a.title').each(function() {
  223. if ( ($(this).closest('div.thing').hasClass('promoted') === false) && ($(this).closest('div.thing').hasClass('shrinked') === true) && ($(this).closest('div.thing').is(":hidden") === false) ) {
  224. element = $(this);
  225. }
  226. });
  227. return element;
  228. }
  229.  
  230. function getElementBeforeActive() {
  231. var element;
  232.  
  233. element = $('.rvlr_active').closest('div.thing:not(.promoted)').prevAll('div.thing:visible:not(.promoted)').eq(0).find('a.title');
  234.  
  235. return element;
  236. }
  237.  
  238. function getElementAfterActive() {
  239. var element;
  240.  
  241. element = $('.rvlr_active').closest('div.thing:not(.promoted)').nextAll('div.thing:visible:not(.promoted)').eq(0).find('a.title');
  242.  
  243. return element;
  244. }
  245.  
  246. function openLink(elm) {
  247. var href = elm.attr('href');
  248. window.open(href, '_blank');
  249. }
  250.  
  251. function getYoutubeId(url) {
  252. var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
  253. var match = url.match(regExp);
  254.  
  255. if (match && match[2].length == 11) {
  256. return match[2];
  257. } else {
  258. return 0;
  259. }
  260. }
  261.  
  262. function openLinkComments(elm) {
  263. var href = elm.closest('div.thing:not(.promoted)').find("a.comments").attr('href');
  264. //window.location = href;
  265. window.open(href, '_blank');
  266. }
  267.  
  268. function upVote(elm) {
  269. elm.closest('div.thing:not(.promoted)').find("div.up, div.upmod").click();
  270. }
  271.  
  272. function downVote(elm) {
  273. elm.closest('div.thing:not(.promoted)').find("div.down, div.downmod").click();
  274. }
  275.  
  276. function GM_LinkTrack () {
  277. var visitedLinkArry = [];
  278. var numVisitedLinks = 0;
  279. var link_count = 0;
  280. var current_timestamp = new Date().getTime();
  281.  
  282. var sortedLocalStorage = SortLocalStorage();
  283.  
  284. // Get visited link-list from storage.
  285. for (var J = sortedLocalStorage.length - 1; J >= 0; --J) {
  286.  
  287. var item = sortedLocalStorage[J];
  288.  
  289. var four_weeks = cached_links_time*1000;
  290.  
  291. // Get saved links
  292. if (/^Visited_\d+.*/i.test (item) ) {
  293.  
  294. var regex = /^Visited_(\d+).*/;
  295. var old_timestamp = regex.exec(item)[1];
  296.  
  297. var regex2 = /^Visited_\d+_(.*)/;
  298. var value = regex2.exec(item)[1];
  299.  
  300. var regex3 = /^(Visited_\d+)_.*/;
  301. var itemName = regex3.exec(item)[1];
  302.  
  303. //console.log(numVisitedLinks + " " + item+ " t: " + old_timestamp + " v: " + value + " n: " + itemName);
  304.  
  305. if (value == '#') {
  306. localStorage.removeItem (itemName);
  307. break;
  308. }
  309.  
  310. if (link_count >= cached_links_count) {
  311. localStorage.removeItem (itemName);
  312. //console.log(numVisitedLinks + " " + value + "t: " + timeConverter(old_timestamp));
  313. link_count--;
  314. }
  315. link_count++;
  316.  
  317. // check link age
  318. if ( (current_timestamp - old_timestamp) < four_weeks ) {
  319. visitedLinkArry.push (value);
  320. numVisitedLinks++;
  321.  
  322. //console.log(numVisitedLinks + " " + localStorage[itemName] + " t: " +old_timestamp[1]);
  323.  
  324. if (hide_links) {
  325. $('a[href*="' + value + '"]').closest('div.thing:not(.promoted)').fadeOut(fade_time);
  326. } else {
  327. //$('a[href="' + localStorage[itemName] + '"]').closest('div').fadeOut(fade_time);
  328. shrinkLinks($('a[href*="' + value + '"]'));
  329. }
  330.  
  331. } else {
  332. // too old, remove from storage
  333. localStorage.removeItem (itemName);
  334. }
  335. }
  336. }
  337.  
  338. function SortLocalStorage() {
  339.  
  340. var localStorageArray = [];
  341.  
  342. if(localStorage.length > 0) {
  343. for (var i=0; i<localStorage.length; i++){
  344. localStorageArray[i] = localStorage.key(i)+ "_" +localStorage.getItem(localStorage.key(i));
  345. }
  346. }
  347.  
  348. return localStorageArray.sort();
  349. }
  350.  
  351. function timeConverter(UNIX_timestamp) {
  352. var a = new Date(UNIX_timestamp*1000);
  353. var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  354. var year = a.getFullYear();
  355. var month = months[a.getMonth()];
  356. var date = a.getDate();
  357. var hour = a.getHours();
  358. var min = a.getMinutes();
  359. var sec = a.getSeconds();
  360. var time = date+','+month+' '+year+' '+hour+':'+min+':'+sec ;
  361. return time;
  362. }
  363.  
  364. function ShowLinks () {
  365. for (var J = localStorage.length - 1; J >= 0; --J) {
  366. var itemName = localStorage.key (J);
  367.  
  368. // Get saved links
  369. if (/^Visited_\d+$/i.test (itemName) ) {
  370. $('a[href="' + localStorage[itemName] + '"]').closest('div.thing:not(.promoted)').fadeToggle(fade_time);
  371. }
  372. }
  373. }
  374.  
  375. this.LinkIsNewPub = function (linkObj) {
  376. LinkIsNew(linkObj);
  377. };
  378.  
  379. function LinkIsNew (linkObj) {
  380. var href = linkObj.attr('href');
  381.  
  382. href = href.replace(/^(http|https)\:\/\//g, '');
  383.  
  384. if (visitedLinkArry.indexOf (href) == -1) {
  385. visitedLinkArry.push (href);
  386. var timestamp = new Date().getTime();
  387.  
  388. var itemName = 'Visited_' + timestamp;
  389. localStorage.setItem (itemName, href);
  390. numVisitedLinks++;
  391. // Hide links imideately after klicked. Makes it impossible to see commenst afterward.
  392. //$('a[href="' + href + '"]').closest('div').fadeOut(fade_time);
  393. }
  394. centerView(linkObj);
  395. shrinkLinks(linkObj);
  396. setActiveElement(linkObj);
  397.  
  398. return true;
  399. }
  400.  
  401. function shrinkLinks (linkObj) {
  402.  
  403. // Alter the look of clicked links imideately
  404. var mainLinkElement = linkObj.closest('div.thing:not(.promoted)');
  405.  
  406. // Remove elements that are nor needed in the smll view
  407. mainLinkElement.find('p.tagline').remove();
  408. mainLinkElement.find('a.thumbnail').css({'opacity': '0', 'width': '0px'});
  409. mainLinkElement.find('a.thumbnail').hide(fade_time, function () {
  410. $(this).remove();
  411. });
  412. mainLinkElement.find('span.domain').remove();
  413. mainLinkElement.find('div.expando-button').remove();
  414. mainLinkElement.find('li a.manHideLink').remove();
  415. mainLinkElement.find('span.linkflairlabel').remove();
  416.  
  417. // Realign elements for the small view
  418. var animObj = {"queue": false, "duration": fade_time};
  419.  
  420. mainLinkElement.find('a.title').animate({'font-size': '9px', 'margin': '0px'}, animObj);
  421. mainLinkElement.find('p.title').css({'float': 'left', 'margin-left': '10px', 'font-size': '9px', 'font-weight': 'normal', 'margin-top': '4px', 'text-overflow': 'ellipsis', 'max-width': '300px', 'white-space': 'nowrap', 'overflow': 'hidden'});
  422.  
  423. mainLinkElement.find('ul.flat-list.buttons').animate({'margin-left': '10px', 'font-size': '9px', 'margin-top': '0px'}, animObj);
  424. mainLinkElement.find('span.rank').animate({'margin-top': '1px', 'font-size': '10px'}, animObj);
  425.  
  426. mainLinkElement.find('div.midcol').css("cssText", "width: 110px !important;"); //jquery hack to apply important
  427. mainLinkElement.find('div.midcol').css({'margin-top': '0px', 'margin-bottom': '0px', 'height': 'unset'});
  428. mainLinkElement.find('div.arrow').css({'float': 'left'});
  429.  
  430. mainLinkElement.find('div.score').css({'float': 'left'});
  431. mainLinkElement.find('div.score').animate({'font-size': '10px', 'padding-left': '10px', 'padding-right': '10px', 'margin-top': '3px', 'width': '30px'}, animObj);
  432. mainLinkElement.find('div.entry').css({'padding': '0px'});
  433.  
  434. //main div
  435. mainLinkElement.css({'padding': '0px'});
  436. mainLinkElement.css({'padding-left': '10px', 'opacity': '0.4', 'margin': '4px'});
  437. //mainLinkElement.find('p.tagline').css({'float': 'right'});
  438. mainLinkElement.addClass('shrinked');
  439.  
  440. return true;
  441. }
  442.  
  443. this.GetVisitedLinkCount = function () {
  444. return numVisitedLinks;
  445. };
  446.  
  447. function centerView(elm) {
  448.  
  449. var offsetTop = $(elm).offset().top;
  450. var newTopPos = offsetTop - $(window).scrollTop();
  451. var elemLimitPos = $(window).height() / 2;
  452.  
  453. //console.log("offsetTop: " + offsetTop);
  454. //console.log("newTopPos: " + newTopPos);
  455. //console.log("elemLimitPos: " + elemLimitPos);
  456.  
  457. if ( newTopPos > elemLimitPos ) {
  458. $('html,body').animate({
  459. scrollTop: offsetTop - 50
  460. }, 800);
  461. }
  462. }
  463.  
  464. $('a.title').click(function() {
  465. LinkIsNew($(this));
  466. });
  467. $('#hidesidebar').click(function(event) {
  468. event.preventDefault();
  469.  
  470. $(".side").toggle();
  471. });
  472.  
  473. if (toggle_links_button && hide_links) {
  474. $('#show_links button').click (function() {
  475. ShowLinks();
  476. });
  477. }
  478.  
  479. if (add_manual_hide_link) {
  480. $('a.manHideLink').click (function(event) {
  481. event.preventDefault();
  482. var $titleLink = $(this).closest('div.thing:not(.promoted)').find('a.title');
  483.  
  484. LinkIsNew($titleLink);
  485. });
  486. }
  487. }