// ==UserScript==
// @name CCSF Schedule Planner Helper
// @namespace https://github.com/ccsfsph/ccsf-schedule-planner-helper
// @version 0.4.2
// @description This userscript helps student to choose course more convenient, extenions: instructor email, instructor scores and rates from RMP for every table, and seats capacity in potential page table
// @author ccsfsph
// @match *://ccsf.collegescheduler.com/*
// @match *://*.ccsf.edu/*
// @icon data:image/x-icon;base64,AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAAAABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAO+uABvvrgCd764A9O+uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAPTvrgCd764AG++uAJ3vrgD8764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD8764Anu+uAPLvrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A8u+uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwD/768A/++vAP/vrwD/768A/++vAP/vrwD/768A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/768A/++sAP/tpgD/654A/+mYAP/olQD/6JQA/+iWAP/pmgD/66AA/+2nAP/vrQD/768A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/tpwD/6ZcA/+SHAP/ifQD/4HgA/+B3AP/gdgD/4HYA/+B3AP/gdwD/4XkA/+J+AP/liAD/6ZkA/+6pAP/vrwD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwD/7qoA/+iVAP/ifwD/4HcA/+B2AP/gdQD/33UA/991AP/gdQD/4HUA/991AP/fdQD/4HUA/+B1AP/gdgD/4HcA/+OCAP/qmwD/760A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++vAP/sowD/5IYA/+B3AP/gdQD/4HUA/+F8Cf/liyf/6Z5L/+yuaP/utnf/77d4/+60c//sqmH/6JtE/+SJI//hegf/4HUA/+B1AP/heQD/55AA/+6rAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/+ufAP/ifwD/4HUA/+B2Af/kiCH/7bFu//bZuf/88eb//vz5//////////////////////////7//vv3//vv4v/21rP/7Kxl/+OEGf/gdQD/4HcA/+aNAP/uqwD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwD/7KEA/+J+AP/fdQD/4oEU/+61dv/669r///79//////////////////////////////////////////////////////////////38//nkzv/rp1v/4XoH/+B3AP/nkwD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/tqAD/44MA/991AP/mkjX/99u8///+/P/////////////////////////////////////////////////////////////////////////////////++vX/8cKO/+J+EP/heQD/6p0A/++vAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/njwD/4HUA/+icR//669v///////////////////////////////////////////////////////////////////////////////////////////////////79//LImv/jgg7/6ZgA/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/768A/+yiAP/hegD/55c+//vt3v////////////////////////////////////////////////////////////////////////////////////////////////////////////78/P/1z4L/764E/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/+aNAP/jhiD/+OLK///////////////////////////////////////////////////+/v/+/Pf//vz1///9+//////////////////////////////////////////////////++vH/9MdP/++tAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/7agA/+OBBP/xxJL////////////////////////////////////////////++u//+eOp//XNZP/zw0P/88I+//THT//31Hr/++vB///9+P/////////////////////////////////++ez/9c5l/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwD/6p0A/+eXO//99Oz///////////////////////////////////////zw0P/zxUj/768G/++tAP/vrQD/760A/++tAP/vrQD/8LIP//XPav/99+f///////////////////77//vpuv/zwT7/768D/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwD/6JUB//LFlf///////////////////////////////////////PLX//K9M//vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/0yVf//vnu///////99uP/9tJ0//CzEv/vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrQD/6p0d//rq2v///////////////////////////////////fj/9cte/++tAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgP/99eD//rlrv/yvjP/760B/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/urAD/7rJL//76+P/////////////////////////////////77MT/8LMQ/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/8LEM//CxDP/vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/urAD/88l4///////////////////////////////////////31oD/760A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrQD/+NuX///////////////////////////////////9+//0xlH/760A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgL/+eOn//////////////////////////////////358//wtzv/760A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwL/+eOq//////////////////////////////////338v/sqDj/7qwA/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/+eCf//////////////////////////////////769//qoUX/7KIA/++vAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/7qkA/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrQD/99iG///////////////////////////////////////srmn/5YoA/++tAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++vAP/rnwD/44IA/+iWAP/uqwD/768A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrQD/9ctd///+/f/////////////////////////////////0zqb/4XoE/+eSAP/urAD/768A/++uAP/vrgD/764A/++uAP/vrwD/764A/+ufAP/ifwD/4HUA/+B4AP/liQD/7KQA/++vAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrQD/8rwu//746f/////////////////////////////////88uf/5pEz/+B2AP/liQD/658A/+6pAP/urAD/7qwA/+6qAP/sogD/55AA/+F8AP/geAX/5Icg/+B4BP/gdgD/4n4A/+mYAP/urAD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/77EJ//rntf//////////////////////////////////////9M+m/+F8Df/gdQD/4XkA/+OAAP/khQD/5IUA/+OBAP/hegD/4HYA/991AP/poE7/+unW/++5fv/igBP/33UA/+B4AP/mjQD/7qsA/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/760A//XLX////fv//////////////////////////////////vz6//HBjf/ifxH/33UA/+B1AP/gdgD/4HYA/+B1AP/fdQD/4HgE/+qhUP/77d7////////9/P/22br/55U6/+B3Af/mjgD/760A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A//CzE//76sD////////////////////////////////////////9+//22bn/6qNU/+SHH//ifxD/4n8Q/+OGHv/onEb/88mb//327////////////////////////PHm//C9df/vrhD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/zxUn//vnt/////////////////////////////////////////////vv5//vu3//548z/+eTN//vt3v/++vb////////////////////////////////////////+/P/546f/8LIP/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgP/99R6///9+v/////////////////////////////////////////////////////////////////////////////////////////////////////////////+/f/32Yj/768F/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/8LEL//jajv///fr///////////////////////////////////////////////////////////////////////////////////////////////////79//ngn//wshD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A//CxC//31X7//vrw/////////////////////////////////////////////////////////////////////////////////////////fj/+N2W//CzE//vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwT/9MZP//vtyf///vz////////////////////////////////////////////////////////////////////+//3z2v/1zmf/77AK/++tAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/760A//C1F//20Gz/++zE//778/////////////////////////////////////////////789v/88ND/99aA//G5Jf/vrQH/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrQD/8LMP//PBPv/20nX/+eCg//rouf/768L/++vB//rntf/5353/9tN3//PDRP/wtRX/760B/++tAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/vrQD/764B/++wB//wsQv/8LEK/++wBv/vrgH/760A/++tAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAPLvrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A8u+uAJ3vrgD8764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD8764Ane+uABvvrgCd764A9O+uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAPTvrgCd764AG4AAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAEAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAADvrgBx764A6u+uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgDq764Ace+uAOrvrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgDq764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/768A/++vAP/vrwD/768A/++vAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwD/764A/+6qAP/sowD/6p0A/+qbAP/qnQD/7KIA/+2oAP/vrgD/768A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/+ylAP/nkQD/44EA/+F5AP/gdgD/4HUA/+B2AP/geAD/4n4A/+aMAP/roQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++sAP/olgD/4XwA/+B2Af/igRT/5pI1/+mgTv/qpVb/6qFQ/+eWO//jhRz/4HcE/+F5AP/nkgD/7qwA/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/urAD/548A/+B3Af/lji3/8L+I//nlz//99/D//v37///+/f///fv//fjz//rr2//zy5//55g//+B4BP/njwD/760A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/+iTAP/hfAv/7bJw//vv4v/////////////////////////////////////////////////99u7/77uB/+J+Df/plwD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++vAP/roQD/4oEM//HDkP/+/Pn////////////////////////////////////////////////////////////+/fv/8sSO/+qbCf/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/760A/+aMAv/vuHz//v37/////////////////////////////vz3//768P/+/Pf////+///////////////////////+/fn/9c9o/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/tpQD/6Z09//zz6v///////////////////////vjo//jZiv/zw0T/8r4z//PDQ//31n7//fTd//////////////////335f/1zmX/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/768A/+ufBP/0zaH///////////////////////335v/0xkz/760B/++tAP/vrQD/760A/++tAP/yvjT//PHS///++//65a7/8r40/++uAf/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/7aol//vv4v//////////////////////99R6/++tAP/vrgD/764A/++uAP/vrgD/764A/++tAP/zxEb/9c5l//CyDf/vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/yvk///vz6//////////////////335f/xuin/760A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/760A//XPbf//////////////////////++vG//CxDf/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrQD/9tN2///////////////////////437v/7akH/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/20Gv///////////////////////jhyP/pmw7/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/+yiAP/uqQD/768A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/760A//TGTP///fn//////////////////PLn/+eTLP/qnAD/764A/++vAP/vrwD/768A/++uAP/qmwD/4XsA/+SEAP/roQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrQD/8bgi//303///////////////////////77p//+F7AP/nkAD/654A/+uhAP/qmwD/5owA/+F7BP/omEH/44Uc/+F6AP/olAD/7qsA/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwP/+d+b///////////////////////88+n/6qNU/+B3Av/gdgD/4HcA/+B1AP/geAb/66he//zz6v/44cf/6JtG/+J+A//sowD/768A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/yvzj//ffn///////////////////////99ez/8seX/+qkVf/pnUj/7Kph//TPp//9+PL////////////99O3/9MyE//CzE//vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAf/20nT///35//////////////////////////////38//78+f///v7////////////////////////////99uH/8r0w/++tAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++wCv/32If//vz3/////////////////////////////////////////////////////////////vjq//XKW//vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++wCP/1zmf//fTc//////////////////////////////////////////////79//zuy//0xUr/764C/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAf/xuib/99V9//vsxf/++Oj//vz1//789v/++vH//fXg//rot//2z2r/8LYZ/++tAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/vrQD/8LIO//G7Kf/zwT3/88JB//K/Nv/xuCD/77AI/++tAP/vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/760A/++tAP/vrQD/760A/++tAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgDp764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A6e+uAHHvrgDq764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAOrvrgBxgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAEoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA764A2++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrwD/768A/++uAP/vrgD/764A/++uAP/vrgD/764A2++uAP/vrgD/764A/++uAP/vrgD/768A/++tAP/tpgD/66EA/+yiAP/uqQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/+ykAP/mjgX/5o8g/+eWNf/nlDD/5YwU/+iWAf/urAD/764A/++uAP/vrgD/764A/++uAP/vrgD/768A/+yjAP/omCv/88qe//vu4P/9+PL//fbu//nl0P/tsm7/6ZkK/++tAP/vrgD/764A/++uAP/vrgD/764A/+6rAP/roy//+eXR/////////vz//vnt//789P///////vv7//TKe//vrQH/764A/++uAP/vrgD/764A/++uAP/tqQf/9tWo///////++vD/9tN3//K8LP/zwT7/+uay//303v/1zWH/768C/++uAP/vrgD/764A/++uAP/vrQD/8LYq//306P//////+eCe/++uBP/vrQD/760A//C2Gv/xuib/764B/++uAP/vrgD/764A/++uAP/vrgD/760A//PCQv/+/Pf///////TLa//vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/zwDn//vvy///////yxH3/7KQA/++uAP/urAD/6ZkA/+qcAP/vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/8LUW//zvz///////+eXO/+eWJ//mjAD/5owJ/+ytYv/rplT/6pwG/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/1z2n///34///////66NX/88ug//bYuf/++fX//fj0//TIYv/vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/77AJ//fVev/++Or///////////////////35//rns//xuib/760A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgT/8r81//fUef/43ZX/99mI//TIUv/wsxD/760A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++tAP/vrQD/760A/++tAP/vrQD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A2++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A/++uAP/vrgD/764A2wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
// @run-at document-start
// @license GPL-3.0
// @connect ratemyprofessors.com
// @connect www.ratemyprofessors.com
// @connect ccsf.edu
// @connect www.ccsf.edu
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function () {
'use strict';
// *********************************************************
// Welcome use CCSF Schedule Planner Helper
// all code is open source at: https://github.com/ccsfsph/ccsf-schedule-planner-helper
// document: http://ccsfsph.github.io/
// ccsf-schedule-planner-helper use `GPL-3.0` as open source license
// you should use code under the license
// *********************************************************
// TODO:
// 1. Remember to fix the switch scheule not work problem, since we need to listen the button event
// 2. when switch the page, we cannot get the status, that's why we use the setTimeout to listen the page refresh, but this will also cause another problem: when user change from saved schedule to current schuele, the page didn't redener again.
// NOTE:
// React
// we can get the detail date from `span aria-label="xx"`
// M - Monday
// W - Wednesday
const originFetch = fetch;
// https://juejin.cn/post/7135590843544502308
window.unsafeWindow.fetch = (url, options) => {
console.debug('unsafeWindow.fetch, url: ', url);
console.debug('unsafeWindow.fetch, options: ', options);
return originFetch(url, options).then(async (response) => {
console.debug('unsafeWindow.fetch, response ', response);
if (url.indexOf('/generate') > -1) {
console.debug('unsafeWindow.fetch, get the generate request success');
// why we have to clone the origin response, otherwise the page cannot continue????
const responseClone = response.clone();
console.debug('unsafeWindow.fetch, responseClone ', responseClone);
let res = await responseClone.json();
console.debug('unsafeWindow.fetch, res ', res);
localStorage.setItem('generate-request', JSON.stringify(res));
}
if (url.indexOf('/regblocks') > -1) {
console.debug('unsafeWindow.fetch, get the generate request success');
// why we have to clone the origin response, otherwise the page cannot continue????
const responseClone = response.clone();
console.debug('unsafeWindow.fetch, responseClone ', responseClone);
let res = await responseClone.json();
console.debug('unsafeWindow.fetch, res ', res);
localStorage.setItem('regblocks', JSON.stringify(res));
}
return response;
});
};
// tampermonkey documentation: https://www.tampermonkey.net/documentation.php
// code example: https://wiki.greasespot.net/GM.xmlHttpRequest
// ====================== base lib begin, load before business ======================
// ---------------------- localStorage support expire Lib begin ----------------------
// see: https://github.com/MrXujiang/xijs/blob/main/src/store/index.ts
// a seal for localStorage, a lib support the expire time for localStorage
// the origin version is ts, in js not support the dataType. I modify it
const store = {
//preId: 'xi-',
preId: '',
timeSign: '|-ts-|',
status: {
SUCCESS: 0,
FAILURE: 1,
OVERFLOW: 2,
TIMEOUT: 3,
},
storage: localStorage || window.localStorage,
getKey: function (key) {
return this.preId + key;
},
set: function (
key,
value,
time,
cb,
) {
let _status = this.status.SUCCESS,
_key = this.getKey(key),
_time;
// set the expire time, if not provided, the default expire time is: 7 days
try {
_time = time
? new Date(time).getTime() || time.getTime()
: new Date().getTime() + (1000 * 60 * 60 * 24 * 7);
} catch (e) {
_time = new Date().getTime() + (1000 * 60 * 60 * 24 * 7);
}
try {
this.storage.setItem(_key, _time + this.timeSign + value);
} catch (e) {
_status = this.status.OVERFLOW;
}
cb && cb.call(this, _status, _key, value);
},
get: function (
key,
cb,
) {
let status = this.status.SUCCESS,
_key = this.getKey(key),
value = null,
timeSignLen = this.timeSign.length,
that = this,
index,
time,
result;
try {
value = that.storage.getItem(_key);
} catch (e) {
result = {
status: that.status.FAILURE,
value: null,
};
cb && cb.call(this, result.status, result.value);
return result;
}
if (value) {
index = value.indexOf(that.timeSign);
time = +value.slice(0, index);
if (time > new Date().getTime() || time == 0) {
value = value.slice(index + timeSignLen);
} else {
(value = null), (status = that.status.TIMEOUT);
that.remove(key);
}
} else {
status = that.status.FAILURE;
}
result = {
status: status,
value: value,
};
cb && cb.call(this, result.status, result.value);
return result;
},
// delete from localStorage, if delete success, return the deleted content
remove: function (
key,
cb,
) {
let status = this.status.FAILURE,
_key = this.getKey(key),
value;
try {
value = this.storage.getItem(_key);
} catch (e) {
// dosomething
}
if (value) {
try {
this.storage.removeItem(_key);
status = this.status.SUCCESS;
} catch (e) {
// dosomething
}
}
cb &&
cb.call(
this,
status,
status > 0
? null
: value
? value.slice(value.indexOf(this.timeSign) + this.timeSign.length)
: null,
);
},
};
// Test code
// see https://github.com/MrXujiang/xijs/blob/main/src/store/index.md
// store the object
// let testStoreKey = 'A Test Key Name'
// let testStoreValue = 'ccsfsph'
// // store it first
// store.set(testStoreKey, testStoreValue, Date.now() + 1000);
// // get it instantly
// console.log('get key ' + testStoreKey + ' from store ', store.get(testStoreKey));
// setTimeout(() => {
// console.log('get the key later, ', store.get(testStoreKey));
// }, 1000);
// // callback after store success
// store.set('kkkk123213', 'https://github.com/MrXujiang/xijs/blob/main/src/store/index.md', Date.now() + 1000, (status, key, value) => {
// console.log('store success, status', status);
// console.log('store success, key', key);
// console.log('store success, value', value);
// });
// ---------------------- localStorage support expire Lib end ----------------------
// ====================== base lib end, load before business ======================
// ====================== global constants begin ======================
// use UPPER_CAMEL_CASE to identify this is a constant
// identify whether use https or not, true to use https, false to use http
const IS_USE_HTTPS = true;
// how many ms to detect whether the user change the page for every times
const pageRefreshInterval = 500;
const RMP_AUTHORIZATION_KEY = 'Basic dGVzdDp0ZXN0';
// ---------------------- constants for Schedule Plnnaer begin ----------------------
// NOTE: since the website use 'React' to compile, the name of css may change for every compile,
// may change the name of css every times when the website update
// NOTE: Since the site use 'React' to build, with the page update, those css name may change.
// So, we may need to update in the future
// for table
const TABLE_THEAD_CLASS_NAME = 'css-ri4373-headerCss';
const TABLE_HEAD_TH_CELL_CLASS_NAME = 'css-17qtf8g-hideOnMobileCss';
const TABLE_DATA_TD_CELL_CLASS_NAME = 'css-1p12g40-cellCss-hideOnMobileCss';
// for `Sections` page bar name
const TABLE_SWITCH_PAGE_BAR_CLASS_NAME = 'css-10ym66b-pagerCss';
// `/courses/xxx` page for advanced filters
const ADVANCED_FILTER_SAVE_BUTTON_CLASS_NAME = 'css-1m89de3-hoverStyles-hoverStyles-defaultStyle-centerBlockCss';
const ADVANCED_FILTER_CLOSE_BUTTON_CLASS_NAME = 'css-vqzzza-closeCss';
// ---------------------- constants for Schedule Plnnaer end ----------------------
// ====================== global constants end ======================
// ====================== global variables begin ======================
// use like g_userStatus to identify this is a global variable
var g_pageLoadFinish = false;
// ---------------------- variables for Schedule Plnnaer begin ----------------------
var href = '';
var PAGE;
// identify user in the chosen schedule page, build scheule page: Current Schedule / Register by CRN
const PAGE_CURRENT_SCHEDULE = 'PAGE_CURRENT_SCHEDULE';
// identify user is choosing the course, viewing the potential / possible schedule
const PAGE_POTENTIAL_SCHEDULE = 'PAGE_POTENTIAL_SCHEDULE';
// page contains /courses
const PAGE_COURSE_SCHEDULE = 'PAGE_COURSE_SCHEDULE';
const PAGE_UNKNOWN = 'PAGE_UNKNOWN';
var g_instructorColumnIndex = 0;
var g_seatsCapacityColumnIndex = 0;
var g_instructorEmailColumnIndex = 0;
var g_isUpdateLocationChangeFinish = false;
var g_isSwitchSheduleUpdateFinish = true;
var g_isSwitchCoursePageFinish = true;
var g_tableHeadTotalCell = -1;
var g_tableHeadCrnIndex = -1;
var g_tableHeadSeatsOpenIndex = -1;
// it seems the page use lazy load, which will cause the table didn't render soon, and cause not find the element.
var g_isFirstLoadSuccess = false;
var g_initCoursePageEventFlag = false;
// ---------------------- variables for Schedule Plnnaer end ----------------------
// ====================== global variables end ======================
// ====================== global functions begin ======================
// ---------------------- base function begin ----------------------
function getRequestURLProtocol() {
return IS_USE_HTTPS ? 'https://' : 'http://';
}
function getEmailHTML(email) {
// <a href="mailto:[email protected]">[email protected]</a>
return '<a href="mailto:' + email + '">' + email + '</a>';
}
// whther or not the user change location, return true if the location change, return false if the location doesn't change
function isLocationChange() {
updateGlobalPageByHref();
console.debug("isLocationChange, href", href);
console.debug("isLocationChange, location.href ", location.href);
let flag = href !== location.href;
console.debug("isLocationChange, flag ", flag);
href = location.href;
if (flag) {
g_isUpdateLocationChangeFinish = false;
console.debug("isLocationChange, location change");
}
if (flag && PAGE === PAGE_COURSE_SCHEDULE) {
console.debug("isLocationChange, flag && PAGE === PAGE_COURSE_SCHEDULE")
g_initCoursePageEventFlag = false;
}
return flag;
}
function updateGlobalPageByHref() {
console.debug("invoke updateGlobalPageByHref...");
let userDisplayURL = location.href;
// https://ccsf.collegescheduler.com/terms/Spring%202023/options
// https://ccsf.collegescheduler.com/
// NOTE: ignore `/cart/edit` page, don't use indexOf
if (userDisplayURL.indexOf('/options') !== -1 || userDisplayURL.indexOf('/currentschedule') !== -1 || userDisplayURL.endsWith('/cart') || userDisplayURL.endsWith('ccsf.collegescheduler.com/') || userDisplayURL.endsWith('ccsf.collegescheduler.com')) {
PAGE = PAGE_CURRENT_SCHEDULE;
}
// https://ccsf.collegescheduler.com/terms/Spring%202023/courses/1581937
else if (userDisplayURL.indexOf('/courses') !== -1) {
PAGE = PAGE_COURSE_SCHEDULE;
}
// https://ccsf.collegescheduler.com/terms/Spring%202023/schedules/xxxxx
else if (userDisplayURL.indexOf('/schedules/') !== -1) {
PAGE = PAGE_POTENTIAL_SCHEDULE;
}
// didn't match
else {
PAGE = PAGE_UNKNOWN;
}
console.debug("updateGlobalPageByHref, PAGE ", PAGE)
}
function isTablePage() {
return PAGE === PAGE_POTENTIAL_SCHEDULE || PAGE === PAGE_CURRENT_SCHEDULE || PAGE === PAGE_COURSE_SCHEDULE;
}
function updateUserSwitchPotentialSchedulePage() {
let locationChangeFlag = isLocationChange();
console.debug("updateUserSwitchPotentialSchedulePage, locationChangeFlag ", locationChangeFlag);
if (locationChangeFlag && PAGE === PAGE_POTENTIAL_SCHEDULE) {
console.debug("updateUserSwitchPotentialSchedulePage, locationChangeFlag && PAGE === PAGE_POTENTIAL_SCHEDULE")
g_isSwitchSheduleUpdateFinish = false;
}
if (locationChangeFlag && PAGE === PAGE_COURSE_SCHEDULE) {
console.debug("updateUserSwitchPotentialSchedulePage, locationChangeFlag && PAGE === PAGE_COURSE_SCHEDULE")
g_isSwitchCoursePageFinish = false;
}
}
/**
* Convert professor's name from format like `Firstname Lastname` to `firstname-lastname`
*
* @param {String} professorName
* @returns reversed name
*/
function reverseProfessorName(professorName) {
console.debug('reverseProfessorName, professorName', professorName);
if (!professorName) {
console.warn('reverseProfessorName, professorName is blank!!!')
return;
}
// NOTE: New version use blank space for name instead, however, in the past, the name use `,` to split the name
let separator = ' ';
if (!professorName.includes(separator)) {
console.warn(`reverseProfessorName, professorName no ' ' space in name ${professorName} try to look for ',' instead`);
separator = ',';
if (!professorName.includes(separator)) {
console.error(`reverseProfessorName, professorName neither ' ' nor ',' in name ${professorName}`);
return;
}
}
let professorNameArray = professorName.split(separator);
console.debug("reverseProfessorName, professorNameArray ", professorNameArray)
if (separator === ' ') {
let firstName = professorNameArray[0].trim().toLowerCase().replace(" ", "-");
console.debug(`reverseProfessorName, professorName: ${professorName}, firstName: ${firstName}`)
let lastName = professorNameArray[1].trim().toLowerCase().replace(" ", "-");
console.debug(`reverseProfessorName, professorName: ${professorName}, lastName: ${lastName}`)
let conversedProfessorName = firstName + '-' + lastName
console.debug(`reverseProfessorName, professorName: ${professorName}, conversedProfessorName: ${conversedProfessorName}`)
return conversedProfessorName;
} else {
// there are some exceptions, like `Collins Rawle, Shelly`, we should remove the blank symbol, and add an hypen `-`
let lastName = professorNameArray[0].trim().toLowerCase().replace(" ", "-");
console.debug(`reverseProfessorName, professorName: ${professorName}, lastName: ${lastName}`)
let firstName = professorNameArray[1].trim().toLowerCase().replace(" ", "-");
console.debug(`reverseProfessorName, professorName: ${professorName}, firstName: ${firstName}`)
let conversedProfessorName = firstName + '-' + lastName
console.debug(`reverseProfessorName, professorName: ${professorName}, conversedProfessorName: ${conversedProfessorName}`)
return conversedProfessorName;
}
}
// ---------------------- base function end ----------------------
// ---------------------- Third-party API function begin ----------------------
// ********************** CCSF API function begin **********************
function getCCSFTeacherInfoURL(professorName) {
// In Schedule Planner Page, it will display like `Conner, Constance`
// however, the API need to get the format like 'constance-conner'
return getRequestURLProtocol() + 'www.ccsf.edu/directory/' + reverseProfessorName(professorName);
}
// we found some information from third-party may be incorrect.
// besides, we can also collect some useful information from here, not query form the third-part to make it faster
const CCSF_INSTRUCTOR_EMAIL = {
// origin is form https://www.ccsf.edu/directory/erlinda-legaspi but it's incorrect. we found a new one on homepage: https://sites.google.com/mail.ccsf.edu/legaspi-english/home
'Legaspi, Erlinda': '[email protected]',
'Nguyen, Sean': '[email protected]',
'Potter, Jonathan': '[email protected]',
}
function getCCSFTeacherInfo(professorName, professorCellElement) {
console.debug("getCCSFTeacherInfo, professorName ", professorName)
// In most cases, the teacher's email will not change. We should cache it permantly
console.debug('getCCSFTeacherInfo, try to get from constant, ', CCSF_INSTRUCTOR_EMAIL[professorName]);
let cacheKeyName = professorName + '/email';
let cacheInstrucotEmail = CCSF_INSTRUCTOR_EMAIL[professorName] || localStorage.getItem(cacheKeyName)
if (cacheInstrucotEmail) {
professorCellElement.innerHTML = getEmailHTML(cacheInstrucotEmail);
return;
}
let teacherInfoURL = getCCSFTeacherInfoURL(professorName);
console.debug('getCCSFTeacherInfo, teacherInfoURL ', teacherInfoURL);
GM_xmlhttpRequest({
method: "GET",
url: teacherInfoURL,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56',
'Accept': 'text/html',
},
onload: function (response) {
console.debug('getCCSFTeacherInfo, response ', response);
let htmlText = response.responseText;
console.debug('getCCSFTeacherInfo, htmlText ', htmlText);
if (!htmlText) {
return;
}
/**
* the example of the html template
*
* <div class="field field--name-field-email field--type-email field--label-above">
<div class="field__label">Email</div>
<div class="field__item"><a href="mailto:[email protected]">[email protected]</a></div>
</div>
well, how can get the `<a href="mailto:[email protected]">[email protected]</a>`
*/
// TODO one day, I will use the regex to do it, haha
let emailTextSymbol = '<div class="field__label">Email</div>';
let emailTextSymbolLength = emailTextSymbol.length;
console.debug('getCCSFTeacherInfo, emailTextSymbolLength ', emailTextSymbolLength);
let emailBeginIndex = htmlText.indexOf(emailTextSymbol);
console.debug('getCCSFTeacherInfo, emailBeginIndex ', emailBeginIndex);
let beginFindIndex = emailBeginIndex + emailTextSymbolLength - 1;
console.debug('getCCSFTeacherInfo, beginFindIndex ', beginFindIndex);
let emailEndIndex = htmlText.indexOf('</div>', beginFindIndex);
console.debug('getCCSFTeacherInfo, emailEndIndex ', emailEndIndex);
let emailHTMLTextLength = emailEndIndex - emailBeginIndex;
console.debug('getCCSFTeacherInfo, emailHTMLTextLength ', emailHTMLTextLength);
let emailHTMLText = htmlText.substr(emailBeginIndex, emailHTMLTextLength)
console.debug('getCCSFTeacherInfo, emailHTMLText ', emailHTMLText);
// now, I get `<div class=\"field__label\">Email</div>\n <div class=\"field__item\"><a href=\"mailto:[email protected]\">[email protected]</a>`
// I need tot get [email protected]
let emailHTMLText2 = emailHTMLText.replace('<div class=\"field__label\">Email</div>\n <div class=\"field__item\">', "");
// <a href="mailto:[email protected]">[email protected]</a>
console.debug('getCCSFTeacherInfo, emailHTMLText2 ', emailHTMLText2);
// see https://blog.csdn.net/zzti_erlie/article/details/89842391
let emailRegex = /">(.*)<\/a>/;
let email = emailRegex.exec(emailHTMLText2)[1].trim();
console.debug('getCCSFTeacherInfo, email ', email);
professorCellElement.innerHTML = getEmailHTML(email);
localStorage.setItem(cacheKeyName, email)
}
});
}
// getCCSFTeacherInfo('Conner, Constance')
// getCCSFTeacherInfo('Legaspi, Erlinda')
// getCCSFTeacherInfo('potter, jonathan')
// ********************** CCSF API function end **********************
// ********************** RMP (Rate My Professors) API function end **********************
// ********************** RMP (Rate My Professors) API function begin **********************
function getProfessorsURL(legacyId) {
return getRequestURLProtocol() + 'www.ratemyprofessors.com/professor?tid=' + legacyId;
}
function getRMPSearchProfessorAPI() {
return getRequestURLProtocol() + 'www.ratemyprofessors.com/graphql';
}
function buildRMPSearchProfessorQuery(professorName) {
return '{"query":"query NewSearchTeachersQuery( $query: TeacherSearchQuery!) { newSearch { teachers(query: $query) { didFallback edges { cursor node { id legacyId firstName lastName school { name id } department } } } }}","variables":{"query":{"text":"' + professorName + '","schoolID":"U2Nob29sLTE4NTA="}}}';
}
function buildRMPGetProfessorDetailQuery(id) {
return '{"query":"query TeacherRatingsPageQuery( $id: ID!) { node(id: $id) { __typename ... on Teacher { id legacyId firstName lastName school { legacyId name id } lockStatus ...StickyHeader_teacher ...RatingDistributionWrapper_teacher ...TeacherMetaInfo_teacher ...TeacherInfo_teacher ...SimilarProfessors_teacher ...TeacherRatingTabs_teacher } id }}fragment StickyHeader_teacher on Teacher { ...HeaderDescription_teacher ...HeaderRateButton_teacher}fragment RatingDistributionWrapper_teacher on Teacher { ...NoRatingsArea_teacher ratingsDistribution { total ...RatingDistributionChart_ratingsDistribution }}fragment TeacherMetaInfo_teacher on Teacher { legacyId firstName lastName department school { name city state id }}fragment TeacherInfo_teacher on Teacher { id lastName numRatings ...RatingValue_teacher ...NameTitle_teacher ...TeacherTags_teacher ...NameLink_teacher ...TeacherFeedback_teacher ...RateTeacherLink_teacher}fragment SimilarProfessors_teacher on Teacher { department relatedTeachers { legacyId ...SimilarProfessorListItem_teacher id }}fragment TeacherRatingTabs_teacher on Teacher { numRatings courseCodes { courseName courseCount } ...RatingsList_teacher ...RatingsFilter_teacher}fragment RatingsList_teacher on Teacher { id legacyId lastName numRatings school { id legacyId name city state avgRating numRatings } ...Rating_teacher ...NoRatingsArea_teacher ratings(first: 20) { edges { cursor node { ...Rating_rating id __typename } } pageInfo { hasNextPage endCursor } }}fragment RatingsFilter_teacher on Teacher { courseCodes { courseCount courseName }}fragment Rating_teacher on Teacher { ...RatingFooter_teacher ...RatingSuperHeader_teacher ...ProfessorNoteSection_teacher}fragment NoRatingsArea_teacher on Teacher { lastName ...RateTeacherLink_teacher}fragment Rating_rating on Rating { comment flagStatus createdByUser teacherNote { id } ...RatingHeader_rating ...RatingSuperHeader_rating ...RatingValues_rating ...CourseMeta_rating ...RatingTags_rating ...RatingFooter_rating ...ProfessorNoteSection_rating}fragment RatingHeader_rating on Rating { date class helpfulRating clarityRating isForOnlineClass}fragment RatingSuperHeader_rating on Rating { legacyId}fragment RatingValues_rating on Rating { helpfulRating clarityRating difficultyRating}fragment CourseMeta_rating on Rating { attendanceMandatory wouldTakeAgain grade textbookUse isForOnlineClass isForCredit}fragment RatingTags_rating on Rating { ratingTags}fragment RatingFooter_rating on Rating { id comment adminReviewedAt flagStatus legacyId thumbsUpTotal thumbsDownTotal thumbs { userId thumbsUp thumbsDown id } teacherNote { id }}fragment ProfessorNoteSection_rating on Rating { teacherNote { ...ProfessorNote_note id } ...ProfessorNoteEditor_rating}fragment ProfessorNote_note on TeacherNotes { comment ...ProfessorNoteHeader_note ...ProfessorNoteFooter_note}fragment ProfessorNoteEditor_rating on Rating { id legacyId class teacherNote { id teacherId comment }}fragment ProfessorNoteHeader_note on TeacherNotes { createdAt updatedAt}fragment ProfessorNoteFooter_note on TeacherNotes { legacyId flagStatus}fragment RateTeacherLink_teacher on Teacher { legacyId numRatings lockStatus}fragment RatingFooter_teacher on Teacher { id legacyId lockStatus isProfCurrentUser}fragment RatingSuperHeader_teacher on Teacher { firstName lastName legacyId school { name id }}fragment ProfessorNoteSection_teacher on Teacher { ...ProfessorNote_teacher ...ProfessorNoteEditor_teacher}fragment ProfessorNote_teacher on Teacher { ...ProfessorNoteHeader_teacher ...ProfessorNoteFooter_teacher}fragment ProfessorNoteEditor_teacher on Teacher { id}fragment ProfessorNoteHeader_teacher on Teacher { lastName}fragment ProfessorNoteFooter_teacher on Teacher { legacyId isProfCurrentUser}fragment SimilarProfessorListItem_teacher on RelatedTeacher { legacyId firstName lastName avgRating}fragment RatingValue_teacher on Teacher { avgRating numRatings ...NumRatingsLink_teacher}fragment NameTitle_teacher on Teacher { id firstName lastName department school { legacyId name id } ...TeacherDepartment_teacher ...TeacherBookmark_teacher}fragment TeacherTags_teacher on Teacher { lastName teacherRatingTags { legacyId tagCount tagName id }}fragment NameLink_teacher on Teacher { isProfCurrentUser id legacyId firstName lastName school { name id }}fragment TeacherFeedback_teacher on Teacher { numRatings avgDifficulty wouldTakeAgainPercent}fragment TeacherDepartment_teacher on Teacher { department departmentId school { legacyId name id }}fragment TeacherBookmark_teacher on Teacher { id isSaved}fragment NumRatingsLink_teacher on Teacher { numRatings ...RateTeacherLink_teacher}fragment RatingDistributionChart_ratingsDistribution on ratingsDistribution { r1 r2 r3 r4 r5}fragment HeaderDescription_teacher on Teacher { id firstName lastName department school { legacyId name city state id } ...TeacherTitles_teacher ...TeacherBookmark_teacher}fragment HeaderRateButton_teacher on Teacher { ...RateTeacherLink_teacher}fragment TeacherTitles_teacher on Teacher { department school { legacyId name id }}","variables":{"id":"' + id + '"}}';
}
// set extParams a default value, otherwise it will display undefined
function setInstructorElement(professorName, changeHerfElement, url, extParams = '') {
console.debug('setInstructorElement, professorName', professorName);
console.debug('setInstructorElement, changeHerfElement', changeHerfElement);
console.debug('setInstructorElement, extParams', extParams);
if (changeHerfElement) {
// remove the origin innerHTML
changeHerfElement.innerHTML = '';
console.debug('searchProfessorByRMP, remove the origin innerHTML');
let aElement = document.createElement('a');
aElement.setAttribute('href', url);
aElement.setAttribute('target', '_blank');
// NOTE: DONT use innerText here, because I want to contain the html characters in extParams
aElement.innerHTML = professorName + extParams;
changeHerfElement.appendChild(aElement);
}
}
function getProfessRateShowFormat(avgRating, numRatings, avgDifficulty) {
console.debug(`getProfessRateShowFormat, avgRating ${avgRating}, numRatings ${numRatings}, avgDifficulty ${avgDifficulty}`)
// TODO we should also use the numRatings, not only the avgRating
// >= 4.8: orange
// 4.0 - 4.7: purple
// 3.0 - 3.9: blue
// 2.0 - 2.9: green
// < 2.0: grey (don't user white, since the in the page cannot see)
if (avgRating >= 4.8) {
avgRating = '<span style="color: orange;">' + avgRating + '</span>';
} else if (avgRating >= 4.0) {
avgRating = '<span style="color: purple;">' + avgRating + '</span>';
} else if (avgRating >= 3.0) {
avgRating = '<span style="color: blue;">' + avgRating + '</span>';
} else if (avgRating >= 2.0) {
avgRating = '<span style="color: green;">' + avgRating + '</span>';
} else {
avgRating = '<span style="color: grey;">' + avgRating + '</span>';
}
let showScores = avgRating + ' / 5';
if (numRatings <= 0) {
showScores = 'N/A';
}
if (avgDifficulty <= 0) {
avgDifficulty = 'N/A';
}
let htmlFormat = '<br>' + showScores + '<br>' + numRatings + ' Ratings' + '<br>' + avgDifficulty + ' Difficulty';
console.debug("getProfessRateShowFormat, htmlFormat ", htmlFormat)
return htmlFormat;
}
function searchProfessorDataHandler(teacher, professorName, changeHerfElement, requestURL) {
let teacherProperty = teacher.node;
console.debug('searchProfessorByRMP, teacherProperty ', teacherProperty);
// legacyId: 2445727
let legacyId = teacherProperty.legacyId;
console.debug('searchProfessorByRMP, legacyId ', legacyId);
// department: "Computer amp Informational Tech."
let department = teacherProperty.department;
console.debug('searchProfessorByRMP, department ', department);
// firstName: "Jonathan"
let firstName = teacherProperty.firstName;
console.debug('searchProfessorByRMP, firstName ', firstName);
// lastName: "Potter"
let lastName = teacherProperty.lastName;
console.debug('searchProfessorByRMP, lastName ', lastName);
// id: "VGVhY2hlci0yNDQ1NzI3"
let id = teacherProperty.id;
console.debug('searchProfessorByRMP, id ', id);
let professionPageURL = getProfessorsURL(legacyId);
console.debug('searchProfessorByRMP, professionPageURL ', professionPageURL);
localStorage.setItem(professorName, JSON.stringify(teacherProperty));
setInstructorElement(professorName, changeHerfElement, professionPageURL);
// use the id to get the professor detail
// TODO get from cache first, maybe have cache from local
let searchProfessorDetailData = buildRMPGetProfessorDetailQuery(id);
GM_xmlhttpRequest({
method: "POST",
url: requestURL,
headers: {
'Authorization': RMP_AUTHORIZATION_KEY,
'Content-Type': 'text/plain',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56',
'Accept': 'application/json',
},
data: searchProfessorDetailData,
onload: function (response) {
console.debug('searchProfessorByRMP, professorDetail, response ', response);
let jsonText = response.responseText;
console.debug('searchProfessorByRMP, professorDetail, jsonText ', jsonText);
let dataObj = JSON.parse(jsonText);
console.debug('searchProfessorByRMP, professorDetail, dataObj ', dataObj);
let professorDetail = dataObj.data.node;
console.debug('searchProfessorByRMP, professorDetail ', professorDetail);
if (!professorDetail) {
console.debug('searchProfessorByRMP, professorDetail is null ', professorDetail);
return;
}
// avgRating: 2.3
let avgRating = professorDetail.avgRating;
console.debug('searchProfessorByRMP, professorDetail, avgRating', avgRating);
// avgDifficulty: 2
let avgDifficulty = professorDetail.avgDifficulty;
console.debug('searchProfessorByRMP, professorDetail, avgDifficulty', avgDifficulty);
// numRatings: 4
let numRatings = professorDetail.numRatings;
console.debug('searchProfessorByRMP, professorDetail, numRatings', numRatings);
store.set(id, jsonText);
setInstructorElement(professorName, changeHerfElement, professionPageURL, getProfessRateShowFormat(avgRating, numRatings, avgDifficulty));
}
})
}
/**
* NOTE: Some professors' name is different from their name on RMP or they have multi name on RMP.
*
* e.g. Luttrell, Maximilian have 'Max Luttrell' and 'Maximilian Luttrell'. But 'Max Luttrell' is a detailed one. So we should use this one.
*/
const RMP_PROFESSOR_NAME_REFLECTION = {
// 'Name On Schedule Planner': 'Name On RMP',
'Luttrell, Maximilian': 'Max Luttrell',
'Maximilian Luttrell': 'Max Luttrell',
'Bacsierra, Benjamin': 'B Bacsierra',
'Benjamin Bacsierra': 'B Bacsierra',
'Cannon, Joseph': 'Joe Cannon',
'Joseph Cannon': 'Joe Cannon',
'Sam Johnson': 'Samuel Johnson',
'Sean Laughlin': 'Sean M Laughlin',
'Sheri Miraglia': 'Sheri J Miraglia',
'David Palaita': 'David (vika) Palaita',
'Shelly Collins Rawle': 'Shelly-Ann Collins Rawle',
'Samuel Bowne': 'Sam Bowne',
'Mike McKeever': 'M McKeever',
'Dan Smith': 'Daniel Smith',
'Jill Evans': 'Jill Evans (Johnsen)',
}
function searchProfessorByRMP(professorName, changeHerfElement) {
console.debug('invoke searchProfessorByRMP');
console.debug('searchProfessorByRMP, professorName', professorName);
console.debug('searchProfessorByRMP, changeHerfElement', changeHerfElement);
let requestURL = getRMPSearchProfessorAPI();
console.debug('searchProfessorByRMP, requestURL', requestURL);
let localStorageProfessorPropertyJSON = localStorage.getItem(professorName);
console.debug('searchProfessorByRMP, localStorageProfessorPropertyJSON', localStorageProfessorPropertyJSON);
if (localStorageProfessorPropertyJSON) {
console.debug('searchProfessorByRMP, nice, found the data from localStorage, not invoke from third-party!');
let localStorageProfessorProperty = JSON.parse(localStorageProfessorPropertyJSON);
console.debug('searchProfessorByRMP, localStorageProfessorProperty', localStorageProfessorProperty);
let url = getProfessorsURL(localStorageProfessorProperty.legacyId);
console.debug('searchProfessorByRMP, url', url)
setInstructorElement(professorName, changeHerfElement, url);
// add data
let id = localStorageProfessorProperty.id;
console.debug('searchProfessorByRMP, id', id);
// detail may expire, if expire, get from api
let storeProfessorDetail = store.get(id);
console.debug('searchProfessorByRMP, storeProfessorDetail', storeProfessorDetail);
if (!storeProfessorDetail || !storeProfessorDetail.value) {
console.debug('searchProfessorByRMP, !storeProfessorDetail || !storeProfessorDetail.value')
let searchProfessorDetailData = buildRMPGetProfessorDetailQuery(id);
GM_xmlhttpRequest({
method: "POST",
url: requestURL,
headers: {
'Authorization': RMP_AUTHORIZATION_KEY,
'Content-Type': 'text/plain',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56',
'Accept': 'application/json',
},
data: searchProfessorDetailData,
onload: function (response) {
console.debug('searchProfessorByRMP, professorDetail, response', response);
let jsonText = response.responseText;
console.debug('searchProfessorByRMP, professorDetail, jsonText', jsonText);
let dataObj = JSON.parse(jsonText);
console.debug('searchProfessorByRMP, professorDetail, dataObj', dataObj);
let professorDetail = dataObj.data.node;
console.debug('searchProfessorByRMP, professorDetail', professorDetail);
if (!professorDetail) {
console.debug('searchProfessorByRMP, professorDetail is null', professorDetail);
return;
}
// avgRating: 2.3
let avgRating = professorDetail.avgRating;
console.debug('searchProfessorByRMP, professorDetail, avgRating', avgRating);
// avgDifficulty: 2
let avgDifficulty = professorDetail.avgDifficulty;
console.debug('searchProfessorByRMP, professorDetail, avgDifficulty', avgDifficulty);
// numRatings: 4
let numRatings = professorDetail.numRatings;
console.debug('searchProfessorByRMP, professorDetail, numRatings', numRatings);
store.set(id, jsonText);
setInstructorElement(professorName, changeHerfElement, url, getProfessRateShowFormat(avgRating, numRatings, avgDifficulty));
}
});
return;
} else {
let localStorageProfessorDetailJSON = storeProfessorDetail.value;
console.debug('searchProfessorByRMP, localStorageProfessorDetailJSON', localStorageProfessorDetailJSON);
let localStorageProfessorDetail = JSON.parse(localStorageProfessorDetailJSON);
console.debug('searchProfessorByRMP, localStorageProfessorDetail', localStorageProfessorDetail);
let avgRating = localStorageProfessorDetail.data.node.avgRating;
console.debug('searchProfessorByRMP, avgRating', avgRating);
let avgDifficulty = localStorageProfessorDetail.data.node.avgDifficulty;
console.debug('searchProfessorByRMP, avgDifficulty', avgDifficulty);
let numRatings = localStorageProfessorDetail.data.node.numRatings;
console.debug('searchProfessorByRMP, numRatings', numRatings);
setInstructorElement(professorName, changeHerfElement, url, getProfessRateShowFormat(avgRating, numRatings, avgDifficulty));
return;
}
}
let searchProfessorName = RMP_PROFESSOR_NAME_REFLECTION[professorName] || professorName;
console.debug('searchProfessorByRMP, professorName', professorName)
console.debug('searchProfessorByRMP, RMP_PROFESSOR_NAME_REFLECTION[professorName]', RMP_PROFESSOR_NAME_REFLECTION[professorName])
console.debug('searchProfessorByRMP, searchProfessorName', searchProfessorName)
let ignoreMultiResult = false;
if (RMP_PROFESSOR_NAME_REFLECTION[professorName]) {
ignoreMultiResult = true
}
let requestData = buildRMPSearchProfessorQuery(searchProfessorName);
console.debug('searchProfessorByRMP, requestData', requestData);
GM_xmlhttpRequest({
method: "POST",
url: requestURL,
headers: {
'Authorization': RMP_AUTHORIZATION_KEY,
'Content-Type': 'text/plain',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56',
'Accept': 'application/json',
},
data: requestData,
onload: function (response) {
console.debug('searchProfessorByRMP, response', response);
let jsonText = response.responseText;
console.debug('searchProfessorByRMP, jsonText', jsonText);
let dataObj = JSON.parse(jsonText);
console.debug('searchProfessorByRMP, dataObj', dataObj);
let teachers = dataObj.data.newSearch.teachers.edges;
console.debug('searchProfessorByRMP, teachers', teachers);
let teacher;
if (teachers.length > 1) {
console.warn('searchProfessorByRMP, teachers.length > 1, teachers', teachers);
let sameNameProfessors = [];
for (let t of teachers) {
console.debug('searchProfessorByRMP, strictCheckName, t:', t);
let resultProfessorFirstName = t.node.firstName
let resultProfessorLastName = t.node.lastName
let resultProfessorLegacyId = t.node.legacyId
console.debug(`searchProfessorByRMP, resultProfessorFirstName: ${resultProfessorFirstName}, resultProfessorLastName: ${resultProfessorLastName}`)
let resultProfessorFullName = `${resultProfessorFirstName}${resultProfessorLastName}`.replaceAll(' ','').toLowerCase().trim();
let searchProfessorFullName = searchProfessorName.replaceAll(' ', '').toLowerCase().trim();
console.debug(`searchProfessorByRMP, resultProfessorFullName: ${resultProfessorFullName}, searchProfessorFullName: ${searchProfessorFullName}`)
if (resultProfessorFullName === searchProfessorFullName) {
console.debug('searchProfessorByRMP, strictCheckName, found! t:', t)
sameNameProfessors.push(t);
// If this name exists in constant pool, we should use it directly
if (ignoreMultiResult) {
console.debug('searchProfessorByRMP, strictCheckName, ignoreMultiResult, found! teacher:', t)
sameNameProfessors = [t];
break;
}
}
}
if (ignoreMultiResult) {
teacher = teachers[0];
console.debug(`searchProfessorByRMP, strictCheckName, ignoreMultiResult, found! teacher: ${teacher}`)
} else {
// order by legacyId (represent the time created in database)
teachers = sameNameProfessors.sort((a, b) => a.node.legacyId - b.node.legacyId);
console.debug('searchProfessorByRMP, teachers.length > 1, order by legacyId, teachers:', teachers)
teacher = teachers[0];
console.debug('searchProfessorByRMP, order by legacyId, teacher:', teacher)
}
} else if (teachers.length === 1) {
teacher = teachers[0];
}
console.debug('searchProfessorByRMP, teacher', teacher);
// cannot search any info for the teacher in RMP
if (!teacher) {
console.warn(`searchProfessorByRMP, cannot search any info for teacher: ${professorName}`);
console.log('searchProfessorByRMP, try to search by LastName')
let professorLastName = professorName.split(',')[0];
console.debug('searchProfessorByRMP, professorLastName: ', professorLastName);
requestData = buildRMPSearchProfessorQuery(professorLastName);
GM_xmlhttpRequest({
method: "POST",
url: requestURL,
headers: {
'Authorization': RMP_AUTHORIZATION_KEY,
'Content-Type': 'text/plain',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56',
'Accept': 'application/json',
},
data: requestData,
onload: function (response) {
console.debug('searchProfessorByRMP2, response', response);
let jsonText = response.responseText;
console.debug('searchProfessorByRMP2, jsonText', jsonText);
let dataObj = JSON.parse(jsonText);
console.debug('searchProfessorByRMP2, dataObj', dataObj);
let teachers = dataObj.data.newSearch.teachers.edges;
console.debug('searchProfessorByRMP2, teachers', teachers);
if (teachers.length > 1) {
console.warn('searchProfessorByRMP2, teachers.length > 1, teachers', teachers);
return;
}
teacher = teachers[0];
console.debug('searchProfessorByRMP2, teacher', teacher);
// cannot search any info for the teacher in RMP
if (!teacher) {
console.warn(`searchProfessorByRMP2, cannot search any info for teacher: ${professorName}`);
console.log('searchProfessorByRMP2, try to search by FirstName')
let professorFirstName = professorName.split(',')[1];
console.debug('searchProfessorByRMP3, professorFirstName:', professorFirstName);
requestData = buildRMPSearchProfessorQuery(professorFirstName);
GM_xmlhttpRequest({
method: "POST",
url: requestURL,
headers: {
'Authorization': RMP_AUTHORIZATION_KEY,
'Content-Type': 'text/plain',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56',
'Accept': 'application/json',
},
data: requestData,
onload: function (response) {
console.debug('searchProfessorByRMP3, response', response);
let jsonText = response.responseText;
console.debug('searchProfessorByRMP3, jsonText', jsonText);
let dataObj = JSON.parse(jsonText);
console.debug('searchProfessorByRMP3, dataObj', dataObj);
let teachers = dataObj.data.newSearch.teachers.edges;
console.debug('searchProfessorByRMP3, teachers', teachers);
if (teachers.length > 1) {
console.warn('searchProfessorByRMP3, teachers.length > 1, teachers', teachers);
return;
}
teacher = teachers[0];
console.debug('searchProfessorByRMP3, teacher', teacher);
// cannot search any info for the teacher in RMP
if (!teacher) {
console.warn(`searchProfessorByRMP3, cannot search any info for teacher: ${professorName}`);
console.log('searchProfessorByRMP3, try to search by LastName')
return;
}
searchProfessorDataHandler(teacher, professorName, changeHerfElement, requestURL)
}
})
} else {
searchProfessorDataHandler(teacher, professorName, changeHerfElement, requestURL)
}
}
})
} else {
searchProfessorDataHandler(teacher, professorName, changeHerfElement, requestURL)
}
}
})
}
//searchProfessorByRMP('Conner, Constance')
// ********************** RMP (Rate My Professors) API function end **********************
// ---------------------- Third-party API function end ----------------------
// ---------------------- Schedule Planner function begin ----------------------
function getScheduleByCRN(crn) {
console.debug('getScheduleByCRN, crn', crn);
let scheduleJSONData = localStorage.getItem('generate-request');
console.debug('getScheduleByCRN, scheduleJSONData' + scheduleJSONData);
let scheduleData = JSON.parse(scheduleJSONData);
console.debug('getScheduleByCRN, scheduleData', scheduleData);
let scheduleDataSections = scheduleData.sections;
for (let scheduleDataSection of scheduleDataSections) {
console.debug('getScheduleByCRN, scheduleDataSection', scheduleDataSection);
if (scheduleDataSection.id === crn) {
console.debug('getScheduleByCRN, scheduleDataSection.id === crn, crn', crn);
return scheduleDataSection;
}
}
}
function getRegBlocksByCRN(crn) {
console.debug('getRegBlocksByCRN, parameter, crn', crn)
let jsonData = localStorage.getItem('regblocks')
console.debug('getRegBlocksByCRN, jsonData', jsonData)
let data = JSON.parse(jsonData)
console.debug('getRegBlocksByCRN, data', data)
let scheduleDataSections = data.sections;
for (let scheduleDataSection of scheduleDataSections) {
console.debug('getRegBlocksByCRN, scheduleDataSection', scheduleDataSection);
if (scheduleDataSection.id === crn) {
console.debug('getRegBlocksByCRN, scheduleDataSection.id === crn, crn', crn);
return scheduleDataSection;
}
}
}
// ---------------------- Schedule Planner function end ----------------------
// ====================== global functions end ======================
// TODO the format is not correctly displayed, when print out to console, the format will messy, emmm...
console.log(`
____ ____ ____ _____ ____ ____ _ _
/ ___/ ___/ ___|| ___/ ___|| _ \| | | |
| | | | \___ \| |_ \___ \| |_) | |_| |
| |__| |___ ___) | _| ___) | __/| _ |
\____\____|____/|_| |____/|_| |_| |_|
Welcome to use CCSFSPH :)
GitHub: https://github.com/ccsfsph/ccsf-schedule-planner-helper
Any question, feedback contact us at: https://github.com/ccsfsph/ccsf-schedule-planner-helper/issues
You can also contact us at: [email protected]
`);
console.log('load CCSFSPH success!');
window.onload = function () {
console.debug("window.onload()");
g_pageLoadFinish = true;
// TODO open the page, may be not render finish, it will cause cannot find the element, emmm....
}
// NOTE: only use setInterval method can work when the page location change,
// I also try to use event listner, but it doesn't work, I guess because the website use href to change location instead of hash.
// plus, click the `generate` button to change href address is not working as well (I am also curious why?)
// This method can work, but low efficiency (sam left).
setInterval(function () {
console.debug("page detector works! invoke for every " + pageRefreshInterval + " ms");
if (g_pageLoadFinish) {
console.debug("page load finish, begin to render page");
updateUserSwitchPotentialSchedulePage();
let flag = (!g_isFirstLoadSuccess && isTablePage());
console.debug("g_pageLoadFinish, flag, ", flag);
if (PAGE === PAGE_COURSE_SCHEDULE) {
console.debug("setInterval, PAGE === PAGE_COURSE_SCHEDULE, g_initCoursePageEventFlag:", g_initCoursePageEventFlag)
if (!g_initCoursePageEventFlag) {
let barElementInitResult = false;
let switchTabElementInitResult = false;
let advancedFilterSaveButtonInitResult = false;
let advancedFilterCloseButtonInitResult = false;
let barElements = document.getElementsByClassName(TABLE_SWITCH_PAGE_BAR_CLASS_NAME);
console.debug("setInterval, barElements ", barElements);
if (barElements) {
let barElement = barElements[0];
console.debug("setInterval, barElement ", barElement);
if (barElement) {
let buttonElements = barElement.getElementsByTagName('button');
console.debug("setInterval, buttonElements ", buttonElements);
if (buttonElements) {
for (let buttonElement of buttonElements) {
console.debug("setInterval, buttonElement ", buttonElement)
buttonElement.onclick = function () {
console.debug("setInterval, buttonElement.onclick")
g_isSwitchCoursePageFinish = false;
console.debug("setInterval, buttonElement.onclick, g_isSwitchCoursePageFinish = false")
}
}
barElementInitResult = true;
console.debug("setInterval, barElementInitResult = true")
}
}
}
// switch tab
let switchTabListElements = document.getElementsByClassName("css-ta6bli-tabCss nav nav-tabs pull-left");
console.debug("setInterval, switchTabListElements ", switchTabListElements)
if (switchTabListElements) {
let switchTabListElement = switchTabListElements[0];
console.debug("setInterval, switchTabListElement ", switchTabListElement)
if (switchTabListElement) {
let switchTabListLiElements = switchTabListElement.getElementsByTagName("li")
console.debug("setInterval, switchTabListLiElements ", switchTabListLiElements)
if (switchTabListLiElements) {
for (let switchTabListLiElement of switchTabListLiElements) {
console.debug("setInterval, switchTabListLiElement ", switchTabListLiElement)
switchTabListLiElement.onclick = function () {
console.debug("setInterval, switchTabListLiElement.onclick")
g_isSwitchCoursePageFinish = false;
console.debug("setInterval, switchTabListLiElement.onclick, g_isSwitchCoursePageFinish = false")
// when click the switch tab, the switch page button event will be removed, since we need to rerender again!
g_initCoursePageEventFlag = false;
console.debug("setInterval, switchTabListLiElement.onclick, g_initCoursePageEventFlag = false");
}
}
switchTabElementInitResult = true;
console.debug("setInterval, switchTabElementInitResult = true")
}
}
}
// Advanced Filters
// <button aria-label="" class=" css-1m89de3-hoverStyles-hoverStyles-defaultStyle-centerBlockCss" type="submit"><span aria-hidden="true" class="fa fa-check css-i5lou9-iconCss"></span><span class="css-1q6wcnd-innerStyle">Save Filters</span></button>
let advancedFiltersElements = document.getElementsByClassName(ADVANCED_FILTER_SAVE_BUTTON_CLASS_NAME);
console.debug("setInterval, advancedFiltersElements ", advancedFiltersElements)
if (advancedFiltersElements) {
let advancedFiltersElement = advancedFiltersElements[0];
console.debug("setInterval, advancedFiltersElement ", advancedFiltersElement)
if (advancedFiltersElement) {
advancedFiltersElement.onclick = function () {
console.debug("setInterval, advancedFiltersElement.onclick")
g_isSwitchCoursePageFinish = false;
console.debug("setInterval, switchTabListLiElement.onclick, g_isSwitchCoursePageFinish = false")
// when click the switch tab, the switch page button event will be removed, since we need to rerender again!
g_initCoursePageEventFlag = false;
console.debug("setInterval, advancedFiltersElement.onclick, g_initCoursePageEventFlag = false");
}
advancedFilterSaveButtonInitResult = true;
console.debug("setInterval, advancedFilterSaveButtonInitResult = true")
}
}
// Advanced Filter close button
// <button type="button" aria-label="Close Advanced Filters Panel" class="css-vqzzza-closeCss"><span aria-hidden="true">×</span></button>
let advancedFiltersCloseElements = document.getElementsByClassName(ADVANCED_FILTER_CLOSE_BUTTON_CLASS_NAME);
console.debug("setInterval, advancedFiltersCloseElements ", advancedFiltersCloseElements)
if (advancedFiltersCloseElements) {
let advancedFiltersCloseElement = advancedFiltersCloseElements[0];
console.debug("setInterval, advancedFiltersCloseElement ", advancedFiltersCloseElement)
if (advancedFiltersCloseElement) {
advancedFiltersCloseElement.onclick = function () {
console.debug("setInterval, advancedFiltersCloseElement.onclick")
g_isSwitchCoursePageFinish = false;
console.debug("setInterval, advancedFiltersCloseElement.onclick, g_isSwitchCoursePageFinish = false")
// when click the switch tab, the switch page button event will be removed, since we need to rerender again!
g_initCoursePageEventFlag = false;
console.debug("setInterval, advancedFiltersCloseElement.onclick, g_initCoursePageEventFlag = false");
}
advancedFilterCloseButtonInitResult = true;
console.debug("setInterval, advancedFilterCloseButtonInitResult = true")
}
}
g_initCoursePageEventFlag = barElementInitResult && switchTabElementInitResult && advancedFilterSaveButtonInitResult && advancedFilterCloseButtonInitResult;
}
}
if (isTablePage() && !g_isUpdateLocationChangeFinish || flag || !g_isSwitchCoursePageFinish) {
handleSchedulePlannerPage();
}
} else {
console.debug("page not load finish, waiting for next time invoke");
}
}, pageRefreshInterval);
function getSeatsRateShowFormat(seatsRate) {
console.debug(`getSeatsRateShowFormat, seatsRate ${seatsRate}`)
let htmlFormat = ''
// >= 90%: orange
// 80% - 89.99%: purple
// 70%: - 79.99%: blue
// 60% - 69.99%: green
// < 2.0: grey (don't user white, since the in the page cannot see)
if (seatsRate >= 90) {
htmlFormat = '<span style="color: orange;">' + seatsRate + '%' + '</span>';
} else if (seatsRate >= 80) {
htmlFormat = '<span style="color: purple;">' + seatsRate + '%' + '</span>';
} else if (seatsRate >= 70) {
htmlFormat = '<span style="color: blue;">' + seatsRate + '%' + '</span>';
} else if (seatsRate >= 60) {
htmlFormat = '<span style="color: green;">' + seatsRate + '%' + '</span>';
} else {
htmlFormat = '<span style="color: grey;">' + seatsRate + '%' + '</span>';
}
console.debug("getSeatsRateShowFormat, htmlFormat", htmlFormat)
return htmlFormat
}
function showCurrentSchedule(tHeadElement, instructorRowIndex) {
console.debug('showCurrentSchedule, parameter, tHeadElement ', tHeadElement);
console.debug('showCurrentSchedule, parameter, instructorRowIndex ', instructorRowIndex);
// now, let us get the real name for each row
for (let tHeadElementRow of tHeadElement.rows) {
console.debug('showCurrentSchedule, tHeadElementRow ', tHeadElementRow);
}
let tableElements = tHeadElement.parentElement;
console.debug('showCurrentSchedule, tableElements ', tableElements);
let tableBodyElements = tableElements.getElementsByTagName('tbody');
console.debug('showCurrentSchedule, tableBodyElements ', tableBodyElements);
let instructorName;
// add column first
// Actually, we should use the loop above. But i'm tired now... just copy it from showPotentialSchedule
let tableHeadthRows = tHeadElement.rows[0];
console.debug('showCurrentSchedule, tableHeadthRows', tableHeadthRows);
let tableHeadColumnindex = -1;
let instructorIndex = -1;
let tableHeadthRowsThElements = tableHeadthRows.getElementsByTagName('th');
let tableHeadTotalCell = tableHeadthRowsThElements.length;
let instrcutorEmailCellIndex = -1;
let seatsOpenCellIndex = -1;
let crnCellIndex = -1;
console.debug('showCurrentSchedule, tableHeadTotalCell ', tableHeadTotalCell);
for (let tableHeadthRow of tableHeadthRowsThElements) {
tableHeadColumnindex++;
console.debug('showCurrentSchedule, tableHeadColumnindex', tableHeadColumnindex);
console.debug('showCurrentSchedule, tableHeadthRow', tableHeadthRow);
// display order: Seats Capacity、Seats Open、Seats Filled
// first, add 'Seats Capacity' before 'Seats Open'
if (tableHeadthRow.innerText === 'Instructor') {
instructorIndex = tableHeadColumnindex;
}
if (tableHeadthRow.innerText === 'Instructor Email') {
instrcutorEmailCellIndex = tableHeadColumnindex;
}
if (tableHeadthRow.innerText === 'Seats Open') {
seatsOpenCellIndex = tableHeadColumnindex;
}
if (tableHeadthRow.innerText === 'CRN #') {
crnCellIndex = tableHeadColumnindex;
}
}
console.debug('showCurrentSchedule, instructorIndex', instructorIndex);
if (instrcutorEmailCellIndex === -1) {
tableHeadTotalCell += 1;
// Add after `Instructor` column
// NOTE: since the appendChild will at to the last, but insertCell cannot assign the tag name
// let emailCapacityCell = tableHeadthRows.insertCell(instructorIndex + 1);
let emailCapacityCell = document.createElement('th');
emailCapacityCell.index = instructorIndex + 1;
emailCapacityCell.setAttribute('class', TABLE_HEAD_TH_CELL_CLASS_NAME)
tableHeadthRows.appendChild(emailCapacityCell)
console.debug('showCurrentSchedule, emailCapacityCell', emailCapacityCell);
emailCapacityCell.innerText = 'Instructor Email';
g_instructorEmailColumnIndex = emailCapacityCell.cellIndex;
g_tableHeadTotalCell = tableHeadTotalCell;
g_instructorColumnIndex = instructorIndex;
console.debug("showCurrentSchedule, g_tableHeadTotalCell, ", g_tableHeadTotalCell);
console.debug("showCurrentSchedule, g_instructorEmailColumnIndex, ", g_instructorEmailColumnIndex);
}
// add instructor email here
instructorEmailCellAddData(tHeadElement, instrcutorEmailCellIndex);
for (let tableBodyElement of tableBodyElements) {
console.debug('showCurrentSchedule, tableBodyElement ', tableBodyElement);
let tableBodyElementTrs = tableBodyElement.getElementsByTagName('tr');
console.debug('showCurrentSchedule, tableBodyElementTrs ', tableBodyElementTrs);
// get the first only, contain the table head data
let tableBodyElementTr = tableBodyElementTrs[0];
console.debug('showCurrentSchedule, tableBodyElementTr ', tableBodyElementTr);
let tableBodyElementTrTd = tableBodyElementTr.getElementsByTagName('td');
console.debug('showCurrentSchedule, tableBodyElementTrTd ', tableBodyElementTrTd)
let instrctorNameElement = tableBodyElementTrTd[instructorRowIndex];
console.debug('showCurrentSchedule, instrctorNameElement ', instrctorNameElement);
// add seatsTotal to seats open cell
if (seatsOpenCellIndex !== -1) {
console.debug('showCurrentSchedule, seatsOpenCellIndex', seatsOpenCellIndex)
let seatsOpenElement = tableBodyElementTrTd[seatsOpenCellIndex]
console.debug('showCurrentSchedule, seatsOpenElement', seatsOpenElement)
let crnElement = tableBodyElementTrTd[crnCellIndex]
console.debug('showCurrentSchedule, crnElement', crnElement)
console.debug('showCurrentSchedule, crnElement.innerText', crnElement.innerText)
console.debug('showCurrentSchedule, crnElement.innerText', getRegBlocksByCRN(crnElement.innerText))
let regBlocks = getRegBlocksByCRN(crnElement.innerText)
console.debug('showCurrentSchedule, regBlocks', regBlocks)
let seatsCapacity = regBlocks.seatsCapacity
console.debug('showCurrentSchedule, seatsCapacity', seatsCapacity)
let seatsOpen = seatsOpenElement.innerText
console.debug('showCurrentSchedule, seatsOpen', seatsOpen)
// NOTE: Prevent duplicate click on search box
if (!isNaN(seatsOpen)) {
let seatsRate = ((1 - (seatsOpen / seatsCapacity)) * 100).toFixed(2)
seatsOpenElement.innerHTML = seatsOpen + ' / ' + seatsCapacity + '<br>' + getSeatsRateShowFormat(seatsRate)
}
}
let instrctorNameElementSpans = instrctorNameElement.getElementsByTagName('span');
console.debug('showCurrentSchedule, instrctorNameElementSpans ', instrctorNameElementSpans);
let instrctorNameElementSpan = instrctorNameElementSpans[0];
console.debug('showCurrentSchedule, instrctorNameElementSpan ', instrctorNameElementSpan);
if (!instrctorNameElementSpan) {
console.debug('showCurrentSchedule, skip, !instrctorNameElementSpan ', instrctorNameElementSpan);
continue;
}
instructorName = instrctorNameElementSpan.innerText;
console.debug('showCurrentSchedule, instructorName', instructorName);
searchProfessorByRMP(instructorName, instrctorNameElementSpan);
}
}
function showPotentialSchedule(tHeadElement, instructorRowIndex) {
console.debug('showPotentialSchedule, tHeadElement ', tHeadElement);
console.debug('showPotentialSchedule, instructorRowIndex ', instructorRowIndex);
let tableHeadthRows = tHeadElement.rows[0];
console.debug('showPotentialSchedule, tableHeadthRows', tableHeadthRows);
let tableHeadColumnindex = -1;
let seatsOpenIndex = -1;
let crnIndex = -1;
let tableHeadthRowsThElements = tableHeadthRows.getElementsByTagName('th');
let tableHeadTotalCell = tableHeadthRowsThElements.length;
console.debug('showPotentialScheduleSwitchPage, tableHeadTotalCell ', tableHeadTotalCell);
for (let tableHeadthRow of tableHeadthRowsThElements) {
tableHeadColumnindex++;
console.debug('showPotentialSchedule, tableHeadColumnindex', tableHeadColumnindex);
console.debug('showPotentialSchedule, tableHeadthRow', tableHeadthRow);
// display order: Seats Capacity、Seats Open、Seats Filled
// first, add 'Seats Capacity' before 'Seats Open'
if (tableHeadthRow.innerText === 'Seats Open') {
seatsOpenIndex = tableHeadColumnindex;
}
if (tableHeadthRow.innerText === 'CRN #') {
crnIndex = tableHeadColumnindex;
}
if (tableHeadthRow.innerText === 'Seats Open') {
seatsOpenIndex = tableHeadColumnindex;
}
}
console.debug('showPotentialSchedule, seatsOpenIndex', seatsOpenIndex);
tableHeadTotalCell += 1;
let seatsCapacityCell = tableHeadthRows.insertCell(seatsOpenIndex);
console.debug('showPotentialSchedule, seatsCapacityCell', seatsCapacityCell);
seatsCapacityCell.innerText = 'Seats Capacity';
g_seatsCapacityColumnIndex = seatsCapacityCell.cellIndex;
console.debug("showPotentialSchedule, g_seatsCapacityColumnIndex, ", g_seatsCapacityColumnIndex);
tableHeadTotalCell += 1;
// add after `Instructor`
let instructorEmailCell = tableHeadthRows.insertCell(seatsOpenIndex);
console.debug('showPotentialSchedule, instructorEmailCell ', instructorEmailCell);
instructorEmailCell.innerText = 'Instructor Email';
g_instructorEmailColumnIndex = instructorEmailCell.cellIndex;
console.debug("showPotentialSchedule, g_instructorEmailColumnIndex ", g_instructorEmailColumnIndex);
tableHeadTotalCell += 1;
let instructorCell = tableHeadthRows.insertCell(seatsOpenIndex);
console.debug('showPotentialSchedule, instructorCell', instructorCell);
instructorCell.innerText = 'Instructor';
g_instructorColumnIndex = instructorCell.cellIndex;
console.debug("showPotentialSchedule, g_instructorColumnIndex, ", g_instructorColumnIndex);
g_tableHeadTotalCell = tableHeadTotalCell;
g_tableHeadCrnIndex = crnIndex;
g_tableHeadSeatsOpenIndex = seatsOpenIndex;
potentialSheduleCellAddData(tHeadElement)
}
function instructorEmailCellAddData(tHeadElement, instrcutorEmailCellIndex) {
g_isSwitchSheduleUpdateFinish = true;
let tableHeadTotalCell = g_tableHeadTotalCell;
let instructorEmailColumnIndex = g_instructorEmailColumnIndex
// begin to add the data
let tableElement = tHeadElement.parentElement;
console.debug('showPotentialSchedule, tableElement ', tableElement);
let tableElementBodyElements = tableElement.getElementsByTagName('tbody');
console.debug('showPotentialSchedule, tableElementBodyElements ', tableElementBodyElements);
for (let tableElementBodyElement of tableElementBodyElements) {
console.debug('showPotentialSchedule, tableElementBodyElement ', tableElementBodyElement);
// add cell data
let tableElementBodyElementTrElement = tableElementBodyElement.getElementsByTagName('tr')[0];
console.debug('showPotentialSchedule, tableElementBodyElementTrElement ', tableElementBodyElementTrElement);
let tableElementBodyElementTrElementCells = tableElementBodyElementTrElement.cells;
console.debug('showPotentialSchedule, tableElementBodyElementTrElementCells, ', tableElementBodyElementTrElementCells);
let tableElementBodyElementTrElementCellsTotalCell = tableElementBodyElementTrElementCells.length;
console.debug('showPotentialSchedule, tableElementBodyElementTrElementCellsTotalCell, ', tableElementBodyElementTrElementCellsTotalCell);
if (tableElementBodyElementTrElementCellsTotalCell === tableHeadTotalCell) {
console.debug('showPotentialSchedule, skip this body, no change');
continue;
}
let instructorColumnCell = tableElementBodyElementTrElementCells[g_instructorColumnIndex];
console.debug('instructorColumnCell, ', instructorColumnCell);
let instructorName = instructorColumnCell.innerText;
console.debug('instructorName, ', instructorName);
let instructorEmailCellValueElement = document.createElement('td');
instructorEmailCellValueElement.setAttribute('class', TABLE_DATA_TD_CELL_CLASS_NAME)
tableElementBodyElementTrElement.appendChild(instructorEmailCellValueElement)
// let instructorEmailCellValueElement = tableElementBodyElementTrElement.insertCell(instructorEmailColumnIndex);
console.debug('showPotentialSchedule, instructorEmailCellValueElement ', instructorEmailCellValueElement);
instructorEmailCellValueElement.innerText = '';
getCCSFTeacherInfo(instructorName, instructorEmailCellValueElement);
// expand the cell for table body
let tableElementBodyElementTdElements = tableElementBodyElement.getElementsByTagName('td');
console.debug('showPotentialSchedule, tableElementBodyElementTdElements ', tableElementBodyElementTdElements);
for (let tableElementBodyElementTdElement of tableElementBodyElementTdElements) {
console.debug('showPotentialSchedule, tableElementBodyElementTdElement ', tableElementBodyElementTdElement);
let tableElementBodyElementTdElementAttribute = tableElementBodyElementTdElement.getAttribute('colspan');
if (tableElementBodyElementTdElementAttribute) {
console.debug('showPotentialSchedule, tableElementBodyElementTdElementAttribute ', tableElementBodyElementTdElementAttribute);
tableElementBodyElementTdElement.setAttribute('colspan', tableHeadTotalCell);
}
}
}
// expand the body foot cell colspan
let tableElementFootElements = tableElement.getElementsByTagName('tfoot');
console.debug('showPotentialSchedule, tableElementFootElements ', tableElementFootElements);
for (let tableElementFootElement of tableElementFootElements) {
console.debug('showPotentialSchedule, tableElementFootElement ', tableElementFootElement);
let tableElementFootElementTdElements = tableElementFootElement.getElementsByTagName('td');
console.debug('showPotentialSchedule, tableElementFootElementTdElements ', tableElementFootElementTdElements);
for (let tableElementFootElementTdElement of tableElementFootElementTdElements) {
console.debug('showPotentialSchedule, tableElementFootElementTdElement ', tableElementFootElementTdElement);
let tableElementFootElementTdElementAttribute = tableElementFootElementTdElement.getAttribute('colspan');
if (tableElementFootElementTdElementAttribute) {
console.debug('showPotentialSchedule, tableElementFootElementTdElementAttribute ', tableElementFootElementTdElementAttribute);
// because the show num is not in the last column, that's why need to -1
tableElementFootElementTdElement.setAttribute('colspan', tableHeadTotalCell - 1);
}
}
}
}
function potentialSheduleCellAddData(tHeadElement) {
g_isSwitchSheduleUpdateFinish = true;
let tableHeadTotalCell = g_tableHeadTotalCell;
let crnIndex = g_tableHeadCrnIndex;
let seatsOpenIndex = g_tableHeadSeatsOpenIndex;
let instructorEmailColumnIndex = g_instructorEmailColumnIndex;
// begin to add the data
let tableElement = tHeadElement.parentElement;
console.debug('showPotentialSchedule, tableElement ', tableElement);
let tableElementBodyElements = tableElement.getElementsByTagName('tbody');
console.debug('showPotentialSchedule, tableElementBodyElements ', tableElementBodyElements);
for (let tableElementBodyElement of tableElementBodyElements) {
console.debug('showPotentialSchedule, tableElementBodyElement ', tableElementBodyElement);
// add cell data
let tableElementBodyElementTrElement = tableElementBodyElement.getElementsByTagName('tr')[0];
console.debug('showPotentialSchedule, tableElementBodyElementTrElement ', tableElementBodyElementTrElement);
let tableElementBodyElementTrElementCells = tableElementBodyElementTrElement.cells;
console.debug('showPotentialSchedule, tableElementBodyElementTrElementCells, ', tableElementBodyElementTrElementCells);
let tableElementBodyElementTrElementCellsTotalCell = tableElementBodyElementTrElementCells.length;
console.debug('showPotentialSchedule, tableElementBodyElementTrElementCellsTotalCell, ', tableElementBodyElementTrElementCellsTotalCell);
if (tableElementBodyElementTrElementCellsTotalCell === tableHeadTotalCell) {
console.debug('showPotentialSchedule, skip this body, no change');
continue;
}
let crn = '';
let crnCell = tableElementBodyElementTrElementCells[crnIndex];
console.debug('showPotentialSchedule, crnCell ', crnCell);
crn = crnCell.innerText;
console.debug('showPotentialSchedule, crn ', crn);
let seatsCapacityCellValueElement = tableElementBodyElementTrElement.insertCell(seatsOpenIndex);
console.debug('showPotentialSchedule, seatsCapacityCellValueElement ', seatsCapacityCellValueElement);
seatsCapacityCellValueElement.innerText = getScheduleByCRN(crn).seatsCapacity;
let instructorEmailCellValueElement = tableElementBodyElementTrElement.insertCell(seatsOpenIndex);
console.debug('showPotentialSchedule, instructorEmailCellValueElement ', instructorEmailCellValueElement);
instructorEmailCellValueElement.innerText = '';
let instructorCellValueElement = tableElementBodyElementTrElement.insertCell(seatsOpenIndex);
console.debug('showPotentialSchedule, instructorCellValueElement ', instructorCellValueElement);
// I think we have better to get the instructor name from the ! 'show questions detail' button, but it doesn't matter. since that data get from api as well
// and instructor may have multi one, use this to keep it normal
let instructorName = getScheduleByCRN(crn).instructor[0].name;
console.debug('showPotentialSchedule, instructorName', instructorName);
instructorCellValueElement.innerText = instructorName;
searchProfessorByRMP(instructorName, instructorCellValueElement);
getCCSFTeacherInfo(instructorName, instructorEmailCellValueElement);
// expand the cell for table body
let tableElementBodyElementTdElements = tableElementBodyElement.getElementsByTagName('td');
console.debug('showPotentialSchedule, tableElementBodyElementTdElements ', tableElementBodyElementTdElements);
for (let tableElementBodyElementTdElement of tableElementBodyElementTdElements) {
console.debug('showPotentialSchedule, tableElementBodyElementTdElement ', tableElementBodyElementTdElement);
let tableElementBodyElementTdElementAttribute = tableElementBodyElementTdElement.getAttribute('colspan');
if (tableElementBodyElementTdElementAttribute) {
console.debug('showPotentialSchedule, tableElementBodyElementTdElementAttribute ', tableElementBodyElementTdElementAttribute);
tableElementBodyElementTdElement.setAttribute('colspan', tableHeadTotalCell);
}
}
}
// expand the body foot cell colspan
let tableElementFootElements = tableElement.getElementsByTagName('tfoot');
console.debug('showPotentialSchedule, tableElementFootElements ', tableElementFootElements);
for (let tableElementFootElement of tableElementFootElements) {
console.debug('showPotentialSchedule, tableElementFootElement ', tableElementFootElement);
let tableElementFootElementTdElements = tableElementFootElement.getElementsByTagName('td');
console.debug('showPotentialSchedule, tableElementFootElementTdElements ', tableElementFootElementTdElements);
for (let tableElementFootElementTdElement of tableElementFootElementTdElements) {
console.debug('showPotentialSchedule, tableElementFootElementTdElement ', tableElementFootElementTdElement);
let tableElementFootElementTdElementAttribute = tableElementFootElementTdElement.getAttribute('colspan');
if (tableElementFootElementTdElementAttribute) {
console.debug('showPotentialSchedule, tableElementFootElementTdElementAttribute ', tableElementFootElementTdElementAttribute);
// because the show num is not in the last column, that's why need to -1
tableElementFootElementTdElement.setAttribute('colspan', tableHeadTotalCell - 1);
}
}
}
}
// in potential schedule, user switch to differnt schedule
function showPotentialScheduleSwitchPage(tHeadElement, instructorRowIndex) {
console.debug('showPotentialScheduleSwitchPage, tHeadElement ', tHeadElement);
console.debug('showPotentialScheduleSwitchPage, instructorRowIndex ', instructorRowIndex);
// the table head will not change, only change the differnt course!
let tableHeadthRows = tHeadElement.rows[0];
console.debug('showPotentialSchedule, tableHeadthRows', tableHeadthRows);
}
function handleSchedulePlannerPage() {
g_isUpdateLocationChangeFinish = true;
let tHeadElements = document.getElementsByClassName(TABLE_THEAD_CLASS_NAME);
if (tHeadElements) {
console.debug(" schedule planner page ");
console.debug('handleSchedulePlannerPage, tHeadElements ', tHeadElements);
let tHeadElement = tHeadElements[0];
if (!tHeadElement) {
return console.debug('handleSchedulePlannerPage, no tHeadElement found');
}
// may have more table in one page, like the index page. current scheule and saved schedule
for (let tHeadElement of tHeadElements) {
console.debug('handleSchedulePlannerPage, tHeadElement ', tHeadElement);
// the first row is the table title head
let tHeadtrElement = tHeadElement.rows[0];
console.debug('handleSchedulePlannerPage, tHeadtrElement ', tHeadtrElement);
let tHeadtrElementCells = tHeadtrElement.cells;
console.debug('handleSchedulePlannerPage, tHeadtrElementCells ', tHeadtrElementCells);
// we should get the column index, and use the index to get the row for each instrutor name
let instructorRowIndex = -1;
for (let tHeadtrElementCell of tHeadtrElementCells) {
instructorRowIndex++;
console.debug('handleSchedulePlannerPage, tHeadtrElementCellIndex ', instructorRowIndex);
console.debug('handleSchedulePlannerPage, tHeadtrElementCell ', tHeadtrElementCell);
// try to find the text 'Instructor' Element
let cellText = tHeadtrElementCell.innerText;
console.debug('handleSchedulePlannerPage, tHeadtrElementCell, cellText', cellText);
if (cellText === 'Instructor') {
console.debug('handleSchedulePlannerPage, tHeadtrElementCell: found the Instructor element', tHeadtrElementCell);
break;
}
}
console.debug('handleSchedulePlannerPage, instructorRowIndex ' + instructorRowIndex);
g_isFirstLoadSuccess = true;
g_isSwitchCoursePageFinish = true;
// may be we need to change another way to identify whther the column is exist in the table
// the build scheuled page and enrolled page have different table title head
// the index begin from zero, however the length starts with 1 when the index is zero!!
// NOTE: in possible schedule page, if the user switch course, there is definitely one table body will change
// therefore, we need to get the table head total cell num, and each body num, if one of them is different, we need to invoke it again
// no found the instructor index, because the last cell is not instrctuor index
if (instructorRowIndex == tHeadtrElementCells.length - 1) {
console.debug('handleSchedulePlannerPage, instructorRowIndex not found');
showPotentialSchedule(tHeadElement, instructorRowIndex);
return;
}
if (!g_isSwitchSheduleUpdateFinish) {
console.debug("handleSchedulePlannerPage, g_isSwitchSheduleUpdateFinish ", g_isSwitchSheduleUpdateFinish);
potentialSheduleCellAddData(tHeadElement);
return;
}
console.debug("handleSchedulePlannerPage, showCurrentSchedule ");
showCurrentSchedule(tHeadElement, instructorRowIndex);
}
} else {
console.debug("not in schedule planner page");
}
}
})();