Greasy Fork is available in English.

(Mobile Version) Better CGS Schedule

Adds class links, removes annoying special schedule days, and makes your schedule into a checklist.

// ==UserScript==
// @name         (Mobile Version) Better CGS Schedule 
// @version      2.5
// @description  Adds class links, removes annoying special schedule days, and makes your schedule into a checklist.
// @author       Liam Wang
// @include      http://inside.catlin.edu/scripts/sched/index.php?*
// @include      https://inside.catlin.edu/scripts/sched/index.php?*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js
// @grant GM_addStyle
// @namespace https://greasyfork.org/users/118299
// ==/UserScript==



(function() {
    'use strict';
    GM_addStyle('.specialday{ padding: 0;} #special{ margin: 0; height: 100% !important;} #special tr:first-child td { border-top: 0; } #special tr td:first-child { border-left: 0; } #special tr td:last-child { border-right: 0; } TD.daylabel, TD.mainlabel { border-style: solid; border-width: 2px; border-color: #000000; } HR.controls { display: none; } TD.controls.arrows a img { content: url("https://i.imgur.com/FBeFEE6.png") !important; } TD.controls.arrows:last-child a img { -moz-transform: scaleX(-1); -o-transform: scaleX(-1); -webkit-transform: scaleX(-1); transform: scaleX(-1); filter: FlipH; -ms-filter: "FlipH"; } TD.controls.links a { color: black } ');

    // Add your classes here! No need to remove the old ones, just add more in the blank spaces.
    var courseList =
        [['Acc Science I', 'https://catlin.haikulearning.com/nakayamab/acceleratedscienceibiologyphysicsche2/cms_page/view'],
         ['Chinese II', 'https://catlin.haikulearning.com/hub/chineseii2/calendar'],
         ['Advanced Algebra II', "https://catlin.haikulearning.com/sloank/algebraii1/cms_page/view/30473220"],
         ['English 9', "https://docs.google.com/document/d/1i9e4bweemW3G9iEfcFcGR49SI3crwSUYs7VwKJRe_eE/edit"],
         ['Human Crossroads', "https://catlin.haikulearning.com/masonc/humancrossroads/cms_page/view/30077224"],
         ['Comp Sci II', "https://catlin.haikulearning.com/merrilla/honorscomputerscienceiidatastructure/cms_page/view/30019866"],
         ['Spanish III', "https://catlin.haikulearning.com/villar/spanishiiicommunicationb1/cms_page/view"],
         ['<ClassNameGoesHere>', "<ClassLinkGoesHere>"],
         ['<ClassNameGoesHere>', "<ClassLinkGoesHere>"],
         ['<ClassNameGoesHere>', "<ClassLinkGoesHere>"],
         ['<ClassNameGoesHere>', "<ClassLinkGoesHere>"],
         ['<ClassNameGoesHere>', "<ClassLinkGoesHere>"],
         ['<ClassNameGoesHere>', "<ClassLinkGoesHere>"],
         ['<ClassNameGoesHere>', "<ClassLinkGoesHere>"],
         ['<ClassNameGoesHere>', "<ClassLinkGoesHere>"]];



    // Remove swipe to page for mobile devices
    //removeMobileSwipePaging();

    // Generate hashcode for this page
    var daylabels;
    $('.daylabel').each(function () {
        daylabels+=$(this).text();
    });
    var pageHash = hashCode(daylabels);
    // Replace the link with the proper HTML for special schedule days
    specialDaysFix();

    // Replace course names with links
    swapLinks(courseList);

    // Reset and restore checklist data
    restoreChecklistData(pageHash);

    // Add reset button
    addResetChecklistButton(pageHash);


    // Checklist functionality
    makeCheckboxes(pageHash);

    // Parse the time labels and assign each class a time range
    parseAssignTimes();

    // Add the time divider to the appropreate cell
    addHrToTableColumn(getCurrentDayIndex());
    setInterval(function(){
        $('.timeHr').each(function() {
            $(this).remove();
        });
        addHrToTableColumn(getCurrentDayIndex());
    }, 60000);
})(jQuery);

function removeMobileSwipePaging() {
    $('#schedarea').removeAttr('ontouchstart');
    $('#schedarea').removeAttr('ontouchend');
    $('#schedarea').removeAttr('ontouchmove');
    $('#schedarea').removeAttr('ontouchcancel');
    $('.controls.arrows').each(function () {
        var link;
        link = $(this).children().attr('href');
        $(this).children().click(function () {
            window.location.href = link;
        });
        $(this).children().removeAttr('href');
    });
}
function specialDaysFix () {
    $('.specialday a').each(function () {
        $(this).css('text-decoration', 'line-through');
        var specialDayHTML = ($.ajax({type: "GET", url: 'https://inside.catlin.edu'+$(this).attr('href'), async: false}).responseText);
        $(this).append(specialDayHTML);
        $(this).replaceWith($(this).find('#schedarea').html());

    });
    $('.specialday').each(function () {
        $(this).children('table:first').children('tbody:first').children('tr:first').remove();
        $(this).children('table:first').attr('id', 'special');
    });

    $('#special').each(function () {
        $(this).css('height', '100%');
    });
}

