您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
mark posts as read (помечает посты как прочитанные)
// ==UserScript== // @name vk.com mark as read // @namespace limizin.userscripts // @description mark posts as read (помечает посты как прочитанные) // @include https://vk.com* // @version 2.4 // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { var postwall = document.getElementById('page_wall_posts'); if (!postwall) return; globals = {}; globals.firefox = navigator.userAgent.toLowerCase().indexOf('firefox') != -1; globals.postwall = postwall; globals.storageKey = 'usernameReadPost/' + hashCode(location.pathname) + hashCode(reverse(location.pathname)); globals.top_shadow_post_id = null; globals.shadowMark = false; globals.maxAutoscrollPosts = 150; globals.autoscrolling = false; globals.scrollOnLoad = false; globals.unreadCount = 0; globals.readPostNumId = 0; //second num after _ in id //add styles if (!document.querySelector('style#usernameReadPost')) { var head = document.querySelector('head'); stl = head.appendChild(document.createElement('style')); stl.id = 'usernameReadPost'; 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;} @keyframes blink { 0% { color: white; } 100% { color: #507299; } } @-webkit-keyframes blink { 0% { color: white; } 100% { color: #507299; } } .blink { -webkit-animation: blink 500ms linear infinite; -moz-animation: blink 500ms linear infinite; animation: blink 500ms linear infinite; }`; } //scroll section var buttonBlock = document.createElement('div'); buttonBlock.style.display = 'inline'; buttonBlock.style.marginLeft = '-58px'; buttonBlock.style.marginRight = '7px'; buttonBlock.style.marginTop = '10px'; buttonBlock.style.float = 'left'; var scrollOnLoadChBox = buttonBlock.appendChild(document.createElement('input')); scrollOnLoadChBox.type = 'checkbox'; scrollOnLoadChBox.style.verticalAlign = 'middle'; scrollOnLoadChBox.title = 'прокрутить до прочитанного при загрузке странице'; globals.scrollOnLoadChBox = scrollOnLoadChBox; scrollOnLoadChBox.click = addEventListener('change', function(e) { globals.scrollOnLoad = globals.scrollOnLoadChBox.checked; saveSettings(); }); var scrollToReadBtn = createButton('', scrollToRead, 'прокрутить до прочитанного'); scrollToReadBtn.style.width = '30px'; var scrollButtonText = scrollToReadBtn.appendChild(document.createElement('span')); scrollButtonText.textContent = '>'; globals.scrollButtonText = scrollButtonText; globals.scrollButton = scrollToReadBtn; buttonBlock.appendChild(scrollToReadBtn); document.querySelector('div.head_nav_item').appendChild(buttonBlock); //add buttons ol load _addButtons(document.querySelectorAll('#page_wall_posts > div.post')); loadSettings(); //shadow posts on load if (globals.top_shadow_post_id) { var post = document.getElementById(globals.top_shadow_post_id); if (post) { _markReadPost(post); globals.shadowMark = true; } } //wall observer observer = new MutationObserver( function(mutations) { var newPosts = new Array(); mutations.forEach(function(mutation) { if (mutation.type != 'childList') return; if (mutation.addedNodes) { for (i = 0; i < mutation.addedNodes.length; i++) { var post = mutation.addedNodes[i]; if (post.classList.contains('no_posts')) continue; newPosts.push(post); } } }); if (newPosts) { _addButtons(newPosts); if (globals.readPostNumId != 0) { var unreadCount = globals.unreadCount; //console.log(unreadCount); for (var i = 0; i < newPosts.length; ++i) { var newPost = newPosts[i]; var newPostNumId = _extractPostNumId(newPost); if (newPostNumId > globals.readPostNumId) { unreadCount += 1; } } if (unreadCount > globals.unreadCount) { globals.unreadCount = unreadCount; globals.scrollButtonText.textContent = globals.unreadCount; } } _shadowSubloadedPosts(newPosts); if (globals.autoscrolling) { scrollToRead(null); } } } ); observer.observe(globals.postwall, { attributes: false, childList: true, characterData: false }); //scroll on load if (globals.scrollOnLoad) { scrollToRead(null); } function loadSettings() { var value = GM_getValue(globals.storageKey); if (value) { var chunks = value.split('|'); globals.top_shadow_post_id = chunks[0]; globals.scrollOnLoad = (chunks[1] == '1'); globals.scrollOnLoadChBox.checked = globals.scrollOnLoad; } } function saveSettings() { var readPostId = globals.top_shadow_post_id; var checked = (globals.scrollOnLoad) ? '1' : '0'; var value = readPostId + '|' + checked; GM_setValue(globals.storageKey, value); } function scrollToRead(event) { var post = document.querySelector('div.usernameReadPost'); if (post) { var postYOffset = post.offsetTop; var median = window.innerHeight / 2; var scrollTo = postYOffset - median; if (scrollTo < 0) { scrollTo = 0; } window.scrollTo(0, scrollTo); changeSrollState(false); } else { if (globals.maxAutoscrollPosts >= globals.postwall.childElementCount) { changeSrollState(true); var prevPostsBtn = document.getElementById('wall_more_link'); if (prevPostsBtn) { prevPostsBtn.click(); } else { changeSrollState(false); } } else { alert(globals.maxAutoscrollPosts + ' постов было загружено. Последний прочитанный пост среди них не найден'); changeSrollState(false); } } } function changeSrollState(start) { if (start) { globals.autoscrolling = true; globals.scrollButtonText.classList.add('blink'); globals.scrollButton.click = null; } else { globals.autoscrolling = false; globals.scrollButtonText.classList.remove('blink'); globals.scrollButton.click = scrollToRead; } } function _shadowSubloadedPosts(posts) { if (globals.shadowMark || !globals.top_shadow_post_id) { return; } for (var i = 0; i < posts.length; ++i) { var post = posts[i]; var postId = post.getAttribute('id'); if (postId == globals.top_shadow_post_id) { _markReadPost(post); break; } } } function markSelectedAsRead(event) { var xpath = './ancestor::div[contains(@class, "post") and not(@id="page_wall_posts")]'; var post = document.evaluate(xpath, event.currentTarget, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (post) { globals.top_shadow_post_id = post.getAttribute('id'); saveSettings(); var prevPostsXpath = './preceding-sibling::div[contains(@class, "usernameReadPost")]'; var prevPosts = document.evaluate(prevPostsXpath, post, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0; i < prevPosts.snapshotLength; i++) { var prevPost = prevPosts.snapshotItem(i); prevPost.classList.remove('usernameReadPost'); } _markReadPost(post); globals.shadowMark = true; } } function _markReadPost(post) { post.classList.add('usernameReadPost'); globals.readPostNumId = _extractPostNumId(post); var prevPostsXpath = 'count(./preceding-sibling::div[contains(@class, "post") and not(@id="page_wall_posts")])'; var prevPosts = document.evaluate(prevPostsXpath, post, null, XPathResult.NUMBER_TYPE, null); globals.unreadCount = prevPosts.numberValue; globals.scrollButtonText.textContent = globals.unreadCount; } function _addButtons(posts) { for (var i = 0; i < posts.length; ++i) { var post = posts[i]; var btn = createButton('+', markSelectedAsRead, 'mark as read'); btn.classList.add('usernameReadPostBtn'); post.appendChild(btn); } } /////////// utils /////////// function _extractPostNumId(post) { var num = post.getAttribute('data-post-id').split('_')[1]; return parseInt(num); } function hashCode(value) { var hash = 0; if (value.length == 0) return hash; for (i = 0; i < value.length; i++) { char = value.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return hash; } function reverse(value) { return value.split('').reverse().join(''); } function xpathResultToArray(xpathResult) { var nodes = new Array(); var nextNode = xpathResult.iterateNext(); while (nextNode) { nodes.push(nextNode); nextNode = xpathResult.iterateNext(); } return nodes; } function createButton(content, handler, title) { var btn = document.createElement('button'); btn.textContent = content; btn.onclick = handler; btn.title = title; btn.className = 'usernameReadBtn'; if (globals.firefox) { btn.style.paddingBottom = '2px'; } return btn; } })();