CS UBB Timetable Fixer

A userscript that enhances the tabelar timetables for CS UBB students.

当前为 2017-10-16 提交的版本,查看 最新版本

// ==UserScript==
// @name         CS UBB Timetable Fixer
// @namespace    http://tampermonkey.net/
// @version      2.1.3
// @description  A userscript that enhances the tabelar timetables for CS UBB students.
// @author       myklosbotond
// @match        http://www.cs.ubbcluj.ro/files/orar/*/tabelar/*.html
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// @require      https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
// @resource     jqUI_CSS https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getResourceText
// ==/UserScript==

/*
 * jshint esversion: 6
 * jshint esnext: true
*/

function getDefaultOptions() {
    'use strict';

    return {
        weekOffset: 1,
        group: 0,
        halfGroup: 0,
        disciplinesToReplace: {},
        disciplinesToHide: []
    };
}

var defaultHTML;
var options;
var disciplines = [];

const disciplineColumn = "disciplina";

(function() {
    'use strict';

    loadOptions();
    setupCss();
    setupFunctions();

    structureHTML();
    injectSettingHTML();

    getDisciplinesList();

    console.log(disciplines);

    setEventHandlers();
    updateSettingsUi();

    runFixer();
})();

function loadOptions() {
    'use strict';

    let defOptions = getDefaultOptions();
    let savedOptions = GM_getValue("options", "{}");
    try{
        savedOptions = JSON.parse(savedOptions);
    }
    catch(err){
        savedOptions = {};
    }

    options = $.extend(defOptions, savedOptions);
}

function setupCss() {
    'use strict';

    GM_addStyle(".curs-tr {background: #fffedd;}");
    GM_addStyle(".curs-tr a{color: #c16b00;}");
    GM_addStyle(".seminar-tr {background: #ddf2ff;}");
    GM_addStyle(".seminar-tr a{color: #0e62a5;}");
    GM_addStyle(".labor-tr {background: #e4ffdd;}");
    GM_addStyle(".labor-tr a{color: #009688;}");

    GM_addStyle(".last-of-day-tr{border-bottom: 2px solid}");

    GM_addStyle("a:hover, a:hover font {background-color: transparent; text-decoration: underline}");

    GM_addStyle(".other-week-tr {filter: grayscale(100%);}");
    GM_addStyle(".other-week-tr td {background:transparent}");
    GM_addStyle(".other-week-tr:hover {filter: grayscale(0%);}");

    GM_addStyle(".meh-tr {opacity: 0.3; filter: grayscale(100%);}");
    GM_addStyle(".meh-tr td {background:transparent}");
    GM_addStyle(".meh-tr:hover {opacity: 0.7}");
    GM_addStyle(".nope-tr {background: transparent}");
    GM_addStyle(".nope-tr td {background: transparent}");
    GM_addStyle(".nope-tr a {color: #003399}");

    let jqUI_CssSrc = GM_getResourceText ("jqUI_CSS");
    jqUI_CssSrc     = jqUI_CssSrc.replace (/url\(['"]images\/ui\-bg_.*00\.png['"]\)/g, "");
    jqUI_CssSrc     = jqUI_CssSrc.replace (/url\(['"]images\/ui\-icons_.*\.png['"]\)/g, "");

    GM_addStyle (jqUI_CssSrc);

    GM_addStyle(".ui-selectmenu-button.ui-button {width: auto}");
    GM_addStyle(".ui-selectmenu-icon.ui-icon {float: right; margin-top: 8px; border-style: solid; border-width: 3px 3px 0 3px; border-color: #0f0f0d transparent transparent transparent; width: 0; height: 0;}");

    GM_addStyle('span.ui-button-icon.ui-icon.ui-icon-closethick {background-image: url(\'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg\" width="24" height="24" viewBox="0 0 24 24"><path d="M24 20.188l-8.315-8.209 8.2-8.282-3.697-3.697-8.212 8.318-8.31-8.203-3.666 3.666 8.321 8.24-8.206 8.313 3.666 3.666 8.237-8.318 8.285 8.203z"/></svg>\'); background-position: center; background-size: 50%;}');

    GM_addStyle('#hider-list-wrapper {max-height: 300px; overflow: auto; margin-bottom: 9px;}');
    GM_addStyle('#renamer-list-wrapper {max-height: 330px; overflow: auto; margin-bottom: 9px;}');

    GM_addStyle('#hider-dialog table{ width: 100%; }');
    GM_addStyle('#hider-dialog .check-column{ width: 1px; }');

    GM_addStyle('.option-wrapper{ display: inline-block; }');
    
    /* =========== Printer styles =========== */
    
    GM_addStyle('@media print{ #settings-wrapper {display:none;} #tables-wrapper table{page-break-after: always;}}');

}

function setupFunctions() {
    'use strict';

    Date.prototype.getWeek = function() {
        var onejan = new Date(this.getFullYear(), 0, 1);
        return Math.ceil((((this - onejan) / 86400000) + onejan.getDay() + 1) / 7);
    };

    setDialogsPositionFixed();
}

function setDialogsPositionFixed() {
    $.ui.dialog.prototype._oldinit = $.ui.dialog.prototype._init;
    $.ui.dialog.prototype._init = function() {

        $(this.element).parent().css("position", "fixed");
        $(this.element).parent().addClass("no-select");
        $(this.element).parent().css("margin", "5px"); //limit 5px to the top
        $(this.element).dialog("option", {
            resizeStart: function(event, ui) {
                $(event.target).parent().css("position", "fixed");
                return true;
            },
            resizeStop: function(event, ui) {
                $(event.target).parent().css("position", "fixed");
                return true;
            }
        });
        this._oldinit();
    };
}

function structureHTML() {
    'use strict';

    let html = $('center').html();
    $('center').html('<div id="tables-wrapper"></div>');
    let wrapper = $('#tables-wrapper');
    wrapper.html(html);

    wrapper
        .find('h1:first')
        .insertBefore(wrapper)
        .attr('id', 'title-heading');

    wrapper.find('table').each(function() {
        let columns = [];
        $(this).find('tr:first th').each(function() {
            columns.push($(this).text().replace(/\s/g, '-').toLowerCase());
        });

        for(let i = 0; i < columns.length; ++i) {
            $(this).find('td:nth-child('+(i+1)+')').addClass('column-'+columns[i]);
        }
    });

    $('.column-' + disciplineColumn).each(function() {
        $(this).attr('data-original', $(this).text());
    });

    defaultHTML = wrapper.html();
}

function injectSettingHTML() {
    'use strict';

    let html = '<div id="settings-wrapper">'+
        '   <div id="week-wrapper" class="option-wrapper">'+
        '   	<span id="week-span"></span>'+
        '   	<label for="offset-week">Offset week by 1</label>'+
        '   	<input type="checkbox" id="offset-week" />'+
        '   </div>'+
        '   <div id="group-wrapper" class="option-wrapper">'+
        '   	<label for="group-select">Your group: </label>'+
        '   	<select id="group-select">'+
        '   	<option value="0"></option>';
    $('#tables-wrapper h1').each(function() {
        try{
            let group = parseInt($(this).text().replace('Grupa ', ''), 10);
            html += '<option value="'+group+'">'+group+'</option>';
        }
        catch(e){
            console.log(e);
        }
    });
    html += '</select>'+
        '   </div>'+
        '   <div id="half-group-wrapper" class="option-wrapper">'+
        '       <label for="half-group-select">Your half group: </label>'+
        '       <select id="half-group-select">'+
        '          <option value="1">1</option>'+
        '          <option value="2">2</option>'+
        '          <option value="0">Don\'t hide</option>'+
        '       </select>'+
        '   </div>'+
        '   <button id="hider-button">Hidden disciplines</button>'+
        '   <button id="renamer-button">Rename disciplines</button>'+
        '   <button id="reset-button">Reset Settings</button>'+

        '   <div id="hider-dialog">'+
        '       <span>Select the disciplines you <b>don\'t</b> want highlighted:</span>'+
        '       <div id="hider-list-wrapper"></div>'+
        '       <div style="float: right;">'+
        '           <button id="hider-save">Save</button>'+
        '           <button id="hider-close">Cancel</button>'+
        '       </div>'+
        '   </div>'+

        '   <div id="renamer-dialog">'+
        '       <span>Enter the text you want each discipline to be renamed to. <br> Leave the field blank if you don\'t want a discipline\'s name replaced.</span>'+
        '       <div id="renamer-list-wrapper"></div>'+
        '       <div style="float: right;">'+
        '           <button id="renamer-save">Save</button>'+
        '           <button id="renamer-close">Cancel</button>'+
        '       </div>'+
        '   </div>';

    html += '</div>';
    $(html).insertAfter('#title-heading');

    $('#offset-week').checkboxradio({
        icon:false
    });
    $('button').button();
    $('select').selectmenu();

    $('#hider-dialog').dialog({
        modal: true,
        width: 320,
        autoOpen: false
    });
    $('#renamer-dialog').dialog({
        modal: true,
        width: 420,
        autoOpen: false
    });
}

function getDisciplinesList() {
    let discSet = new Set();
    $('.column-' + disciplineColumn).each(function() {
        discSet.add($(this).text());
    });

    disciplines = [...discSet];
}

function setEventHandlers() {
    $('body')
        .on('change', '#offset-week', function() {
        options.weekOffset = ($(this).prop('checked') ? 1 : 0);
        performChange();
    })
        .on('selectmenuchange', '#group-select', function() {
        options.group = parseInt($(this).val(), 10);
        performChange();
    })
        .on('selectmenuchange', '#half-group-select', function() {
        options.halfGroup = parseInt($(this).val(), 10);
        performChange();
    })
        .on('click', '#hider-button', function() {
        $('#hider-dialog')
            .dialog('option', 'position', {
            my: 'center top',
            at: 'center top+50',
            of: window
        })
            .dialog('open');
    })
        .on('click', '#renamer-button', function() {
        $('#renamer-dialog')
            .dialog('option', 'position', {
            my: 'center top',
            at: 'center top+50',
            of: window
        })
            .dialog('open');
    })
        .on('click', '#reset-button', function() {
        options = getDefaultOptions();
        performChange();
    })
        .on('click', '#hider-save', function() {
        let hides = [];
        $('#hider-list-wrapper tr').each(function() {
            if($(this).find('.hide-check').is(':checked')){
                hides.push($(this).find('.discipline-name').attr('data-discipline'));
            }
        });

        $('#hider-dialog').dialog('close');
        options.disciplinesToHide = hides;
        performChange();
    })
        .on('click', '#hider-close', function() {
        $('#hider-dialog').dialog('close');
    })
        .on('click', '#renamer-save', function() {
        let renames = {};
        $('#renamer-list-wrapper tr').each(function() {
            let renameTo = $(this).find('.rename-to').val();
            if(renameTo.length > 0){
                renames[$(this).find('.discipline-name').text()] = renameTo;
            }
        });

        options.disciplinesToReplace = renames;

        $('#renamer-dialog').dialog('close');
        performChange();
    })
        .on('click', '#renamer-close', function() {
        $('#renamer-dialog').dialog('close');
    });
}

function performChange(){
    saveOptions();
    updateSettingsUi();
    runFixer();
}

function saveOptions() {
    GM_setValue("options", JSON.stringify(options));
}

function updateSettingsUi() {
    let week = getWeekNo();
    if(week === 0){
        week = 2;
    }
    $('#week-span').text("Week "+ week + ": ");

    $('#offset-week')
        .prop('checked', options.weekOffset === 1)
        .checkboxradio('refresh');

    if($('#tables-wrapper .column-frecventa:contains(sapt)').length === 0){
        $('#week-wrapper').hide();
    }
    else{
        $('#week-wrapper').show();
    }



    if($('#group-select:contains('+options.group+')').length){
        $('#group-select')
            .val(options.group)
            .selectmenu('refresh');
    }

    if($('#tables-wrapper table').length === 1){
        $('#group-wrapper').hide();
    }
    else{
        $('#group-wrapper').show();
    }



    $('#half-group-select')
        .val(options.halfGroup)
        .selectmenu('refresh');

    if($('#tables-wrapper .column-formatia:contains(/1)').length === 0 &&
       $('#tables-wrapper .column-formatia:contains(/2)').length === 0){
        $('#half-group-wrapper').hide();
    }
    else{
        $('#half-group-wrapper').show();
    }



    let hiderList = '<table>';
    let renamerList = '<table>';
    for(let i = 0; i < disciplines.length; ++i) {
        let discipline = disciplines[i];

        let discName = discipline;
        let discReplacer = '';
        if(options.disciplinesToReplace.hasOwnProperty(discipline) && options.disciplinesToReplace[discipline]){
            discName = options.disciplinesToReplace[discipline];
            discReplacer = discName;
        }

        hiderList += '<tr><td class="discipline-name" data-discipline="'+discipline+'">'+discName+'</td><td class="check-column"><input class="hide-check" type="checkbox" '+(options.disciplinesToHide.includes(discipline) ? ' checked': '')+'/></td></tr>';
        renamerList += '<tr><td class="discipline-name" >'+discipline+'</td><td class="input-column"><input class="rename-to" type="text" value="'+discReplacer+'"/></td></tr>';
    }
    hiderList += '</table>';
    renamerList += '</table>';

    $('#hider-list-wrapper').html(hiderList);
    $('#renamer-list-wrapper').html(renamerList);
}

function runFixer() {
    'use strict';

    $('#tables-wrapper').html(defaultHTML);

    let myGroupHeading = $("h1:contains('"+options.group+"')");
    if($('#tables-wrapper table').length === 1){
    	myGroupHeading = $('#tables-wrapper h1');
    }
    let myGroupTable = myGroupHeading.next("table");

    myGroupHeading.prependTo("#tables-wrapper").css("margin", "30px" );
    myGroupTable.insertAfter(myGroupHeading).css("margin-bottom","147px");

    for(let discipline in options.disciplinesToReplace){
        if(options.disciplinesToReplace.hasOwnProperty(discipline) && options.disciplinesToReplace[discipline]){
            let a = $('.column-'+disciplineColumn+' a:contains('+discipline+')');
            a.each(function() {
                let text = $(this).text();
                $(this)
                    .text(text.replace(discipline, options.disciplinesToReplace[discipline]));
            });
        }
    }

    for(let i = 0; i < options.disciplinesToHide.length; ++i) {
        let discipline = options.disciplinesToHide[i];
        myGroupTable
            .find('.column-'+disciplineColumn+'[data-original="'+discipline+'"]')
            .closest('tr')
            .addClass("meh-tr")
            .addClass("nope-tr");
    }

    myGroupTable.find("tr:contains('"+options.group+"/"+(3-options.halfGroup)+"')").addClass("meh-tr");

    let weekNo = getWeekNo();
    let otherWeekNo = weekNo + 1;

    let saptString = "sapt. " + otherWeekNo;

    myGroupTable.find("tr:contains(" + saptString + ")").addClass("other-week-tr");

    $("tr:contains('Curs')").addClass("curs-tr");
    $("tr:contains('Seminar')").addClass("seminar-tr");
    $("tr:contains('Laborator')").addClass("labor-tr");

    let days = ["Luni", "Marti", "Miercuri", "Joi", "Vineri"];
    $("table").each(function(){
        for(let i in days){
            let dayString = days[i];
            let last = $(this).find("tr:contains(" + dayString + ")");
            last = $(last[last.length - 1]);
            if(!last.is(':last-child')){
                last.addClass("last-of-day-tr");
            }
        }
    });
}

function getWeekNo() {
    let weekInYear = new Date().getWeek();
    let firstWeek = new Date(2017, 1, 27).getWeek();
    return (weekInYear - firstWeek + 1 + options.weekOffset) % 2;
}