vk.com mark as read

mark posts as read (помечает посты как прочитанные)

当前为 2017-03-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name vk.com mark as read
  3. // @namespace limizin.userscripts
  4. // @description mark posts as read (помечает посты как прочитанные)
  5. // @include https://vk.com*
  6. // @version 2.1
  7. // @grant GM_setValue
  8. // @grant GM_getValue
  9. // ==/UserScript==
  10. (function() {
  11. var postwall = document.getElementById('page_wall_posts');
  12. if (!postwall)
  13. return;
  14.  
  15. globals = {};
  16. globals.firefox = navigator.userAgent.toLowerCase().indexOf('firefox');
  17. globals.postwall = postwall;
  18. globals.storageKey = 'usernameReadPost/' + hashCode(location.pathname) + hashCode(reverse(location.pathname));
  19. globals.top_shadow_post_id = GM_getValue(globals.storageKey);
  20. globals.shadowMark = false;
  21. globals.maxAutoscrollPosts = 100;
  22. globals.autoscrolling = false;
  23.  
  24. if (!document.querySelector('style#usernameReadPost')) {
  25. var head = document.querySelector('head');
  26. stl = head.appendChild(document.createElement('style'));
  27. stl.id = 'usernameReadPost';
  28. stl.innerHTML = '.usernameReadPost, .usernameReadPost ~ * {background-color: silver !important;} .usernameReadBtn {background-color: #507299; color: #ffffff; border: thin solid #C4C4C4; cursor: pointer;} .usernameReadPostBtn {top:0; right:0; position: absolute;}';
  29. }
  30.  
  31. var buttonBlock = document.createElement('div');
  32. buttonBlock.style.display = 'inline';
  33. buttonBlock.style.marginLeft = '-38px';
  34. buttonBlock.style.marginRight = '7px';
  35. buttonBlock.style.marginTop = '10px';
  36. buttonBlock.style.float = 'left';
  37. var scrollToReadBtn = createButton('>', scrollToRead, 'scroll to read');
  38. scrollToReadBtn.style.width = '30px';
  39. globals.scrollButton = scrollToReadBtn;
  40.  
  41. buttonBlock.appendChild(scrollToReadBtn);
  42. document.querySelector('div.head_nav_item').appendChild(buttonBlock);
  43.  
  44. //add buttons ol load
  45. _addButtons(document.querySelectorAll('div.wall_posts > div.post'));
  46.  
  47. //shadow posts on load
  48. if (globals.top_shadow_post_id) {
  49. var post = document.getElementById(globals.top_shadow_post_id);
  50. if (post) {
  51. post.classList.add('usernameReadPost');
  52. globals.shadowMark = true;
  53. }
  54. }
  55.  
  56. //wall observer
  57. observer = new MutationObserver(onWallChange);
  58. var config = {
  59. attributes: false,
  60. childList: true,
  61. characterData: false
  62. };
  63. observer.observe(globals.postwall, config);
  64.  
  65. function onWallChange(mutations) {
  66. var newPosts = new Array();
  67. mutations.forEach(function(mutation) {
  68. if (mutation.type != 'childList')
  69. return;
  70. if (mutation.addedNodes) {
  71. for (i = 0; i < mutation.addedNodes.length; i++) {
  72. var post = mutation.addedNodes[i];
  73. if (post.classList.contains('no_posts'))
  74. continue;
  75. newPosts.push(post);
  76. }
  77. }
  78. });
  79.  
  80. if (newPosts) {
  81. _addButtons(newPosts);
  82. _shadowSubloadedPosts(newPosts);
  83. if (globals.autoscrolling) {
  84. scrollToRead(null);
  85. }
  86. }
  87. }
  88.  
  89. function scrollToRead(event) {
  90. var post = document.querySelector('div.usernameReadPost');
  91. if (post) {
  92. var postYOffset = post.offsetTop;
  93. var median = window.innerHeight / 2;
  94. var scrollTo = postYOffset - median;
  95. if (scrollTo < 0) {
  96. scrollTo = 0;
  97. }
  98. window.scrollTo(0, scrollTo);
  99. changeSrollState(false);
  100. } else {
  101. if (globals.maxAutoscrollPosts >= globals.postwall.childElementCount) {
  102. changeSrollState(true);
  103. var prevPostsBtn = document.getElementById('wall_more_link');
  104. if (prevPostsBtn) {
  105. prevPostsBtn.click();
  106. }
  107. } else {
  108. alert(globals.maxAutoscrollPosts + ' posts was scrolled. Read post not found.');
  109. changeSrollState(false);
  110. }
  111. }
  112. }
  113.  
  114. function changeSrollState(start) {
  115. if (start) {
  116. globals.autoscrolling = true;
  117. globals.scrollButton.textContent = '!';
  118. globals.scrollButton.click = null;
  119. } else {
  120. globals.autoscrolling = false;
  121. globals.scrollButton.textContent = '>';
  122. globals.scrollButton.click = scrollToRead;
  123. }
  124. }
  125.  
  126. function _shadowSubloadedPosts(posts) {
  127. if (globals.shadowMark || !globals.top_shadow_post_id) {
  128. return;
  129. }
  130. for (var i = 0; i < posts.length; ++i) {
  131. var post = posts[i];
  132. var postId = post.getAttribute('id');
  133. if (postId == globals.top_shadow_post_id) {
  134. post.classList.add('usernameReadPost');
  135. globals.shadowMark = true;
  136. break;
  137. }
  138. }
  139. }
  140.  
  141. function markSelectedAsRead(event) {
  142. var xpath = './ancestor::div[contains(@class, "post") and not(@id="page_wall_posts")]';
  143. var post = document.evaluate(xpath, event.currentTarget, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  144. if (post) {
  145. GM_setValue(globals.storageKey, post.getAttribute('id'));
  146. var prevPostsXpath = './preceding-sibling::div[contains(@class, "usernameReadPost")]';
  147. var prevPosts = document.evaluate(prevPostsXpath, post, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  148. for (var i = 0; i < prevPosts.snapshotLength; i++) {
  149. var prevPost = prevPosts.snapshotItem(i);
  150. prevPost.classList.remove('usernameReadPost');
  151. }
  152. post.classList.add('usernameReadPost');
  153. }
  154. }
  155.  
  156. function _addButtons(posts) {
  157. for (var i = 0; i < posts.length; ++i) {
  158. var post = posts[i];
  159. var btn = createButton('+', markSelectedAsRead, 'mark as read');
  160. btn.classList.add('usernameReadPostBtn');
  161. post.appendChild(btn);
  162. }
  163. } /////////// utils ///////////
  164.  
  165. function hashCode(value) {
  166. var hash = 0;
  167. if (value.length == 0) return hash;
  168. for (i = 0; i < value.length; i++) {
  169. char = value.charCodeAt(i);
  170. hash = ((hash << 5) - hash) + char;
  171. hash = hash & hash; // Convert to 32bit integer
  172. }
  173. return hash;
  174. }
  175.  
  176. function reverse(value) {
  177. return value.split('').reverse().join('');
  178. }
  179.  
  180. function xpathResultToArray(xpathResult) {
  181. var nodes = new Array();
  182. var nextNode = xpathResult.iterateNext();
  183. while (nextNode) {
  184. nodes.push(nextNode);
  185. nextNode = xpathResult.iterateNext();
  186. }
  187. return nodes;
  188. }
  189.  
  190. function createButton(content, handler, title) {
  191. var btn = document.createElement('button');
  192. btn.textContent = content;
  193. btn.onclick = handler;
  194. btn.title = title;
  195. btn.className = 'usernameReadBtn';
  196. if (globals.firefox) {
  197. btn.style.paddingBottom = '2px';
  198. }
  199. return btn;
  200. }
  201. })();