AtCoder Language Filter

提出言語のフィルタリングと並び替え

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==UserScript==
// @name         AtCoder Language Filter
// @namespace    https://github.com/morioprog
// @version      1.0.2
// @description  提出言語のフィルタリングと並び替え
// @author       morio_prog
// @match        *://atcoder.jp/contests/*/tasks/*
// @match        *://atcoder.jp/contests/*/submit*
// @match        *://atcoder.jp/contests/*/custom_test*
// @license      CC0
// @require      https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.12/js/select2.min.js
// ==/UserScript==

(function() {
    'use strict';

    const DEFAULT_LANGUAGE_KEY = 'defaultLang';
    const SAVED_LANGUAGE_KEY = 'userscript-languagefilter-savedlanguage';

    function moveElementToEndOfParent($ele) {
        const parent = $ele.parent();
        $ele.detach();
        parent.append($ele);
    }

    let defLang = localStorage.getItem(DEFAULT_LANGUAGE_KEY);
    if (defLang) defLang = defLang.replace(/[^0-9]/g, '');
    const $selectLanguage = $('#select-lang select');

    /* Build language-map */
    const optMap = new Map(); // {lang: [value, data-mime]}
    $selectLanguage.children('option').each(function(_, e) {
        const $opt = $(e);
        optMap.set(
            $opt.text(),
            [
                $opt.attr('value'),
                $opt.attr('data-mime'),
            ]
        );
        $(this).remove();
    });

    /* Add button */
    const buttonHtml = `<p><button type="button" class="btn btn-default btn-sm btn-auto-height" data-toggle="modal" data-target="#LangFilterModal">提出言語の選択</button></p>`;
    $('#main-container > div.row > div > form > div > div.editor-buttons').append(buttonHtml);

    /* Add modal */
    const modalHtml = `
<div class="modal fade" id="LangFilterModal" tabindex="-1" role="dialog" aria-labelledby="LangFilterModalLabel">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title" id="LangFilterModalLabel">提出言語の選択</h4>
            </div>
            <div class="modal-body" id="langfilter-modal">
                <select id="langfilter-select2" multiple="multiple"></select>
            </div>
            <div class="modal-footer">
                <button type="button" id="langfilter-selectall" class="btn btn-info pull-left">Select All</button>
                <button type="button" id="langfilter-clear" class="btn btn-danger pull-left">Clear</button>
                <span id="langfilter-savelabel" style="margin-right:20px;"></span>
                <button type="button" id="langfilter-save" class="btn btn-success">Save changes</button>
            </div>
        </div>
    </div>
</div>`;
    $('body').prepend(modalHtml);

    const $lfsel2 = $('select#langfilter-select2');
    optMap.forEach(function(val, key) {
        $lfsel2.append(
            $('<option>', {
                value: val[0],
                text: key,
            })
        );
    });

    /* Load saved languages */
    let savedLanguage = localStorage.getItem(SAVED_LANGUAGE_KEY);
    let selectedLanguage = [];
    if (savedLanguage) {
        try {
            savedLanguage = JSON.parse(savedLanguage);
        } catch (error) {
            savedLanguage = [];
            console.log(error);
        }
        $.each(savedLanguage, function(_, lang) {
            if (!optMap.has(lang)) return true;
            const val = optMap.get(lang)[0];
            const mim = optMap.get(lang)[1];
            $selectLanguage.append(
                $('<option>', {
                    'value':     val,
                    'data-mime': mim,
                    'text':      lang,
                    'selected':  val == defLang,
                })
            );
            selectedLanguage.push(lang);
            const $opt = $lfsel2.find(`option[value='${val}']`);
            $opt.prop('selected', true);
            moveElementToEndOfParent($opt);
        });
    }

    /* Apply Select2 */
    $lfsel2.select2({
        placeholder: 'Languages',
        theme: 'bootstrap',
    }).on('select2:select', function(evt) {
        const $opt = $(this).children(`option[value=${evt.params.data.id}]`);
        moveElementToEndOfParent($opt);
        $(this).trigger("change");
    });

    const $ul = $('#langfilter-modal > span > span.selection > span > ul.select2-selection__rendered');
    $ul.sortable({
        containment: 'parent',
        update: function() {
            $(this).children("li[title]").each(function(_, li) {
                const $opt = $lfsel2.children('option').filter(function() {
                    return $(this).html() == li.title;
                });
                moveElementToEndOfParent($opt);
            });
        }
    });

    $('#langfilter-clear').on('click', function() {
        $lfsel2.val(null).trigger('change');
    });

    $('#langfilter-selectall').on('click', function() {
        $lfsel2.children('option').prop('selected', true);
        $lfsel2.trigger("change");
    });

    $('#langfilter-save').on('click', function() {
        selectedLanguage = [];
        $ul.children('li.select2-selection__choice').each(function() {
            selectedLanguage.push($(this).text().slice(1));
        });
        if (selectedLanguage.length === 0) {
            $('#langfilter-savelabel').removeClass('text-success');
            $('#langfilter-savelabel').addClass('text-danger');
            $('#langfilter-savelabel').text('Please select at least 1 language!');
        } else {
            localStorage.setItem(SAVED_LANGUAGE_KEY, JSON.stringify(selectedLanguage));
            $('#langfilter-savelabel').removeClass('text-danger');
            $('#langfilter-savelabel').addClass('text-success');
            $('#langfilter-savelabel').text(`Saved (${(new Date()).toLocaleString()})`);
        }
    });

})();