V2ex supper helper

Make v2ex easyer to use

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         V2ex supper helper
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Make v2ex easyer to use
// @author       You
// @match        https://v2ex.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=v2ex.com
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-start
// @license      MIT
// ==/UserScript==
class PageManager {
    constructor() {
        this.conditionalWorks = [];
        this.activeConditionWork = null;
        setInterval(() => {
            this.conditionalWorks.forEach(cw => {
                cw.conditionWork.report();
            });
        }, 1000);
    }

    stage(name) {
        var conditionalWork = new ConditionalWork(name);
        this.conditionalWorks.push({
            name: name,
            conditionWork: conditionalWork,
        });
        return conditionalWork;
    }

    off(name) {
        var check = this.conditionalWorks.find(cw => cw.name === name);
        if (check) {
            check.conditionWork.off();
        }
    }
}

class ConditionalWork {

    constructor(name) {
        this.__name = name;
        this.__conditionFn = null;
        this.__actionFn = null;
        this.__running = false;
        this.__timeDuration = 0;
        this.__intervalMethod = 'interval';
        this.__handlerId = null;
        this.__logLevel = 0;
        this.__log = console.log;
    }

    log = (message, level) => {
        if (isNaN(level)) level = 1;
        if (this.__logLevel > 0 && level >= this.__logLevel) {
            this.__log(message);
        }
    }

    __checkBeforeRun() {
        if (!this.__conditionFn) {
            throw new Error("Condition function must be defined");
        }
        if (!this.__timeDuration) {
            throw new Error(`${this.__intervalMethod} must be defined`);
        }
    }

    __complete() {
        this.__running = false;
        this.__handlerId = null;
    }

    logLevel(level) {
        this.__logLevel = level;
        return this;
    }

    asTimeout(timeout) {
        if (this.__intervalMethod === 'interval') {
            // initial set for timeout
            if (isNaN(timeout)) {
                throw new Error("Timeout must be a number");
            } else {
                if (timeout < 1) {
                    throw new Error("timeout must be greater than 0");
                } else {
                    timeout = Math.floor(timeout);
                }
            }
        } else {
            throw new Error("Interval already set");
        }
        this.__timeDuration = timeout;
        this.__intervalMethod = 'timeout';
        return this;
    }

    on(conditionFn) {
        if (typeof conditionFn !== "function") {
            throw new Error("Condition function must be a function");
        }
        if (typeof this.__conditionFn === "function") {
            throw new Error("Condition function already defined");
        }
        this.__conditionFn = conditionFn;
        return this;
    }

    act(actionFn) {
        // on must be called before act
        if (typeof this.__conditionFn !== "function") {
            throw new Error("Condition function must be defined first");
        }
        if (typeof actionFn !== "function") {
            throw new Error("Action function must be a function");
        }
        this.__actionFn = actionFn;
        return this;
    }

    every(interval) {
        if (!isNaN(this.__timeDuration) && this.__timeDuration !== 0) {
            throw new Error("Interval already set");
        }

        if (isNaN(interval)) {
            throw new Error("Interval must be a number");
        } else {
            if (interval < 1) {
                throw new Error("Interval must be greater than 0");
            } else {
                interval = Math.floor(interval);
            }
        }

        this.__timeDuration = interval;
        return this;
    }

    __hanlder = (cancelFn) => {
        let shouldContinue = false;
        if (this.__conditionFn()) {
            shouldContinue = this.__actionFn(this.log);
        } else {
            shouldContinue = true;
        }
        if (shouldContinue === false) {
            cancelFn(this.__handlerId);
            this.__complete();
        }
    }

    __timeoutHanlder = () => {
        this.__hanlder(clearTimeout);
        setTimeout(() => {
            this.__timeoutHanlder();
        }, this.__timeDuration);
    }

    __intervalHandler = () => {
        if (!this.__handlerId) {
            this.__handlerId = setInterval(this.__intervalHandler, this.__timeDuration);
        } else {
            this.__hanlder(clearInterval);
        }
    }

    run = () => {
        // check basic configurations: interval, conditionFn, isRunning
        this.__checkBeforeRun();
        if (this.__running) {
            this.log("Work is already running", 1);
            return;
        }
        switch (this.__intervalMethod) {
            case 'interval':
                this.__intervalHandler();
                this.__running = true;
                break;
            case 'timeout':
                this.__timeoutHanlder();
                this.__running = true;
            default:
                throw new Error("Interval method not defined");
        }
    }

    report() {
        if (this.__running) {
            this.log(`${this.__name} is running`, 1);
        } else {
            this.log(`${this.__name} is not running`, 2);
        }
    }
}

(function () {
    // 'use strict';
    GM_addStyle(`
        .cell.read {
            background-color: antiquewhite;
        }
    `);

    function getWebsite() {
        // Get website domain
        return window.location.hostname;
    }

    function getJsonDataObject() {
        return JSON.parse(GM_getValue(getWebsite(), '{}'));
    }

    function setUrlRead(url) {
        // Set url as read
        var data = getJsonDataObject();
        data[url] = true;
        GM_setValue(getWebsite(), JSON.stringify(data));
    }

    function isUrlRead(url) {
        // Check if url is read
        var data = getJsonDataObject();
        return data[url] === true;
    }

    function getMainPageCells() {
        // Get main page cells
        return document.querySelectorAll('#Main div.cell.item');
    }

    function getTopicsPageCells() {
        // Get topics page cells
        return document.querySelectorAll('#TopicsNode .cell');
    }

    function getCells() {
        const mainCells = getMainPageCells();
        const topicsCells = getTopicsPageCells();
        if (mainCells.length > 0) {
            return mainCells;
        }
        if (topicsCells.length > 0) {
            return topicsCells;
        }
        return [];
    }

    function findLinks() {
        return Array.from(getCells()).map((cell) => { return cell.querySelector('.item_title a').href; });
    }

    var pm = new PageManager();

    const condition = () => {
        return findLinks().length > 0;
    }

    // record current page as read
    setUrlRead(window.location.href);

    pm.stage('Add color to viewd topics')
        .on(condition)
        .logLevel(1)
        .act((logger) => {
            logger('Add color to viewd topics');
            // find all cells
            var cells = getCells();
            // add color to read topics
            Array.from(cells).forEach((cell) => {
                const link = cell.querySelector('.item_title a').href;
                logger(`${link} is ${isUrlRead(link) ? 'read' : 'unread'}`);
                if (isUrlRead(link)) {
                    cell.classList.add('read');
                }
            });
            logger('Done', 2);
            return false;
        })
        .every(1000)
        .run();

})();