North-Plus Block

为北+提供关键词屏蔽功能

// ==UserScript==
// @name         North-Plus Block
// @namespace    https://github.com/sssssssl/NP-Scripts
// @version      0.2.1
// @description  为北+提供关键词屏蔽功能
// @author       sl
// @match        https://*.summer-plus.net/thread.php?fid-*
// @match        https://*.summer-plus.net/thread.php?fid-*
// @match        https://*.level-plus.net/thread.php?fid-*
// @match        https://*.white-plus.net/thread.php?fid-*
// @match        https://*.south-plus.net/thread.php?fid-*
// @match        https://*.imoutolove.me/thread.php?fid-*
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==

(function() {
    'use strict';


    const STORE_BLOCK_MODE_KEY = 'blockEnabled';
    const STORE_BLOCK_WORDS_KEY = 'blockWords';

    const ID_BTN_BLOCK_SWITCH = 'np-block-switch';
    const ID_BTN_SHOW_LIST = 'np-block-btn-show-list';
    const ID_NP_BLOCK_LIST_CONTAINER = 'np-block-list-container';
    const ID_NP_BLOCK_LIST = 'np-block-list';

    const ID_BTN_ADD_WORD = 'np-block-list-add-btn';
    const ID_INPUT_WORD = 'np-block-list-add-input';
    const CLS_BTN_DEL_WORD = 'btn-delete-block-word';

    const CLS_MODE_DISABLED = 'np-block-disabled';
    const CLS_MODE_ENABLED = 'np-block-enabled';
    const CLS_LIST_UNFOLD = 'np-list-unfold';
    const CLS_LIST_FOLD = 'np-list-fold';
    const CLS_LIST_ITEM = 'block-list-item';

    const CLS_FADEIN = 'np-fadein';
    const CLS_FADEOUT = 'np-fadeout';
    const TIME_FADE = 1;

    const CLS_THREAD = '.tr3.t_one';
    const CLS_THREAD_TITLE = 'h3 a';

    const NP_BLOCK_HTML = `
        <div id="np-block" class="${CLS_FADEIN} np-fixed-height">
            <div id="np-block-header">
                <span>屏蔽模式</span>
                <span id="${ID_BTN_BLOCK_SWITCH}" class="${CLS_MODE_DISABLED} np-clickable">关</span>
            </div>
            <div id="${ID_BTN_SHOW_LIST}" class="${CLS_MODE_DISABLED} np-clickable">屏蔽列表</div>
            <div id="${ID_NP_BLOCK_LIST_CONTAINER}" class="${CLS_LIST_FOLD}">
                <div id="${ID_NP_BLOCK_LIST}"> 
                </div>
                <div id="np-block-list-add" class="block-list-item">
                    <input id="np-block-list-add-input" class="np-fixed-height" type="text">
                    <span id="np-block-list-add-btn" class="np-clickable">加</span>
                </div>
            </div>
        </div>
    `;

    const NP_BLOCK_STYLE = `
        <style>
        #np-block {
            position: fixed;
            width: 140px;
            top: 10px;
            right: 10px;
            background-color: rgb(234, 237, 237);
        }

        .${CLS_FADEIN} {
            -ms-animation: fadein ${TIME_FADE}s;
            -webkit-animation: fadein ${TIME_FADE}s;
            animation fadein ${TIME_FADE}s;
        }

        @keyframes fadein {
            from { opacity: 0; }
            to   { opacity: 1; }
        }

        @-webkit-keyframes fadein {
            from { opacity: 0; }
            to   { opacity: 1; }
        }

        @-ms-keyframes fadein {
            from { opacity: 0; }
            to   { opacity: 1; }
        }

        .${CLS_FADEOUT} {
            -ms-animation: fadeout ${TIME_FADE}s;
            -webkit-animation: fadeout ${TIME_FADE}s;
            animation fadeout ${TIME_FADE}s;     
        }

        @keyframes fadeout {
            from { opacity: 1; }
            to   { opacity: 0; }
        }

        @-webkit-keyframes fadeout {
            from { opacity: 1; }
            to   { opacity: 0; }
        }

        @-ms-keyframes fadeout {
            from { opacity: 1; }
            to   { opacity: 0; }
        }

        #np-block > * {
            width: 90%;
            margin-left: auto;
            margin-right: auto;
            text-align: center;
            margin-top: 5px;
        }

        .np-fixed-height {
            line-height: 22px;
            vertical-align: middle;
        }
        
        #${ID_BTN_BLOCK_SWITCH} {
            padding-left: 10px;
            padding-right: 10px;
            float: right;
            transition: all 0.5s;
        }
        
        #${ID_BTN_SHOW_LIST} {
            transition: all 0.5s;
        }
        
        .${CLS_MODE_DISABLED} {
            background-color: black;
            color: white;
        }
        
        .${CLS_MODE_ENABLED} {
            background-color: white;
            color: black;
        }
        
        #${ID_NP_BLOCK_LIST_CONTAINER} {
            overflow: hidden;
            transition: all 0.5s;
        }
        
        .${CLS_LIST_FOLD} {
            max-height: 0px;
        }
        
        .${CLS_LIST_UNFOLD} {
            max-height: 250px;
            margin-bottom: 5px;
        }
        
        #${ID_NP_BLOCK_LIST} {
            max-height: 200px;
            overflow-y: scroll;
        }
        
        #${ID_NP_BLOCK_LIST}::-webkit-scrollbar {
            display: none;
        }
        
        .${CLS_LIST_ITEM} {
            text-align: center;
            background-color: rgb(33, 47, 61);
            color: white;
            width: 90%;
            margin-left: auto;
            margin-right: auto;
            margin-top: 5px;
        }
        
        .${CLS_BTN_DEL_WORD} {
            float: right;
            background-color: red;
            padding-right: 10px;
            padding-left: 10px;
            transition: all 0.5s;
        }
        
        .${CLS_BTN_DEL_WORD}:hover {
            background-color: rgb(192, 57, 43);
        }
        
        #np-block-list-add {
            background-color: inherit;
        }
        
        #np-block-list-add-input {
            appearance: none;
            font-size: 1em;
            width: 60%;
            border: none;
            margin: auto auto;
            box-sizing: border-box;
        }
        
        #np-block-list-add-input:focus {
            outline: 0;
        }
        
        #np-block-list-add-btn {
            float: right;
            background-color: green;
            padding-right: 10px;
            padding-left: 10px;
            transition: all 0.5s;
        }
        
        #np-block-list-add-btn:hover {
            background-color: rgb(28, 40, 51);
        }

        .np-clickable {
            cursor: default;
        }

        .np-clickable:hover {
            cursor: pointer;
        }
        </style>
    `;

    // 2. CODE ENTRYPOINT.

    let blockModeEnabled = GM_getValue(STORE_BLOCK_MODE_KEY, false);
    let blockWords = GM_getValue(STORE_BLOCK_WORDS_KEY);
    if(!blockWords) {
        blockWords = [];
    }
    let hiddenThreads = [];

    // add panel & register callbacks.
    document.head.insertAdjacentHTML('beforeend', NP_BLOCK_STYLE);
    document.body.insertAdjacentHTML('beforeend', NP_BLOCK_HTML);

    let btnBlockSwitch = document.getElementById(ID_BTN_BLOCK_SWITCH)
    let btnShowlist = document.getElementById(ID_BTN_SHOW_LIST);
    let btnAddWord = document.getElementById(ID_BTN_ADD_WORD);
    let inputWord = document.getElementById(ID_INPUT_WORD);
    let npBlockListContainer = document.getElementById(ID_NP_BLOCK_LIST_CONTAINER);
    let npBlockList = document.getElementById(ID_NP_BLOCK_LIST);

    btnShowlist.onclick = () => {
        btnShowlist.classList.toggle(CLS_MODE_DISABLED);
        btnShowlist.classList.toggle(CLS_MODE_ENABLED);
        npBlockListContainer.classList.toggle(CLS_LIST_FOLD);
        npBlockListContainer.classList.toggle(CLS_LIST_UNFOLD);
    };

    btnBlockSwitch.onclick = () => {
        btnBlockSwitch.classList.toggle(CLS_MODE_DISABLED);
        btnBlockSwitch.classList.toggle(CLS_MODE_ENABLED);
        blockModeEnabled = !blockModeEnabled;
        btnBlockSwitch.textContent = blockModeEnabled ? '开' : '关';
        GM_setValue(STORE_BLOCK_MODE_KEY, blockModeEnabled);
        if(blockModeEnabled) {
            parseThreadTitle(blockWords);
        }
        else {
            let i = hiddenThreads.length;
            while (i > 0) {
                let elem = hiddenThreads.pop();
                elem.style.display = 'table-row';
                elem.classList.remove(CLS_FADEOUT);
                elem.classList.add(CLS_FADEIN);
                i -= 1;
            }
        }
    };

    btnAddWord.onclick = () => {
        let word = inputWord.value;
        if(word.length == 0) {
            return;
        }
        inputWord.value = '';
        blockWords.push(word);
        GM_setValue(STORE_BLOCK_WORDS_KEY, blockWords);
        renderBlockWord(word);
        if(blockModeEnabled) {
            parseThreadTitle([word]);
        }
    }

    btnBlockSwitch.textContent = blockModeEnabled ? '开' : '关';
    if(blockModeEnabled && btnBlockSwitch.classList.contains(CLS_MODE_DISABLED)) {
        btnBlockSwitch.classList.toggle(CLS_MODE_DISABLED);
        btnBlockSwitch.classList.toggle(CLS_MODE_ENABLED);
    }

    blockWords.forEach(renderBlockWord);

    if(blockModeEnabled) {
        parseThreadTitle(blockWords);
    }

    function parseThreadTitle(wordList) {
        let titleList = [];
        let threads = document.querySelectorAll(CLS_THREAD);
        threads.forEach((elem) => {
            let title = elem.querySelector(CLS_THREAD_TITLE).textContent;
            wordList.forEach(word => {
                if(title.includes(word)) {
                    hiddenThreads.push(elem);
                    elem.classList.remove(CLS_FADEIN);
                    elem.classList.add(CLS_FADEOUT);
                    setTimeout(() => {
                        elem.style.display = 'none';
                    }, TIME_FADE*1000);
                }
            });
        });
    }

    function addDeleteWordCallback(btn) {
        btn.onclick = () => {
            let word = btn.previousElementSibling.textContent;
            let idx = blockWords.indexOf(word);
            if(idx >= 0) {
                blockWords.splice(idx, 1);
                GM_setValue(STORE_BLOCK_WORDS_KEY, blockWords);
                btn.parentElement.remove();
            }
            else {
                console.log(`${word}, ${idx}`);
            }
        };
    }

    function renderBlockWord(word) {
        let wordHTML = `
            <div class="${CLS_LIST_ITEM}">
                <span class="block-word">${word}</span>
                <span class="${CLS_BTN_DEL_WORD} np-clickable">删</span>
            </div>
        `;
        npBlockList.insertAdjacentHTML('afterbegin', wordHTML);
        let newDelBtn = npBlockList.querySelector(`.${CLS_BTN_DEL_WORD}`);
        addDeleteWordCallback(newDelBtn);
    }

})();