V2ex supper helper

Make v2ex easyer to use

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

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

})();