vk.com mark as read

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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;
    }
})();