summaryrefslogtreecommitdiff
path: root/uoft-artsci-exam-filter.user.js
blob: 2535f8ae6a4e39e601805409ed4941d4dbfdc66f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// ==UserScript==
// @name          University of Toronto Arts & Science Exam Schedule Filter
// @namespace     https://alxu.ca/
// @match         http://www.artsci.utoronto.ca/current/exams/*
// @version       1.0
// @grant         none
// @downloadURL   https://alxu.ca/uoft-artsci-exam-filter.user.js
// @require       https://www.kryogenix.org/code/browser/sorttable/sorttable.js#sha512=33bdc388d816cab2190ee33918143074a3d1bc8da315b0d6117eb8233d8a7ed51752aa26419296c06120c6faee6053d4589fca2a7590846139d69e84cb600808
// @run-at        document-end
// ==/UserScript==

/*
 * Copyright 2017 Alex Xu, released under GPLv3 or later
 *
 * Firefox: Install Greasemonkey: https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/ then (re-)open this file.
 * Chrome: Install Tampermonkey: https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo then (re-)open this file.
 * Note that installing this script directly will also work, but sorting will not be supported, and in addition, on Windows/Mac the script will be automatically and permanently disabled once Chrome restarts.
 */

(function () {
"use strict";

if (document.title.indexOf("Timetable") === -1)
    return;

var tbl = document.getElementsByClassName("vertical")[0],
    trs = tbl.querySelectorAll("tr:not(:first-child)");

var storage = localStorage.getItem("filterinfo");
if (storage)
    try {
        storage = JSON.parse(storage);
    } catch (e) {
        if (!(e instanceof SyntaxError))
            throw e;
    }

if (!storage)
    storage = {};

var parseCourses = function (mycoursesstr) {
    var mycourses = {names: [], sections: []};
    mycoursesstr.split(/[ ,;]/).forEach(function (c) {
        // merge multiple delimiters
        if (!c)
            return;
        var cspl = c.split(/[\/:]/);
        if (cspl[1])
            mycourses.sections[mycourses.names.length] = cspl[1].toUpperCase();
        mycourses.names.push(cspl[0].toUpperCase());
    });
    return mycourses;
};

// the meat.
var dofilter = function (myname, mycourses) {
    var getCI = function (name) {
        return mycourses.names.findIndex(function (mycname) {
            return name.indexOf(mycname) > -1;
        });
    };

    var checkCourse = function (tr) {
        var ch = tr.children,
            s = ch[1].innerHTML;

        // if we don't have any courses, match everything instead of nothing
        if (mycourses.names.length) {
            var courseindex = getCI(ch[0].innerHTML);
            if (courseindex === -1)
                return false;

            var l = s.match(/L?[0-9]+/);
            if (l && mycourses.sections[courseindex] && l[0].indexOf(mycourses.sections[courseindex]) === -1)
                return false;
        }

        if (myname) {
            var nr = s.match(/([A-Z]+) - ([A-Z]+)/);
            if (nr && (myname < nr[1] || myname > nr[2]))
                return false;
        }

        return true;
    };

    for (var i = trs.length - 1; i >= 0; i--) {
        trs[i].style.display = checkCourse(trs[i]) ? '' : 'none';
    }
};

var ourctnr = document.createElement("div");
ourctnr.appendChild(document.createTextNode("Filter by name: "));

var makeInput = function (attr, size) {
    var input = document.createElement("input");
    input.type = "text";
    input.size = size;
    if (storage[attr])
        input.value = storage[attr];
    input.addEventListener("input", function () {
        storage[attr] = this.value.toUpperCase();
        // in theory we could cache this, but it's fast enough
        dofilter(storage.name, parseCourses(storage.courses));
        localStorage.setItem("filterinfo", JSON.stringify(storage));
    }, false);
    ourctnr.appendChild(input);
};

makeInput("name", "4");

ourctnr.appendChild(document.createTextNode(", courses: "));

makeInput("courses", "50");

ourctnr.appendChild(document.createElement("br"));
ourctnr.appendChild(document.createTextNode("Courses should be separated by spaces and sections preceded by slash."));
ourctnr.appendChild(document.createElement("br"));
ourctnr.appendChild(document.createTextNode("Example: ABC101 XYZ201/L0301"));

tbl.parentNode.insertBefore(ourctnr, tbl);

if (storage.name || storage.courses)
    window.requestAnimationFrame(function () {
        dofilter(storage.name, parseCourses(storage.courses));

        if (typeof sorttable !== "undefined") {
            sorttable.init();
            sorttable.makeSortable(tbl);
            sorttable.innerSortFunction.apply(tbl.getElementsByTagName("th")[2], []);
        }
    });

if (typeof sorttable !== "undefined")
    for (var i = trs.length - 1; i >= 0; i--) {
        var dateEl = trs[i].children[2],
            dateSplit = dateEl.innerHTML.split(" "); // ["MON", "1", "JAN"]
        dateEl.setAttribute("sorttable_customkey", dateSplit[2] + dateSplit[1]); // "JAN1"
    }

}());