function swapLinks(courseList) {
    var courseName;
    $('.coursename').each(function () {
        courseName = $(this).text();
        for (var ii=0; ii<courseList.length; ii++) {
            if (courseName == courseList[ii][0]) {
                $(this).replaceWith('<a class="coursename" target="_blank" href="'+courseList[ii][1]+'">'+courseName+'</a>');
                break;
            }
        }
    });
}

function addResetChecklistButton(pageHash) {
    $('<td class="controls links"><a>Clear Checklist</a></td>').insertAfter('.controls.links:eq(2)');
    $('.links').each(function () {
        $(this).css('width', '21%');
        $(this).css('text-decoration', 'underline');
    });
    $('.controls.links:eq(3)').click(function () {
        $('.period').each(function () {
            if ($(this).attr('data-is-checked')=='true') {
                eraseCookie(pageHash.toString()+$(this).attr('data-index'));
            }
        });
        location.reload();
    });
}

function restoreChecklistData(pageHash) {
    $('.period')
        .each(function () {$(this).attr('data-is-checked', 'false');});
    var counter = 0;
    $('.period').each(function () {
        $(this).attr('data-index', counter);
        if (readCookie(pageHash.toString()+counter.toString())=='true') {
            $(this).css('background-color', shadeColor($(this).attr('bgcolor'), -0.5));
            $(this).css('text-decoration', 'line-through');
            $(this).attr('data-is-checked', 'true');
        }
        counter++;
    });
    $('.coursename')
        .css('color', 'black')
        .click(function(e) {e.stopPropagation();});
}

function makeCheckboxes(pageHash) {
    $('.period')
        .click(function () {
        if ($(this).attr('bgcolor') != '#FFFFFF' && $(this).attr('bgcolor') != '#C0C0C0') {
            if($(this).attr('data-is-checked')=='false'){
                checkClass($(this), pageHash);
            } else {
                uncheckClass($(this), pageHash);
            }
        }
    }).hover(function () {
        if ($(this).attr('bgcolor') != '#FFFFFF' && $(this).attr('bgcolor') != '#C0C0C0') {
            if($(this).attr('data-is-checked')=='false'){
                $(this).css('background-color', shadeColor($(this).attr('bgcolor'), -0.1));
            }
        }
    }, function () {
        if ($(this).attr('bgcolor') != '#FFFFFF' && $(this).attr('bgcolor') != '#C0C0C0') {
            if($(this).attr('data-is-checked')=='false'){
                $(this).css('background-color', $(this).attr('bgcolor'), -0.1);
            }
        }
    });
}

function checkClass(classObj, pageHash) {
    classObj.css('background-color', shadeColor(classObj.attr('bgcolor'), -0.5));
    classObj.css('text-decoration', 'line-through');
    classObj.attr('data-is-checked', 'true');
    createCookie(pageHash.toString()+classObj.attr('data-index').toString(), 'true');
}

function uncheckClass(classObj, pageHash) {
    classObj.css('background-color', shadeColor(classObj.attr('bgcolor'), -0.1));
    classObj.css('text-decoration', 'none');
    classObj.attr('data-is-checked', 'false');
    eraseCookie(pageHash.toString()+classObj.attr('data-index').toString());
}

function parseAssignTimes() {
    var timeLabelsArray = new Array(0);
    $('.sched:not(#special) > tbody > tr > .times:first').each(function () {
        var timeText = $(this).text();
        var startTime = timeText.substring(0, timeText.indexOf("-"));
        var startHours = parseInt(startTime.substring(0, startTime.indexOf(":")), 10);
        var convertedStartTime = ((startHours<7) ? startHours+12 : startHours)*60+parseInt(startTime.substring(startTime.indexOf(":")+1), 10);
        timeLabelsArray[timeLabelsArray.length] = convertedStartTime;
    });
    $('.sched:not(#special) > tbody > tr > .times').each(function () {
        var timeText = $(this).text();
        var endTime = timeText.substring(timeText.indexOf("-")+1);
        var endHours = parseInt(endTime.substring(0, endTime.indexOf(":")), 10);
        var convertedEndTime = (((endHours<7)? endHours+12 : endHours)*60)+parseInt(endTime.substring(endTime.indexOf(":")+1), 10);
        timeLabelsArray[timeLabelsArray.length] = convertedEndTime;
    });
    $(".sched:not(#special) > tbody > tr").each(function(rowIndex) {
        $(this).children().each(function(tdIndex) {
            $(this).attr('data-start-time', timeLabelsArray[(rowIndex-1)]);
            $(this).attr('data-end-time', timeLabelsArray[rowIndex+($(this).attr('rowspan')-1)]);
        });
    });
}

