Skip to content

Instantly share code, notes, and snippets.

@katrinafyi
Last active May 22, 2018 10:56
Show Gist options
  • Save katrinafyi/e292074f57247ecca4b93b54e97ce278 to your computer and use it in GitHub Desktop.
Save katrinafyi/e292074f57247ecca4b93b54e97ce278 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Blackboard Search Enhancements
// @author Kenton Lam
// @description Searches blackboard
// @match https://learn.uq.edu.au/*
// @match https://ilearn.bond.edu.au/*
// @version 0.1.1
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/featherlight/1.7.13/featherlight.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.0/fuse.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js
// @require https://openuserjs.org/src/libs/sizzle/GM_config.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.js
// ==/UserScript==
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var featherlight__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
/* harmony import */ var featherlight__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(featherlight__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var fuse_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3);
/* harmony import */ var fuse_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fuse_js__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4);
/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var gm_config__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(5);
/* harmony import */ var gm_config__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(gm_config__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var lz_string__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6);
/* harmony import */ var lz_string__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(lz_string__WEBPACK_IMPORTED_MODULE_5__);
// ==UserScript==
// @name Blackboard Search Enhancements
// @author Kenton Lam
// @description Searches blackboard
// @match https://learn.uq.edu.au/*
// @match https://ilearn.bond.edu.au/*
// @version VERSION
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/featherlight/1.7.13/featherlight.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.0/fuse.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js
// @require https://openuserjs.org/src/libs/sizzle/GM_config.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.js
// ==/UserScript==
// These imports are so VSCode recognises the libraries.
// They are removed by webpack and loaded via TamperMonkey's @require.
/* Queue.js */
/* eslint-disable */
//code.iamkate.com
function Queue(){var a=[],b=0;this.getLength=function(){return a.length-b};this.isEmpty=function(){return 0==a.length};this.enqueue=function(b){a.push(b)};this.dequeue=function(){if(0!=a.length){var c=a[b];2*++b>=a.length&&(a=a.slice(b),b=0);return c}};this.peek=function(){return 0<a.length?a[b]:void 0}};
/* eslint-enable */
function BlackboardSearch() {
if (window.location.href.indexOf('/courseMenu.jsp') !== -1) return;
let $ = jquery__WEBPACK_IMPORTED_MODULE_0___default.a.noConflict(true);
// todo debug check before console log.
class UniHelper {
constructor(pageUrl) {
this.uni = {};
this.detectUni(pageUrl);
}
detectUni(pageUrl) {
for (let i = 0; i < UniHelper.uniDefinitions.length; i++) {
const uni = UniHelper.uniDefinitions[i];
if (lodash__WEBPACK_IMPORTED_MODULE_3___default.a.startsWith(pageUrl, uni.baseUrl)) {
this.uni = uni;
false && console.log('Matched: ' + uni.baseUrl);
}
}
if (!this.uni) {
console.log('Unknown university: ' + pageUrl);
this.uni = UniHelper.uniDefinitions[0];
}
}
parseCourse(courseString) {
if (this.uni.parseCourse) {
return this.uni.parseCourse(courseString);
} else {
return UniHelper.uniDefinitions[0].parseCourse(courseString);
}
}
parseUrl(url) {
if (this.uni.parseUrl) {
return this.uni.parseUrl(url);
} else {
let match = /[&?]course_id=([^&?]+)/.exec(url);
if (!match) return null;
return match[1];
}
}
getIFrameUrl(courseId) {
if (this.uni.getIFrameUrl) {
return this.uni.getIFrameUrl(courseId);
} else {
return this.uni.baseUrl + 'webapps/blackboard/content/courseMenu.jsp?course_id=' +
courseId +'&newWindow=true';
}
}
}
UniHelper.uniDefinitions = [
{
baseUrl: 'https://learn.uq.edu.au/',
parseCourse: function (courseTitle) {
let match = /^\[([A-Za-z0-9/]+)\] (.*)$/.exec(courseTitle);
if (!match) return null;
let letters = match[1].slice(0, 4);
let courseCodeArray = match[1].split('/');
for (let c = 0; c < courseCodeArray.length; c++) {
if (courseCodeArray[c].length < 8) {
courseCodeArray[c] = letters + courseCodeArray[c];
}
}
return {
courseName: match[0].replace('['+match[1]+'] ', '').trim(),
courseCodeArray: courseCodeArray
};
}
},
{
baseUrl: 'https://ilearn.bond.edu.au/',
parseCourse: function (courseTitle) {
let m = /^([^_]+)_[^ ]+ \((.*)\)$/.exec(courseTitle);
return {
courseName: m[2],
courseCodeArray: [m[1]],
};
}
}
];
class BlackboardTreeParser {
constructor(uniHelper) {
/** @type {UniHelper} */
this.uniHelper = uniHelper;
this.callback = function() {};
this.courseId = '';
this.retryCount = 0;
this.treeData = {};
this.locked = false;
}
parseTree(courseId, callback) {
this.courseId = courseId;
this.callback = callback;
this.retryCount = 0;
this.locked = true;
this.appendIFrame();
}
isUpdating() {
return this.locked;
}
parseOneUL(listNode, rootName) {
for (let j = 0; j < listNode.children.length; j++) {
if (listNode.children[j].tagName.toUpperCase() === 'LI') {
let li = listNode.children[j];
let text = li.children[2].textContent.trim();
let thisName = lodash__WEBPACK_IMPORTED_MODULE_3___default.a.concat(rootName, text);
this.treeData.items.push({
'courseId': this.courseId,
'link': li.children[2].href,
'label': thisName
});
if (li.children.length > 3 && li.children[3].tagName.toUpperCase() === 'UL')
this.parseOneUL(li.children[3], thisName);
}
}
}
startTreeParse(rootDiv) {
this.treeData = {
'lastUpdated': Date.now(),
'courseId': this.courseId,
'courseName': this.courseName,
'courseCodeArray': this.courseCodeArray,
'iframeSrc': this.iframeSrc,
'items': [],
};
for (let i = 0; i < rootDiv.children.length; i++) {
this.parseOneUL(rootDiv.children[i], [this.courseCodeArray.join('\u200A/\u200A')]);
}
return this.treeData;
}
bootstrapTreeParse() {
this.retryCount++;
function retry() {
setTimeout(this.bootstrapTreeParse.bind(this), 100*Math.pow(2, this.retryCount-1));
}
console.log('bootstrap tree parse:');
console.log(this);
let frameDoc = this.iframe.contentDocument;
frameDoc.querySelector('#expandAllLink').click();
let div = frameDoc.querySelector('#courseMenu_folderView');
if (div === null || frameDoc.querySelector('.--empty--') !== null) {
retry.call(this);
return false;
} else {
console.log('parsing');
let tree = this.startTreeParse.call(this, div);
if (!tree.items.length) {
retry.call(this);
return false;
}
this.iframe.parentNode.removeChild(this.iframe);
this.retryCount = 0;
this.locked = false;
return this.callback(tree);
}
}
iframeOnLoad() {
let parsed = this.uniHelper.parseCourse(
this.iframe.contentDocument.getElementById('courseMenu_link').textContent);
this.courseName = parsed.courseName;
this.courseCodeArray = parsed.courseCodeArray;
if (this.iframe.contentDocument.getElementById('courseMapButton'))
this.callback(null);
else
this.bootstrapTreeParse.call(this);
}
appendIFrame() {
console.log('inserting iframe');
if (document.getElementById('userscript-search-iframe') !== null) {
throw new Error('Blackboard search IFrame already exists.');
}
this.iframe = document.createElement('iframe');
this.iframe.id = 'userscript-search-iframe';
// bug this will break if someone uses multiple unis.
this.iframe.src = this.uniHelper.getIFrameUrl(this.courseId);
this.iframeSrc = this.iframe.src;
this.iframe.onload = this.iframeOnLoad.bind(this);
this.iframe.style.width = '210px';
this.iframe.style.display = 'none';
document.getElementById('navigationPane').appendChild(this.iframe);
}
}
class BlackboardSearchManager {
constructor(pageUrl) {
this.courseDataObject = {};
this.linkItems = [];
this.fuse = new fuse_js__WEBPACK_IMPORTED_MODULE_2___default.a(this.linkItems, {
shouldSort: true,
tokenize: true,
matchAllTokens: true,
maxPatternLength: 32,
minMatchCharLength: 5,
keys: [
'text'
],
threshold: 0.3,
});
this.selectedRow = null;
this.coursesToUpdate = [];
this.uniHelper = new UniHelper(pageUrl);
this.pageCourseId = this.uniHelper.parseUrl(pageUrl);
this.parser = new BlackboardTreeParser(this.uniHelper);
this.config = new gm_config__WEBPACK_IMPORTED_MODULE_4___default.a();
/**
* @type {Object<string, Date>[]}
*/
this.weekDefinitions = [];
this.selectedCourses = [];
this.initialiseSettings();
this.checkCurrentCourse();
}
checkCurrentCourse() {
if (!this.pageCourseId)
return;
if (this.courseDataObject.hasOwnProperty(this.pageCourseId))
return;
let parsedCourse = this.uniHelper.parseCourse(
document.getElementById('courseMenu_link').textContent);
let codes = parsedCourse.courseCodeArray;
if (this.inSelectedCourses(codes)) {
console.log('adding current page');
this.queueUpdateCourse(this.pageCourseId);
return true;
}
console.log('not adding current page');
return false;
}
doSearch(event, force=false) {
if (!force && !$.featherlight.current()) return false;
while (this.searchResults.hasChildNodes()) {
this.searchResults.removeChild(this.searchResults.lastChild);
}
let query = this.searchBox.value.trim();
let selectedRowInResults = false;
if (!query) {
for (let i = 0; i < this.linkItems.length; i++) {
const item = this.linkItems[i];
// todo label to path
if (item.label[1] === 'Announcements') {
this.searchResults.appendChild(item.element);
$(item.element).fadeIn(200);
if (item.element === this.selectedRow)
selectedRowInResults = true;
}
}
if (!selectedRowInResults && this.searchResults.firstElementChild) {
this.selectRow(this.searchResults.firstElementChild);
}
} else {
let results = this.fuse.search(query);
for (let i = 0; i < Math.min(results.length, 50); i++) {
let r = results[i].element;
this.searchResults.appendChild(r);
$(r).hide().fadeIn(200);
if (r === this.selectedRow)
selectedRowInResults = true;
}
if (!selectedRowInResults && results.length) {
this.selectRow(results[0].element);
}
}
if (event) {
event.preventDefault();
}
return false;
}
selectPreviousRow() {
if (this.selectedRow.previousElementSibling)
this.selectRow(this.selectedRow.previousElementSibling);
else
this.selectRow(this.selectedRow.parentNode.lastElementChild);
}
selectNextRow() {
if (this.selectedRow.nextElementSibling)
this.selectRow(this.selectedRow.nextElementSibling);
else
this.selectRow(this.selectedRow.parentNode.firstElementChild);
}
selectRow(row) {
if (row) {
row.classList.add('search-selected');
row.firstElementChild.focus();
this.searchBox.focus();
}
if (this.selectedRow)
this.selectedRow.classList.remove('search-selected');
this.selectedRow = row;
}
searchKeyHandler(event) {
switch (event.which) {
case 38: // up
if (!this.selectedRow)
this.selectRow(this.searchResults.firstElementChild);
else
this.selectPreviousRow();
break;
case 40: // right
if (!this.selectedRow)
this.selectRow(this.searchResults.firstElementChild);
else
this.selectNextRow();
break;
case 13:
if (this.selectedRow)
window.open(this.selectedRow.firstElementChild.href, '_self');
break;
default: return; // exit this handler for other keys
}
event.preventDefault();
}
tickTime() {
this.timeSpan.textContent = new Date().toLocaleTimeString(
undefined, {hour: '2-digit', minute: '2-digit'});
if ($.featherlight.current()) {
setTimeout(this.tickTime.bind(this), 60000-Date.now()%60000);
}
}
tickDateAndCalendar() {
this.dateSpan.textContent = new Date().toLocaleDateString(undefined,
{
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
});
this.semesterSpan.textContent = '';
this.weekSpan.textContent = '';
let now = new Date();
let msPerWeek = 7*24*60*60*1000;
for (let d = 0; d < this.weekDefinitions.length; d++) {
const w = this.weekDefinitions[d];
if (now >= w.startDate && now < w.endDate) {
this.weekSpan.textContent = 'Week ' +
(Math.max(Math.floor((now-w.startMonday) / msPerWeek), 0) + w.startNum);
this.semesterSpan.textContent = w.name;
break;
}
}
if ($.featherlight.current()) {
let msPerDay = 24*60*60*1000;
setTimeout(this.tickDateAndCalendar.bind(this),
msPerDay-Date.now()%msPerDay);
}
}
refreshTimeElements() {
this.tickTime();
this.tickDateAndCalendar();
}
createElement(element, options) {
var el = document.createElement(element);
if (options)
lodash__WEBPACK_IMPORTED_MODULE_3___default.a.assign(el, options);
return el;
}
createWindow() {
this.searchWindow = document.createElement('div');
this.searchWindow.id = 'userscript-search-window';
this.header = document.createElement('div');
this.header.id = 'userscript-header';
this.dateTimeSpan = document.createElement('span');
this.dateTimeSpan.id = 'userscript-date-time';
this.timeSpan = document.createElement('span');
this.timeSpan.id = 'userscript-time';
this.dateSpan = document.createElement('span');
this.dateSpan.id = 'userscript-date';
this.dateTimeSpan.appendChild(this.timeSpan);
this.dateTimeSpan.appendChild(document.createElement('br'));
this.dateTimeSpan.appendChild(this.dateSpan);
this.tickTime();
this.header.appendChild(this.dateTimeSpan);
this.calendar = this.createElement('span', {
id: 'userscript-calendar'
});
this.weekSpan = this.createElement('span', {
id: 'userscript-week',
textContent: 'Week 4',
});
this.calendar.appendChild(this.weekSpan);
this.calendar.appendChild(this.createElement('br'));
this.semesterSpan = this.createElement('span', {
id: 'userscript-semester',
textContent: 'Semester 1, 2018',
});
this.calendar.appendChild(this.semesterSpan);
this.tickDateAndCalendar();
this.header.appendChild(this.calendar);
this.searchWindow.appendChild(this.header);
this.searchForm = document.createElement('form');
this.searchForm.id = 'userscript-search-form';
this.searchBox = document.createElement('input');
this.searchBox.id = 'userscript-search-input';
this.searchBox.type = 'search';
this.searchBox.name = 'search';
this.searchBox.setAttribute('autocomplete', 'off');
this.searchBox.tabIndex = 0;
this.searchBox.addEventListener('input',
lodash__WEBPACK_IMPORTED_MODULE_3___default.a.debounce(this.doSearch.bind(this), 200));
$(this.searchBox).keydown(
this.searchKeyHandler.bind(this));
this.searchForm.appendChild(this.searchBox);
this.searchButton = document.createElement('input');
this.searchButton.type = 'submit';
this.searchButton.style.display = 'none';
this.searchButton.onclick = lodash__WEBPACK_IMPORTED_MODULE_3___default.a.noop;
this.searchForm.appendChild(this.searchButton);
this.searchWindow.appendChild(this.searchForm);
this.searchResults = document.createElement('ul');
this.searchResults.id = 'userscript-search-results';
this.searchWindow.appendChild(this.searchResults);
this.footerDiv = document.createElement('div');
this.footerDiv.id = 'userscript-search-footer';
this.updateButton = document.createElement('span');
this.updateButton.id = 'userscript-update-button';
this.updateButton.textContent = 'Refresh';
this.updateButton.onclick = this.updateAllCourses.bind(this);
this.footerDiv.appendChild(this.updateButton);
this.footerDiv.appendChild(document.createTextNode(' | '));
this.settingsButton = document.createElement('span');
this.settingsButton.id = 'userscript-options-button';
this.settingsButton.textContent = 'Options';
this.settingsButton.onclick = this.showConfig.bind(this);
this.footerDiv.appendChild(this.settingsButton);
this.searchWindow.appendChild(this.footerDiv);
return this.searchWindow;
}
appendSearchForm(root) {
root.appendChild(this.searchWindow);
}
updateAllCourses() {
for (const courseId in this.courseDataObject) {
if (this.courseDataObject.hasOwnProperty(courseId)) {
this.queueUpdateCourse(courseId);
}
}
}
queueUpdateCourse(courseId) {
if (!this.parser.isUpdating()) {
this.parser.parseTree(courseId, this.parseTreeCallback.bind(this));
} else {
console.log('already updating');
this.coursesToUpdate.push(courseId);
}
}
getCourseObject(courseId) {
return this.courseDataObject[courseId];
}
maybeUpdateCourse(courseObject) {
let courseId = courseObject.courseId;
let updateInterval;
if (courseId === this.pageCourseId)
updateInterval = this.config.get('CurrentCourseUpdateInterval');
else
updateInterval = this.config.get('OtherCourseUpdateInterval');
if (Date.now() - courseObject.lastUpdated > updateInterval*60000) {
console.log('Updating '+courseId);
this.queueUpdateCourse(courseObject.courseId);
}
}
maybeUpdateAllCourses() {
for (const id in this.courseDataObject) {
if (this.courseDataObject.hasOwnProperty(id)) {
this.maybeUpdateCourse(this.courseDataObject[id]);
}
}
}
parseTreeCallback(treeData) {
if (treeData) {
console.log(JSON.stringify(treeData, undefined, 2));
let courseId = treeData.courseId;
this.courseDataObject[courseId] = treeData;
}
if (this.coursesToUpdate.length === 0) {
this.updateLinks();
this.storeCourseData();
} else {
let course = this.coursesToUpdate.pop();
this.queueUpdateCourse(course, true);
}
}
storeCourseData() {
this.config.set('CourseDataLZ',
lz_string__WEBPACK_IMPORTED_MODULE_5___default.a.compressToUTF16(JSON.stringify(this.courseDataObject)));
this.config.save();
}
formatLinkText(textArray, shorten=false) {
return textArray.join(' > ');
}
updateLinks() {
lodash__WEBPACK_IMPORTED_MODULE_3___default.a.remove(this.linkItems, lodash__WEBPACK_IMPORTED_MODULE_3___default.a.stubTrue);
for (const id in this.courseDataObject) {
if (this.courseDataObject.hasOwnProperty(id)) {
this.linkItems.push(...this.courseDataObject[id].items);
}
}
for (let i = 0; i < this.linkItems.length; i++) {
const item = this.linkItems[i];
let li = document.createElement('li');
let a = document.createElement('a');
a.href = item.link;
// todo label to path.
a.textContent = this.formatLinkText(item.label, true);
li.appendChild(a);
item.element = li;
item.text = this.formatLinkText(item.label);
item.courseCode = item.label[0];
}
return this.linkItems;
}
inCourseDataObject(courseCode) {
for (const id in this.courseDataObject) {
if (this.courseDataObject.hasOwnProperty(id)) {
const courseData = this.courseDataObject[id];
if (courseData.courseCodeArray.indexOf(courseCode) !== -1)
return true;
}
}
return false;
}
inSelectedCourses(courseCodeArray) {
if (!courseCodeArray) return false;
console.log(courseCodeArray);
console.log(this.selectedCourses);
for (let i = 0; i < courseCodeArray.length; i++) {
if (this.selectedCourses.indexOf(courseCodeArray[i]) !== -1)
return true;
}
return false;
}
// todo refactor this and config related window into another class.
updateSettings() {
// Parse week definitions text box into dates.
lodash__WEBPACK_IMPORTED_MODULE_3___default.a.remove(this.weekDefinitions, lodash__WEBPACK_IMPORTED_MODULE_3___default.a.stubTrue);
let weekLines = this.config.get('WeekDefinitions').split('\n');
let weekRegex = /^(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d+)?\s+(.+)$/;
for (let index = 0; index < weekLines.length; index++) {
let line = weekLines[index].trim();
try {
let w = weekRegex.exec(line);
if (!w) {
console.log('Invalid week definition: '+line);
continue;
}
let start = new Date(Number(w[1]), Number(w[2])-1, Number(w[3]), 0, 0, 0);
let end = new Date(Number(w[4]), Number(w[5])-1, Number(w[6]), 24, 0, 0);
// Relative Monday. For calculating week number.
// If 'start' falls on a weekend, startMonday will be
// the _following_ Monday. Otherwise, it is the preceding
// Monday. Clone date object.
let startMonday = new Date(start.getTime());
// Where 1 = Monday, ..., 7 = Sunday.
let startDayOfWeek = (startMonday.getDay()+6) % 7 + 1;
if (startDayOfWeek >= 6) {
startMonday.setDate(
startMonday.getDate() + 7 - startDayOfWeek + 1);
} else {
startMonday.setDate(
startMonday.getDate() - startDayOfWeek + 1);
}
let numStart;
if (!w[7])
numStart = 1;
else
numStart = Number(w[7]);
let text = w[8];
this.weekDefinitions.push({
startDate: start,
startMonday: startMonday,
endDate: end,
startNum: numStart,
name: text,
});
} catch (e) {
console.log('Invalid week definition: ' + line);
}
}
// Parse enabled courses into list.
lodash__WEBPACK_IMPORTED_MODULE_3___default.a.remove(this.selectedCourses);
let splitCourses = this.config.get('SelectedCourses').split('\n');
for (let i = 0; i < splitCourses.length; i++) {
const code = splitCourses[i];
if (code.trim()) {
this.selectedCourses.push(code);
}
}
// Delete courses no longer in list.
for (const courseId in this.courseDataObject) {
if (this.courseDataObject.hasOwnProperty(courseId)) {
if (!this.inSelectedCourses(
this.courseDataObject[courseId].courseCodeArray)) {
console.log('deleting: ' + courseId);
this.deleteCourse(courseId);
}
}
}
}
deleteCourse(idToDelete) {
let deletedItems = lodash__WEBPACK_IMPORTED_MODULE_3___default.a.remove(this.linkItems, function (item) {
return item.courseId === idToDelete;
});
for (let i = 0; i < deletedItems.length; i++) {
const item = deletedItems[i];
if (item.element.parentNode)
item.element.parentNode.removeChild(item.element);
}
delete this.courseDataObject[idToDelete];
this.storeCourseData();
}
initialiseSettings() {
this.config.init({
id: 'BlackboardSearchConfig',
title: 'Search Options',
fields: {
'CourseDataLZ': {
type: 'hidden',
default: lz_string__WEBPACK_IMPORTED_MODULE_5___default.a.compressToUTF16('{}')
},
'CurrentCourseUpdateInterval': {
label: 'Active update interval (minutes)',
title: 'How often to update when a course page is visited.',
type: 'unsigned float',
default: 120
},
'OtherCourseUpdateInterval': {
label: 'Background update interval (minutes)',
type: 'unsigned float',
default: 360
},
'SelectedCourses': {
label: 'Enabled courses',
type: 'textarea',
default: '',
},
'WeekDefinitions': {
label: 'Calendar',
type: 'textarea',
default: `2018-02-19 2018-04-01 1 Semester 1
2018-04-02 2018-04-15 1 Mid-semester Break — Semester 1
2018-04-16 2018-06-03 7 Semester 1
2018-06-04 2018-06-08 1 Revision Period — Semester 1
2018-06-09 2018-06-24 1 Examination Period — Semester 1
2018-07-23 2018-09-23 1 Semester 2
2018-09-24 2018-09-30 1 Mid-semester Break — Semester 2
2018-10-02 2018-10-28 10 Semester 2
2018-10-29 2018-11-02 1 Revision Period — Semester 2
2018-11-03 2018-11-18 1 Examination Period — Semester 2
2019-02-25 2019-04-21 1 Semester 1
2019-04-22 2019-04-28 1 Mid-semester Break — Semester 1
2019-04-29 2019-06-02 9 Semester 1
2019-06-03 2019-06-07 1 Revision Period — Semester 1
2019-06-08 2019-06-23 1 Examination Period — Semester 1
2019-07-22 2019-09-29 1 Semester 2
2019-09-30 2019-10-06 1 Mid-semester Break — Semester 2
2019-10-08 2019-10-27 11 Semester 2
2019-10-28 2019-11-01 1 Revision Period — Semester 2
2019-11-02 2019-11-17 1 Examination Period — Semester 2`,
}
},
events: {
save: this.updateSettings.bind(this),
},
css: configCss,
});
let courseData = JSON.parse(lz_string__WEBPACK_IMPORTED_MODULE_5___default.a.decompressFromUTF16(this.config.get('CourseDataLZ')));
lodash__WEBPACK_IMPORTED_MODULE_3___default.a.assign(this.courseDataObject, courseData);
this.updateLinks();
this.updateSettings();
}
showConfig() {
$.featherlight.current().close();
this.config.open();
}
}
console.log('Blackboard search starting.');
let searchManager = new BlackboardSearchManager(window.location.href);
searchManager.maybeUpdateAllCourses();
let searchWindow = searchManager.createWindow();
const SPACE = ' '.charCodeAt(0);
function keyboardShortcut(e) {
if (e.ctrlKey && e.keyCode === SPACE) {
if ($.featherlight.current()) return;
$.featherlight($(searchWindow), {
openSpeed: 50,
closeSpeed: 200,
persist: true,
beforeOpen: function() {
searchManager.refreshTimeElements();
searchManager.doSearch(undefined, true);
if (document.body.scrollHeight > document.body.clientHeight) {
document.body.style.paddingRight = '17px';
document.getElementsByClassName('global-nav-bar-wrap')[0].style.right = '17px';
}
},
afterOpen: function() {
let input = document.querySelector('#userscript-search-input');
input.focus();
input.select();
},
afterClose: function() {
document.body.style.paddingRight = '0px';
document.getElementsByClassName('global-nav-bar-wrap')[0].style.right = '0px';
}
});
}
}
document.addEventListener('keydown', keyboardShortcut, false);
}
let configCss = `
#BlackboardSearchConfig * {
font-family: 'Segoe UI', 'Helvetica';
}
body#BlackboardSearchConfig {
padding: 10px;
}
#BlackboardSearchConfig .config_var, #BlackboardSearchConfig .field_label {
font-size: 11pt;
height: 2em;
}
#BlackboardSearchConfig .config_var {
display: table;
width: 100%;
text-align: right;
}
#BlackboardSearchConfig .field_label {
display: table-cell;
width: 70%;
vertical-align: middle;
text-align: left;
font-weight: normal;
}
#BlackboardSearchConfig input[type="text"], #BlackboardSearchConfig textarea {
height: 2em;
width: 100%;
float: right;
padding-left: 2px;
}
#BlackboardSearchConfig textarea {
resize: vertical;
}
#BlackboardSearchConfig_resetLink {
}
#BlackboardSearchConfig button, #BlackboardSearchConfig .saveclose_buttons {
font-weight: normal;
font-size: 12pt;
border-width: 1px;
border-radius: 5px;
border-color: #272727;
border-style: solid;
padding: 3px 20px 3px 20px;
color: #272727;
background-color: transparent;
transition-property: background-color color;
transition-duration: 500ms;
}
button:focus {
outline: 0;
}
#BlackboardSearchConfig button:hover {
background-color: #272727;
color: white;
}
#BlackboardSearchConfig #BlackboardSearchConfig_closeBtn {
margin-right: 0px;
}
#BlackboardSearchConfig #BlackboardSearchConfig_WeekDefinitions_field_label {
vertical-align: top;
padding-top: 3px;
width: 30%;
}
#BlackboardSearchConfig #BlackboardSearchConfig_field_WeekDefinitions {
height: 9em;
font-family: monospace;
white-space: nowrap;
overflow-x: auto;
}
#BlackboardSearchConfig #BlackboardSearchConfig_SelectedCourses_field_label {
vertical-align: top;
padding-top: 3px;
}
#BlackboardSearchConfig #BlackboardSearchConfig_field_SelectedCourses {
height: 5.5em;
font-family: monospace;
}
`;
let mainCss = `
.featherlight-content {
border-bottom: 5px !important;
padding-bottom: 15px !important;
width: 50% !important;
}
#userscript-search-input {
padding-top: 3px;
padding-bottom: 3px;
padding-left: 3px;
margin-bottom: 1px;
}
ul#userscript-search-results {
list-style-type: none;
margin-top: 10px;
margin-bottom: 10px;
height: 19em;
margin: 0;
overflow-x: auto;
overflow-y: auto;
}
ul#userscript-search-results > li {
padding: 5px;
display:block;
text-overflow: ellipsis;
transition-property: background-color;
transition-duration: 100ms;
}
ul#userscript-search-results > li> a:hover {
text-decoration: underline;
}
li.search-selected {
background-color: #e7e7e7;
}
ul#userscript-search-results > li > a {
text-decoration: none;
}
#userscript-search-window {
font-size: 13pt;
font-weight: normal;
font-family: 'Segoe UI', 'Helvetica';
}
#userscript-search-input {
width: 100%;
}
#userscript-search-footer {
text-align: right;
color: grey;
font-size: 12pt;
}
#userscript-search-footer > span {
text-decoration: underline;
cursor: pointer;
}
iframe#BlackboardSearchConfig {
width: 35% !important;
min-width: 400px !important;
}
#userscript-header {
display: table;
text-align: left;
width: 100%;
margin-bottom: 8px;
}
#userscript-header > span {
display:table-cell;
}
#userscript-time, #userscript-week {
font-size: 20pt;
}
#userscript-calendar {
text-align: right;
}
`;
let style = document.createElement('style');
style.type = 'text/css';
style.id = 'userscript-search-style';
style.appendChild(document.createTextNode(mainCss));
document.head.appendChild(style);
let cssId = 'userscript-featherlight-css';
let link = document.createElement('link');
link.id = cssId;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/featherlight/1.7.13/featherlight.min.css';
link.media = 'all';
link.onload = BlackboardSearch;
document.head.appendChild(link);
/***/ }),
/* 1 */
/***/ (function(module, exports) {
module.exports = jQuery;
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = jQuery.featherlight;
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = Fuse;
/***/ }),
/* 4 */
/***/ (function(module, exports) {
module.exports = _;
/***/ }),
/* 5 */
/***/ (function(module, exports) {
module.exports = GM_configStruct;
/***/ }),
/* 6 */
/***/ (function(module, exports) {
module.exports = LZString;
/***/ })
/******/ ]);
//# sourceMappingURL=blackboard_search.user.js.map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment