summaryrefslogtreecommitdiff
path: root/uoft-artsci-exam-filter.user.js
blob: 01239a569d9e96fa47bb1673bd1ba6575078e01f (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
142
143
144
145
146
// ==UserScript==
// @name          University of Toronto Arts & Science Exam Schedule Filter
// @namespace     https://alxu.ca/
// @match         http://www.artsci.utoronto.ca/current/exams/*
// @version       1.4
// @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 and 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];

if (!tbl)
    return;

var trs = tbl.querySelectorAll("tr:not(:first-child)");

if (!trs.length)
    return;

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 = (mycoursesstr) => mycoursesstr
        // ignore leading, trailing, consecutive delimiters
        .split(/[ ,;]+/).filter((v) => v)
        .map((c) => c.split(/[\/:]+/))
        .map((cspl) => ({
            name: cspl[0].toUpperCase(),
            section: cspl.length > 1 ? cspl[1].toUpperCase() : null
        }));

var dofilter = function (myname, mycourses) {
    var getCourse = (name) => mycourses.find((c) => c && name.indexOf(c.name) > -1);

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

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

        // if we don't have any courses, match everything instead of nothing
        if (mycourses.length) {
            var course = getCourse(ch[0].innerHTML);
            if (!course)
                return false;

            if (course.section) {
                var l = s.match(/L?[0-9]+/);
                if (l && l[0].indexOf(course.section) === -1)
                    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, placeholder) {
    var input = document.createElement("input");
    input.type = "text";
    input.size = size;
    input.placeholder = placeholder;
    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", "SMIT");

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

makeInput("courses", "60", "ENG100 ECO100/101 CLA204/L0101");

ourctnr.appendChild(document.createElement("br"));
ourctnr.appendChild(document.createTextNode("For name, you should enter the first few letters of your surname (not your given name, obviously)."));
ourctnr.appendChild(document.createElement("br"));
ourctnr.appendChild(document.createTextNode("Courses should be separated by spaces and sections preceded by slash. You can omit the start or end of a field. For example: entering \"BC10 XYZ201/L0301\" for courses will show only courses containing \"BC10\" as well as section L0301 of course XYZ201."));

tbl.parentNode.insertBefore(ourctnr, tbl);

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

        if (typeof sorttable !== "undefined") {
            for (var i = trs.length - 1; i >= 0; i--) {
                var c = trs[i].children,
                    dEl = c[2],
                    dSpl = dEl.innerHTML.split(" ");

                c[2].setAttribute("sorttable_customkey",
                    ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"].indexOf(dSpl[2]) +
                    ("00" + dSpl[1]).slice(-2) +
                    ["AM", "PM", "EV"].indexOf(c[3].innerHTML.split(" ")[0]));
            }
            sorttable.init();
            sorttable.makeSortable(tbl);
            sorttable.innerSortFunction
                .apply(tbl.getElementsByTagName("th")[2], []);
        }
    });


}());