function addHrToTableColumn(currentDay) {
    if (currentDay==-1) {
        return;
    }

    var hour = new Date().getHours();
    var time = hour * 60 + new Date().getMinutes() + new Date().getSeconds()/60;
    var colToGet = currentDay;

    var offsets = [];
    var skips = [];

    function incrementOffset(index) {
        if (offsets[index]) {
            offsets[index]++;
        } else {
            offsets[index] = 1;
        }
    }

    function getOffset(index) {
        return offsets[index] || 0;
    }
    $(".sched:not(#special) > tbody > tr").each(function(rowIndex) {
        var thisOffset = getOffset(rowIndex);

        $(this).children().each(function(tdIndex) {
            var rowspan = $(this).attr("rowspan");

            if (tdIndex + thisOffset >= colToGet) {
                if(skips[rowIndex]) return false;
                if (!$(this).hasClass('specialday')){
                    var startTime = $(this).attr('data-start-time');
                    var endTime = $(this).attr('data-end-time');

                    if (startTime<=time&&time<endTime) {
                        addHr($(this), time, startTime, endTime);
                    }
                }

                if (rowspan > 1) {
                    for (var i = 1; i < rowspan; i++) {
                        skips[rowIndex + i] = true;
                    }
                }
                return false;
            }
            if (rowspan > 1) {
                for (var ii = 1; ii < rowspan; ii++) {
                    incrementOffset(rowIndex + ii);
                }
            }
        });
    });
}

function getCurrentDayIndex() {
    var month = new Date().getMonth();
    var date = new Date().getDate();
    var foundIndex = -2;
    $(".sched > tbody > tr:first > .daylabel").each(function (dayLabelIndex) {
        if ($(this).text().substring(4, ($(this).text().indexOf(")")==-1)?$(this).text().length:$(this).text().indexOf(")")-3) == getMonthAbbr(month)+" "+date) {
            foundIndex = dayLabelIndex;
        }
    });
    return foundIndex+1;
}

function addHr(classObj, time, startTime, endTime) {
    //classObj.css('background-color', shadeColor(classObj.attr('bgcolor'), -0.3));
    var contrastColor = (getContrastYIQ(colorToHex(classObj.css('background-color'))));
    var adjustedPercentage = ((time-startTime)/(endTime-startTime)*100);
    classObj.css('position', 'relative');
    classObj.append('<hr class="timeHr" style="position:absolute !important; width:90%; border : 0;height: 15px; background-image: linear-gradient(to right, rgba(0, 0, 0, 0), '+contrastColor+', rgba(0, 0, 0, 0));  margin:0px; left:5%; right:5%;  top:'+adjustedPercentage+'%;" >');
}

function colorToHex(rgb){
    rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
    return ((rgb && rgb.length === 4) ? "#" +
            ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
            ("0" + parseInt(rgb[2],10).toString(16)).slice(-2) +
            ("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : '').toUpperCase();
}

function shadeColor(color, percent) {
    var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF;
    return ("#"+(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1)).toUpperCase();
}

function hashCode(s) {
    var h = 0, l = s.length, i = 0;
    if ( l > 0 )
        while (i < l)
            h = (h << 5) - h + s.charCodeAt(i++) | 0;
    return h;
}

function createCookie(name, value, days) {
    var expires;

    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        expires = "; expires=" + date.toGMTString();
    } else {
        expires = "";
    }
    document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/";
}

function readCookie(name) {
    var nameEQ = encodeURIComponent(name) + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) === ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
    }
    return null;
}

function eraseCookie(name) {
    createCookie(name, "", -1);
}
function getContrastYIQ(hexcolor){
    var r = parseInt(hexcolor.substr(1,2),16);
    var g = parseInt(hexcolor.substr(3,2),16);
    var b = parseInt(hexcolor.substr(5,2),16);
    var yiq = ((r*299)+(g*587)+(b*114))/1000;
    return (yiq >= 150) ? 'black' : 'white';
}

function getMonthAbbr(monthNum) {
    switch (monthNum) {
        case 0:
            return 'Jan';
        case 1:
            return 'Feb';
        case 2:
            return 'Mar';
        case 3:
            return 'Apr';
        case 4:
            return 'May';
        case 5:
            return 'Jun';
        case 6:
            return 'Jul';
        case 7:
            return 'Aug';
        case 8:
            return 'Sep';
        case 9:
            return 'Oct';
        case 10:
            return 'Nov';
        case 11:
            return 'Dec';
    }
}