vk.com mark as read

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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;
    }
})();