summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--uoft-artsci-exam-filter.user.js141
1 files changed, 141 insertions, 0 deletions
diff --git a/uoft-artsci-exam-filter.user.js b/uoft-artsci-exam-filter.user.js
new file mode 100644
index 0000000..2535f8a
--- /dev/null
+++ b/uoft-artsci-exam-filter.user.js
@@ -0,0 +1,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"
+ }
+
+}());