'+(1":Math.round(60*p)+" sec. left")}}}}if(options.enablePeriodNotifications){for(var g=Array.prototype.slice.call(document.getElementsByClassName("now")),h=g.diff(a),f=a.diff(g),v=0;v?',n.appendChild(a)}else n.textContent=e.name;t.appendChild(n),document.getElementById("optionsContent").appendChild(t)}function createOption(e){var t=document.createElement("tr"),n=document.createElement("td"),a=document.createElement("td");if(e.hasOwnProperty("tooltip")){var i=document.createElement("span");i.title=e.tooltip,i.innerHTML=e.description+'?:',n.appendChild(i)}else n.textContent=e.description+":";var o=document.createElement("input");o.name=e.name,o.type=e.type;var r=mobile&&e.hasOwnProperty("mobileDefault")?e.mobileDefault:e.default;"number"==o.type?(o.min=0,o.value=r):"checkbox"==o.type&&r&&(o.checked="checked"),a.appendChild(o),t.appendChild(n),t.appendChild(a),document.getElementById("optionsContent").appendChild(t)}function attachOptionActions(){updateUpdateInterval(),document.getElementsByName("activeUpdateInterval")[0].addEventListener("change",function(e){updateUpdateInterval()}),document.getElementsByName("showPassingPeriods")[0].addEventListener("change",function(e){updateSchedule(null,!0)}),document.getElementsByName("color")[0].addEventListener("change",function(e){updateSchedule(null,!0)}),document.getElementsByName("enablePeriodNotifications")[0].addEventListener("change",function(e){if(options.enablePeriodNotifications){var t=Notification.permission;"Notification"in window?"denied"==t?alert("Please allow desktop notifications for this site to enable this feature."):"default"==t&&Notification.requestPermission():alert("This browser does not support desktop notifications.")}}),document.getElementsByName("enableDoge")[0].addEventListener("change",function(e){document.getElementById("warning").removeEventListener("click",options.enableDoge?null:checkDoge),document.getElementById("warning").addEventListener("click",options.enableDoge?checkDoge:null)}),document.body.classList.add(options.enableDayView?"day":"week"),document.getElementsByName("enableDayView")[0].addEventListener("change",function(e){updateSchedule(null,!0),document.body.classList.remove("week"),document.body.classList.remove("day"),document.body.classList.add(options.enableDayView?"day":"week"),scrollTo(0,0)}),mobile||document.addEventListener("keydown",function(e){switch(e.keyCode){case 116:options.interceptF5&&(e.preventDefault(),updateSchedule());break;case 82:options.interceptCtrlR&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),updateSchedule());break;case 37:goLast();break;case 39:goNext();break;case 40:goCurr()}-1!=(inputStr+=e.keyCode).indexOf(KONAMI)&&(inputStr="")})}function download(e,t,n){var a=new XMLHttpRequest;a.open("GET",e,!0),a.onreadystatechange=function(){4==a.readyState&&(200==a.status?t(a.responseText):n&&n(!1,a.status))},a.ontimeout=function(){n(!0,null)},a.send()}function updateUpdateInterval(){document.hidden?setUpdateInterval(options.hiddenUpdateInterval):setUpdateInterval(hasFocus?options.activeUpdateInterval:options.inactiveUpdateInterval)}function setUpdateInterval(e){clearInterval(updateScheduleID),updateScheduleID=0Future schedules may be incorrect.",dogeCounter=7,hasFocus=!0,options={},inputStr="",KEY_LEFT=37,KEY_UP=38,KEY_RIGHT=39,KEY_DOWN=40,KEY_A=65,KEY_B=66,KONAMI=""+KEY_UP+KEY_UP+KEY_DOWN+KEY_DOWN+KEY_LEFT+KEY_RIGHT+KEY_LEFT+KEY_RIGHT+KEY_B+KEY_A,START_DATE=new Date("August 25, 2019"),START_SCHEDULE=1,LINKS={Lunch:"https://harkerdev.github.io/harker-lunch/#$DAYNAMELOWER$","School Meeting":"https://docs.google.com/forms/d/e/1FAIpQLSeZoCFQhzPqiX-Tbcc0qRUuw7_rjMgUxkiR97GN6aNB8Ulfsg/viewform?entry.1033439092=$MONTH$%2F$DATE$"},COLLABORATION_REPLACEMENTS=["Collaboration -> Office Hours","Collaboration -> Office Hours","Collaboration -> Faculty Meeting","Collaboration -> Office Hours","Collaboration -> After School"],TOTAL_SCHEDULES=8,SCHEDULES=["A","B","C","D","A","B","C","D"],MILLIS_PER_DAY=864e5;function updateUrlParams(){urlParams={};for(var e,t=/(?!^)\+/g,n=/([^&=]+)=?([^&]*)/g,a=function(e){return decodeURIComponent(e.replace(t," "))},i=location.search.substring(1);e=n.exec(i);)urlParams[a(e[1])]=a(e[2])}function error(){console.log("error downloading")}function initViewport(){if(mobile){var e=document.createElement("meta");e.name="viewport",e.content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0",document.getElementsByTagName("head")[0].appendChild(e),document.getElementsByTagName("body")[0].class="mobile"}}function initTitle(){document.getElementById("leftArrow").addEventListener("click",goLast),document.getElementById("rightArrow").addEventListener("click",goNext),document.getElementById("refresh").addEventListener("click",function(){updateSchedule(null,!0)}),$.ajax({url:"titles.txt",success:function(e){titleStr=e},cache:!1})}function checkDoge(){0==(dogeCounter-=1)?(setDoge(!0),setTitleTitle("doge")):dogeCounter<0?(setDoge(!1),dogeCounter=7,setTitleTitle(titleStr)):setTitleTitle("You are "+dogeCounter+(1==dogeCounter?" step":" steps")+" away from becoming a developer!")}function parseRawSchedule(e){var t=e.split("\n"),n=0;for((schedules=[])[0]=[];t.length>0;)if(0===t[0].length)schedules[++n]=[],t.shift();else{var a=t.shift();if(0===n&&a.indexOf("|")>=0)for(var i=new Date(a.substring(0,a.indexOf("|"))),o=new Date(a.substring(a.indexOf("|")+1,a.indexOf("\t")));i<=o;i.setDate(i.getDate()+1))schedules[0].push(i.getMonth().valueOf()+1+"/"+i.getDate()+"/"+i.getFullYear().toString().substr(-2)+a.substring(a.indexOf("\t")));else schedules[n].push(a)}}function setDisplayDate(e,t){var n=e?new Date(e):getDateFromUrlParams();if(setDayBeginning(n),t||!displayDate||n.valueOf()!=displayDate.valueOf()){var a=document.getElementById("schedule");for(displayDate=new Date(n);a.rows.length;)a.deleteRow(-1);var i=a.insertRow(-1);if(options.enableDayView)0!=n.getDay()&&6!=n.getDay||goNext(),createDay(i,n=getNextWeekday(n));else{n=getMonday(n);for(var o=0;o<5;o++)createDay(i,n),n.setDate(n.getDate()+1)}}}function getDateFromUrlParams(){var e=new Date;return urlParams.y>0&&urlParams.m>0&&urlParams.d>0?e=new Date("20"+urlParams.y,urlParams.m-1,urlParams.d):urlParams.m>0&&urlParams.d>0&&(e=new Date((new Date).getFullYear(),urlParams.m-1,urlParams.d)),options.enableDayView||(e=getMonday(e)),e}function warn(e){var t=document.getElementById("warning");t.style.display=e?"block":"none",t.innerHTML=e}function createDay(e,t){var n=getDayInfo(t),a=e.insertCell(-1);a.date=t.valueOf(),9==t.getMonth()&&31==t.getDate()&&a.classList.add("halloween");var i=document.createElement("div");i.classList.add("head");var o=document.createElement("div");o.classList.add("headWrapper");var r="";void 0===n.name&&null===n.name||(r="("+n.name+")"),"()"===r&&(r=""),o.innerHTML=days[t.getDay()]+''+n.dateString+" "+r+"
",i.appendChild(o),a.appendChild(i);var s="8:00";if(n.index>0)for(var d=1;d=0){var h=document.createElement("table");h.classList.add("lunch");var v=h.insertRow(-1),y=v.insertCell(-1),D=v.insertCell(-1),E=u.substring(0,u.indexOf("||")),b=u.substring(u.indexOf("||")+2),w=c.substring(0,c.indexOf("||")),S=c.substring(c.indexOf("||")+2);4==findNumberOfOccurences(c,"|")?(show1Time=!0,show2Time=!0,createSubPeriods(y,w,p,E.substring(E.indexOf("-")+1,E.indexOf("|")),E.substring(E.indexOf("|")+1,E.lastIndexOf("-")),m,t,show1Time,show2Time),createSubPeriods(D,S,p,b.substring(b.indexOf("-")+1,b.indexOf("|")),b.substring(b.indexOf("|")+1,b.lastIndexOf("-")),m,t,show1Time,show2Time)):(createPeriod(y,w,p,m,t),show1Time=4==n.id||"ReCreate"==n.id,show2Time=!show1Time,createSubPeriods(D,S,p,b.substring(b.indexOf("-")+1,b.indexOf("|")),b.substring(b.indexOf("|")+1,b.lastIndexOf("-")),m,t,show1Time,show2Time)),f.appendChild(h)}else createPeriod(f,c,p,m,t);a.appendChild(f)}}function makePeriodNameReplacements(e,t){if(t.length>0)for(var n=0;n")+2);return e}function setTitleTitle(e,t){var n=t?new Date(t):getDateFromUrlParams(),a=e.split("\n");a.length>1&&a.pop(),displayMessage=a[Math.floor(Math.random()*a.length)],getMonday(n)>getMonday(new Date)&&(displayMessage+=futureWarning),warn(displayMessage)}function getMonday(e){var t=new Date(e);return t.getDay()>=6?t.setDate(t.getDate()+2):t.setDate(t.getDate()-t.getDay()+1),setDayBeginning(t),t}function getNextWeekday(e){var t=new Date(e);return 0==t.getDay()||6==t.getDay()?(goNext(),getMonday(e)):(setDayBeginning(t),t)}function setDayBeginning(e){e.setHours(0,0,0,0)}function getDateFromString(e,t){var n=e.substring(0,e.indexOf(":")),a=e.substring(e.indexOf(":")+1);return n<7&&(n=parseInt(n,10)+12),new Date(t.getFullYear(),t.getMonth(),t.getDate(),n,a)}function getDayInfo(e){for(var t,n=e.getMonth().valueOf()+1+"/"+e.getDate().valueOf()+"/"+e.getFullYear().toString().substr(-2),a=0,i=[],o=0;o=0?(t=schedules[0][o].substring(schedules[0][o].indexOf("\t")+1,schedules[0][o].indexOf("[")-1),i=schedules[0][o].substring(schedules[0][o].indexOf("[")+1,schedules[0][o].indexOf("]")).split(",")):t=schedules[0][o].substring(schedules[0][o].indexOf("\t")+1),a=getScheduleIndex(t));void 0===t&&(a=0===(t=e.getDay())||6==t?t=0:getScheduleIndex(t=calculateScheduleRotationID(e)));var r="";return t<=TOTAL_SCHEDULES&&(r=SCHEDULES[t-1]),i.push(COLLABORATION_REPLACEMENTS[e.getDay()-1]),{index:a,id:t,dateString:n,replacements:i,name:r}}function getScheduleIndex(e){if(0===e)return 0;for(var t=1;t=s&&s>=START_DATE&&0!==s.getDay()&&6!=s.getDay()&&t--}}return(n=t<0?START_SCHEDULE+Math.floor(t%TOTAL_SCHEDULES)+TOTAL_SCHEDULES:START_SCHEDULE+Math.floor(t%TOTAL_SCHEDULES))>8&&(n-=8),n>4&&n%2==0&&(n-=4),n}function createPeriod(e,t,n,a,i,o){void 0===o&&(o=!0),startDate=getDateFromString(n,i),endDate=getDateFromString(a,i);var r=document.createElement("div");r.classList.add("periodWrapper"),r.periodName=t,r.start=startDate,r.end=endDate;var s=(endDate-startDate)/6e4;if(!0===options.color)switch(r.periodName){case"P1":r.classList.add("periodone");break;case"P2":r.classList.add("periodtwo");break;case"P3":r.classList.add("periodthree");break;case"P4":r.classList.add("periodfour");break;case"P5":r.classList.add("periodfive");break;case"P6":r.classList.add("periodsix");break;case"P7":r.classList.add("periodseven")}if(s>0){if(r.style.height=s-1+"px",s>=15&&(t&&(2==t.length&&"P"==t[0]&&t[1]>=1&&t[1]<=7&&""!=options["period"+t[1]]&&(t=options["period"+t[1]]+" ("+t+")"),r.innerHTML=t),o&&(r.innerHTML+=(s<30?" ":"
")+n+" – "+a)),LINKS[t]){var d=document.createElement("a");d.className="plain",d.href=LINKS[t].split("$DATE$").join(i.getDate()).split("$DAY$").join(i.getDay()+1).split("$FULLYEAR$").join(i.getFullYear()).split("$MONTH$").join(i.getMonth()+1).split("$DAYNAME$").join(days[i.getDay()]).split("$DAYNAMELOWER$").join(days[i.getDay()].toLowerCase()),d.appendChild(r),r=d}return e.appendChild(r)}}function createSubPeriods(e,t,n,a,i,o,r,s,d){void 0===s&&(s=!0),void 0===d&&(d=!0);var l=document.createElement("div");if(l.classList.add("period"),createPeriod(l,t.substring(0,t.indexOf("|")),n,a,r,s),e.appendChild(l),options.showPassingPeriods){var c=document.createElement("div");c.classList.add("period"),createPeriod(c,"",a,i,r),e.appendChild(c)}var u=document.createElement("div");u.classList.add("period"),createPeriod(u,t.substring(t.indexOf("|")+1),i,o,r,d),e.appendChild(u)}function create3SubPeriods(e,t,n,a,i,o,r,s,d,l,c){var u=document.createElement("div");u.classList.add("period"),createPeriod(u,t,n,a,c),e.appendChild(u);var p=document.createElement("div");p.classList.add("period"),createPeriod(p,i,o,r,c),e.appendChild(p);var m=document.createElement("div");m.classList.add("period"),createPeriod(m,s,d,l,c),e.appendChild(m)}function goLast(){var e=new Date(displayDate);if(options.enableDayView)for(e.setDate(e.getDate()-1);0==e.getDay()||6==e.getDay();)e.setDate(e.getDate()-1);else e.setDate(e.getDate()-7);updateSchedule(e,!1,!0),updateSearch(e)}function goNext(){var e=new Date(displayDate);if(options.enableDayView)for(e.setDate(e.getDate()+1);0==e.getDay()||6==e.getDay();)e.setDate(e.getDate()+1);else e.setDate(e.getDate()+7);updateSchedule(e,!1,!0),updateSearch(e)}function goCurr(){var e=new Date;updateSchedule(e),updateSearch(e)}function updateSearch(e,t){var n=new Date;options.enableDayView||(n=getMonday(n)),e.getDate()!=n.getDate()||e.getMonth()!=n.getMonth()?(urlParams.m=e.getMonth()+1,urlParams.d=e.getDate()):(delete urlParams.m,delete urlParams.d),e.getYear()!=n.getYear()?urlParams.y=e.getFullYear().toString().substr(-2):delete urlParams.y;var a="?";for(var i in urlParams)a+=i+"="+urlParams[i]+"&";a=a.slice(0,-1),history.pushState(e,document.title,location.protocol+"//"+location.host+location.pathname+a+location.hash)}function setHighlightedPeriod(e){e||(e=new Date((new Date).toLocaleString("en-US",{timeZone:"America/Los_Angeles"})));var t=new Date(e);t.setHours(0,0,0,0);var n=document.getElementById("today"),a=[];if(n){for(var i=(a=Array.prototype.slice.call(n.getElementsByClassName("now"))).length-1;i>=0;i--){var o=a[i];o.classList.remove("now");var r=o.getElementsByClassName("periodLength")[0];r&&o.removeChild(r)}n.id=""}for(var s=document.getElementById("schedule").rows[0].cells,d=0;d=0&&e-p.end<0&&(p.classList.add("now"),(p.end-p.start)/6e4>=40)){var m=(p.end-e)/6e4;p.innerHTML+=''+(m>1?Math.round(m)+" min. left
":Math.round(60*m)+" sec. left")}}}}if(options.enablePeriodNotifications){for(var g=Array.prototype.slice.call(document.getElementsByClassName("now")),f=g.diff(a),h=a.diff(g),v=0;v=0||!mobile)&&createOptionSection(e)}),initOptions(),attachOptionActions(),updateSchedule(null,!0,!0)}function displayOptionsError(e,t){updateSchedule(),warn(e?"Retrieval of options.json timed out!":"Something went wrong while retrieving options.json!")}function createOptionSection(e){createOptionSectionTitle(e),e.options.forEach(function(e){(!e.hasOwnProperty("platforms")||mobile&&e.platforms.indexOf("mobile")>=0||!mobile)&&createOption(e)})}function createOptionSectionTitle(e){var t=document.createElement("tr"),n=document.createElement("th");if(n.colspan=2,e.hasOwnProperty("tooltip")){var a=document.createElement("span");a.title=e.tooltip,a.innerHTML=e.name+'?',n.appendChild(a)}else n.textContent=e.name;t.appendChild(n),document.getElementById("optionsContent").appendChild(t)}function createOption(e){var t=document.createElement("tr"),n=document.createElement("td"),a=document.createElement("td");if(e.hasOwnProperty("tooltip")){var i=document.createElement("span");i.title=e.tooltip,i.innerHTML=e.description+'?:',n.appendChild(i)}else n.textContent=e.description+":";var o=document.createElement("input");o.name=e.name,o.type=e.type;var r=mobile&&e.hasOwnProperty("mobileDefault")?e.mobileDefault:e.default;"number"==o.type?(o.min=0,o.value=r):"checkbox"==o.type&&r&&(o.checked="checked"),a.appendChild(o),t.appendChild(n),t.appendChild(a),document.getElementById("optionsContent").appendChild(t)}function attachOptionActions(){updateUpdateInterval(),document.getElementsByName("activeUpdateInterval")[0].addEventListener("change",function(e){updateUpdateInterval()}),document.getElementsByName("showPassingPeriods")[0].addEventListener("change",function(e){updateSchedule(null,!0)}),document.getElementsByName("color")[0].addEventListener("change",function(e){updateSchedule(null,!0)});for(var e=1;e<=7;e++)document.getElementsByName("period"+e)[0].addEventListener("change",function(e){updateSchedule(null,!0)});document.getElementsByName("enablePeriodNotifications")[0].addEventListener("change",function(e){if(options.enablePeriodNotifications){var t=Notification.permission;"Notification"in window?"denied"==t?alert("Please allow desktop notifications for this site to enable this feature."):"default"==t&&Notification.requestPermission():alert("This browser does not support desktop notifications.")}}),document.getElementsByName("enableDoge")[0].addEventListener("change",function(e){document.getElementById("warning").removeEventListener("click",options.enableDoge?null:checkDoge),document.getElementById("warning").addEventListener("click",options.enableDoge?checkDoge:null)}),document.body.classList.add(options.enableDayView?"day":"week"),document.getElementsByName("enableDayView")[0].addEventListener("change",function(e){updateSchedule(null,!0),document.body.classList.remove("week"),document.body.classList.remove("day"),document.body.classList.add(options.enableDayView?"day":"week"),scrollTo(0,0)}),mobile||document.addEventListener("keydown",function(e){switch(e.keyCode){case 116:options.interceptF5&&(e.preventDefault(),updateSchedule());break;case 82:options.interceptCtrlR&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),updateSchedule());break;case 37:goLast();break;case 39:goNext();break;case 40:goCurr()}-1!=(inputStr+=e.keyCode).indexOf(KONAMI)&&(inputStr="")})}function download(e,t,n){var a=new XMLHttpRequest;a.open("GET",e,!0),a.onreadystatechange=function(){4==a.readyState&&(200==a.status?t(a.responseText):n&&n(!1,a.status))},a.ontimeout=function(){n(!0,null)},a.send()}function updateUpdateInterval(){document.hidden?setUpdateInterval(options.hiddenUpdateInterval):setUpdateInterval(hasFocus?options.activeUpdateInterval:options.inactiveUpdateInterval)}function setUpdateInterval(e){clearInterval(updateScheduleID),updateScheduleID=e>0?setInterval(function(){updateSchedule()},1e3*e):null}function sendNotification(e,t){if("Notification"in window){var n=new Notification(e);t>0&&setTimeout(function(){n.close()},1e3*t)}}function isMobile(){var e=navigator.userAgent||navigator.vendor||window.opera;return window.innerWidth<=800&&window.innerHeight<=600||(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(e)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(e.substr(0,4)))}function updateClock(){var e=new Date,t=e.getHours(),n=t%12,a=e.getMinutes();document.getElementById("currentTime").innerHTML=(0===n?12:n)+":"+addLeadingZero(a)+(t>=12?" PM":" AM")}function addLeadingZero(e){return e<10?"0"+e:e}function isSameDate(e,t){return e.getFullYear()===t.getFullYear()&&t.getMonth()==t.getMonth()&&e.getDate()===t.getDate()}function findNumberOfOccurences(e,t){for(var n=-1,a=-2;-1!=a;n++,a=e.indexOf(t,a+1));return n}function countWeekendDays(e,t){var n=1+Math.round((t.getTime()-e.getTime())/MILLIS_PER_DAY);return 2*Math.floor((e.getDay()+n)/7)+(0===e.getDay())-(6==t.getDay())}updateUrlParams(),window.history.replaceState(getDateFromUrlParams(),document.title,document.location),document.addEventListener("visibilitychange",function(e){document.hidden||updateSchedule(),updateUpdateInterval()}),addEventListener("focus",function(e){updateSchedule(),hasFocus=!0,updateUpdateInterval()}),addEventListener("blur",function(e){hasFocus=!1,updateUpdateInterval()}),addEventListener("popstate",function(e){updateUrlParams(),updateSchedule(e.state)}),addEventListener("load",function(e){initViewport(),initTitle(),$.ajax({url:"special.txt",success:function(e){parseRawSchedule(e),$.ajax({url:"options.json",success:function(e){createOptions(JSON.stringify(e))},cache:!1})},cache:!1})}),log=console.log.bind(console);
--------------------------------------------------------------------------------
/special.txt:
--------------------------------------------------------------------------------
1 | 8/21/15 Matriculation
2 | 8/27/15 44
3 | 9/7/15 Labor Day
4 | 9/8/15 O2 [Class Meeting->School Meeting]
5 | 9/18/15 51
6 | 9/28/15 Fall Break
7 | 10/2/15 54
8 | 10/7/15 31a
9 | 10/8/15 47
10 | 10/12/15 O2
11 | 10/13/15 O3
12 | 10/14/15 PSAT
13 | 10/21/15 31
14 | 10/22/15 41 [Assembly/Advisory->Advisory]
15 | 10/26/15 Fall Break
16 | 10/29/15 41 [Assembly/Advisory->Assembly]
17 | 11/6/15 51
18 | 11/11/15 31
19 | 11/13/15 51
20 | 11/19/15 47
21 | 11/23/15|11/27/15 Thanksgiving Break
22 | 12/10/15 41
23 | 12/11/15 58
24 | 12/14/15 O1 [School Meeting->Extra Help]
25 | 12/15/15 F16 s1d1
26 | 12/16/15 F16 s1d2
27 | 12/17/15 F16 s1d3
28 | 12/18/15 F16 s1d4
29 | 12/19/15|1/3/16 Winter Break
30 | 1/15/16 B1
31 | 1/18/16 Holiday
32 | 1/28/16 B3
33 | 2/1/16 4 [Advisory->Class Meeting]
34 | 2/2/16 B2
35 | 2/3/16 1
36 | 2/4/16 4
37 | 2/5/16 3
38 | 2/7/16|2/15/16 Presidents Week
39 | 3/1/16 21 [Class Meeting->Assembly]
40 | 3/9/16 31 [Assembly->Advisory]
41 | 3/10/16 41
42 | 3/11/16 53
43 | 3/18/16 57
44 | 3/22/16 2B
45 | 3/23/16 3
46 | 3/24/16 No School
47 | 3/25/16 No School
48 | 3/26/16|4/2/16 Spring Break
49 | 4/5/16 21
50 | 4/12/16 O2 [Class Meeting->ReCreate Reading]
51 | 4/13/16 31
52 | 4/21/16 45a
53 | 4/22/16 54
54 | 4/26/16 21
55 | 5/3/16 25
56 | 5/9/16 O1 [School Meeting->Extra Help]
57 | 5/10/16 O2 [Class Meeting->Extra Help]
58 | 5/17/16 32
59 | 5/18/16 O4
60 | 5/19/16 26
61 | 5/26/16 41
62 | 5/30/16 No School
63 | 5/31/16 O1 [School Meeting->Extra Help]
64 | 6/1/16 F16 s2d1
65 | 6/2/16 F16 s2d2
66 | 6/3/16 F16 s2d3
67 | 6/9/16|8/18/16 Summer Break
68 | 8/19/16 Matriculation
69 | 8/25/16 ReCreate
70 | 9/5/16 Labor Day
71 | 9/12/16 31a
72 | 9/13/16 44
73 | 9/30/16 Fall Break
74 | 10/6/16 41
75 | 10/18/16 4 [Advisory->School Meeting]
76 | 10/19/16 PSAT
77 | 10/31/16 Fall Break
78 | 11/3/16 27
79 | 11/4/16 31b
80 | 11/7/16 41
81 | 11/9/16 2 [School Meeting->Advisory]
82 | 11/11/16 4 [Advisory->School Meeting]
83 | 11/15/16 2b
84 | 11/17/16 4b
85 | 11/21/16 PTC
86 | 11/22/16 PTC
87 | 11/23/16|11/25/16 Thanksgiving Break
88 | 11/28/16 2 [School Meeting->Advisory]
89 | 12/6/16 4b [Advisee Reviews->Advisory]
90 | 12/9/16 32
91 | 12/12/16 46
92 | 12/13/16 F16 s1d1
93 | 12/14/16 F16 s1d2
94 | 12/15/16 F16 s1d3
95 | 12/16/16 F16 s1d4
96 | 12/17/16|1/2/17 Winter Break
97 | 1/4/17 2c
98 | 1/9/17 5
99 | 1/10/17 22
100 | 1/16/17 MLKDay
101 | 1/26/17 12
102 | 1/27/17 25
103 | 2/2/17 29
104 | 2/3/17 31a
105 | 2/7/17 13
106 | 2/8/17 28
107 | 2/13/17|2/20/17 PresWeekBreak
108 | 3/10/17 21
109 | 3/14/17 4b
110 | 3/16/17 2b
111 | 3/20/17 4c
112 | 3/30/17|3/31/17 ParentTeacherConferences
113 | 4/3/17|4/7/17 SpringBreak
114 | 4/11/17 5
115 | 4/12/17 2d
116 | 4/14/17 4 [Advisory->School Meeting]
117 | 4/17/17 1 [Soph Mtg.->Senior Mtg.]
118 | 4/24/17 2b [Advisee Reviews->Regatta]
119 | 4/28/17 21b
120 | 5/4/17 2b [Advisee Reviews->Office Hours]
121 | 5/5/17 3b
122 | 5/10/17 2b [Advisee Reviews->Office Hours]
123 | 5/18/17 47
124 | 5/25/17 5
125 | 5/26/17 2b [Advisee Reviews->Advisory]
126 | 5/29/17 MemorialDay
127 | 6/1/17 7a
128 | 6/2/17 2e
129 | 6/5/17 1a
130 | 6/6/17 F17 s2d1
131 | 6/7/17 F17 s2d2
132 | 6/8/17 F17 s2d3
133 | 6/9/17|8/25/17 Summer
134 | 8/25/17 Matriculation 2017
135 | 8/31/17 ReCreate
136 | 9/4/17 Labor Day
137 | 9/21/17 2f
138 | 9/22/17 32b
139 | 9/25/17 45
140 | 9/26/17 7a
141 | 9/27/17 2g
142 | 9/28/17 5a
143 | 9/29/17 Fall Break
144 | 10/4/17 21c
145 | 10/11/17 PSAT
146 | 10/13/17 4 [Office Hours->Service Fair]
147 | 10/17/17 27
148 | 10/18/17 31a
149 | 10/25/17 40
150 | 10/27/17 20
151 | 10/30/17 Fall Break
152 | 11/7/17 41
153 | 11/13/17 4a
154 | 11/15/17 2b
155 | 11/17/17 4b
156 | 11/20/17|11/24/17 Thanksgiving Break
157 | 11/28/17 71
158 | 12/6/17 4b [Advisee Reviews->Advisory]
159 | 12/8/17 2b [Advisee Reviews->Holiday Show]
160 | 12/12/17 F17 s1d1
161 | 12/13/17 F17 s1d2
162 | 12/14/17 F17 s1d3
163 | 12/15/17 F17 s1d4
164 | 12/18/17|1/1/18 Winter Break
165 | 1/4/18 2c
166 | 1/10/18 21d
167 | 1/12/18 40b [Honor Council Assembly->Assembly]
168 | 1/15/18 MLKDay
169 | 1/25/18 41b
170 | 1/31/18 4e
171 | 2/2/18 21e
172 | 2/7/18 13
173 | 2/8/18 28b
174 | 2/12/18|2/19/18 PresWeekBreak
175 | 2/26/18 41c
176 | 3/14/18 4f
177 | 3/16/18 2b
178 | 3/20/18 41b
179 | 3/22/18 2d
180 | 3/23/18 31c
181 | 3/26/18 44
182 | 3/28/18 2c
183 | 4/2/18|4/6/18 Spring Break
184 | 4/16/18 2d
185 | 4/30/18 4b [Advisee Reviews->Regatta]
186 | 5/4/18 41d
187 | 5/8/18 2b [Advisee Reviews->Office Hours]
188 | 5/9/18 3b
189 | 5/11/18 1b
190 | 5/14/18 2b [Advisee Reviews->Office Hours]
191 | 5/15/18 3b
192 | 5/17/18 1b
193 | 5/18/18 2b [Advisee Reviews->Office Hours]
194 | 5/24/18 72
195 | 5/28/18 Memorial Day
196 | 5/30/18 15
197 | 5/31/18 2b [Advisee Reviews->Office Hours]
198 | 6/1/18 33
199 | 6/4/18 48
200 | 6/5/18 F18 s2d1
201 | 6/6/18 F18 s2d2
202 | 6/7/18 F18 s2d3
203 | 6/8/18|8/23/18 Summer
204 | 8/24/18 Matriculation 2017
205 | 8/30/18 ReCreate
206 | 9/3/18 Labor Day
207 | 9/20/18 2b [Advisee Reviews->Assembly]
208 | 9/28/18 Fall Break
209 | 10/3/18 2b [Advisee Reviews->Parade]
210 | 10/5/18 61
211 | 10/8/18 7a
212 | 10/10/18 PSAT
213 | 10/11/18 5a
214 | 10/12/18 41f
215 | 10/16/18 2h
216 | 10/24/18 49
217 | 10/26/18 2 [School Meeting->Advisory]
218 | 10/29/18 Fall Break
219 | 11/2/18 2i
220 | 11/6/18 41
221 | 11/12/18 4 [Office Hours->Service Fair]
222 | 11/14/18 2b
223 | 11/16/18 4b
224 | 11/19/18|11/23/18 Thanksgiving Break
225 | 11/27/18 71
226 | 12/11/18 4b [Advisee Reviews->Advisory]
227 | 12/14/18 31d
228 | 12/17/18 46 [P4->P2]
229 | 12/18/18 F18 s1d1
230 | 12/19/18 F18 s1d2
231 | 12/20/18 F18 s1d3
232 | 12/21/18 F18 s1d4
233 | 12/24/18|1/4/19 Winter Break
234 | 1/10/19 4g
235 | 1/21/19 MLKDay
236 | 1/31/19 21f
237 | 2/4/19 41g
238 | 2/7/19 36
239 | 2/8/19 63
240 | 2/11/19|2/18/19 Presidents' Break
241 | 3/8/19 73
242 | 3/12/19 4b
243 | 3/14/19 2j
244 | 3/18/19 4h
245 | 3/20/19 20
246 | 3/22/19 41b
247 | 4/1/19|4/5/19 Spring Break
248 | 4/10/19 4b [Advisee Reviews->ROAR]
249 | 4/12/19 21b
250 | 4/16/19 4c
251 | 4/22/19 4i
252 | 4/24/19 20b
253 | 5/6/19 2b [Advisee Reviews->Office Hours]
254 | 5/7/19 APCSchedule
255 | 5/9/19 APASchedule
256 | 5/10/19 2b [Advisee Reviews->Office Hours]
257 | 5/13/19 APCSchedule
258 | 5/15/19 APASchedule
259 | 5/16/19 2b [Advisee Reviews->Office Hours]
260 | 5/17/19 APCSchedule
261 | 5/23/19 37
262 | 5/24/19 44
263 | 5/27/19 Memorial Day
264 | 5/28/19 51
265 | 5/30/19 38
266 | 5/31/19 Lockers
267 | 6/3/19 7b
268 | 6/4/19 F19 s2d1
269 | 6/5/19 F19 s2d2
270 | 6/6/19 F19 s2d3
271 | 6/7/19|8/22/19 Summer
272 | 8/23/19 Matriculation
273 | 8/29/19 ReCreate
274 | 9/2/19 Labor Day
275 | 9/9/19 2b [Advisee Reviews->Assembly]
276 | 9/11/19 Club Fair
277 | 9/27/19 Fall Break
278 | 10/8/19 2b [Advisee Reviews->Parade]
279 | 10/10/19 62
280 | 10/11/19 XX
281 | 10/16/19 PSAT
282 | 10/21/19 21c
283 | 10/22/19 31e
284 | 10/23/19 44
285 | 10/25/19 20
286 | 10/28/19 Fall Break
287 | 10/30/19 4 [Office Hours->Service Fair]
288 | 11/1/19 2b [Advisee Reviews->School Meeting]
289 | 11/5/19 4e
290 | 11/6/19 1b
291 | 11/11/19 4a
292 | 11/13/19 21
293 | 11/15/19 41c
294 | 11/19/19 2b
295 | 11/21/19 4b
296 | 11/25/19|11/29/19 Thanksgiving Break
297 | 12/10/19 4d
298 | 12/13/19 31f
299 | 12/16/19 44
300 | 12/17/19 F19 s1d1
301 | 12/18/19 F19 s1d2
302 | 12/19/19 F19 s1d3
303 | 12/20/19 F19 s1d4
304 | 12/23/19|1/3/20 Winter Break
305 | 1/9/20 4g
306 |
307 | 1
308 | P1 8:00-9:25
309 | P2 9:35-11:00
310 | Lunch||Senior Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
311 | P3 12:00-1:25
312 | P4 1:35-3:00
313 | Collaboration 3:10-3:30
314 |
315 | 2
316 | P5 8:00-9:25
317 | School Meeting 9:35-10:05
318 | Office Hours 10:10-10:40
319 | P6 10:50-12:15
320 | Lunch 12:20-1:30
321 | P7 1:35-3:00
322 | Collaboration 3:10-3:30
323 |
324 | 3
325 | P1 8:00-9:25
326 | P4 9:35-11:00
327 | Lunch||Junior Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
328 | P2 12:00-1:25
329 | P3 1:35-3:00
330 | Collaboration 3:10-3:30
331 |
332 | 4
333 | P5 8:00-9:25
334 | Advisory 9:35-10:05
335 | Office Hours 10:10-10:40
336 | P7 10:50-12:15
337 | Lunch||Lunch|Club Leadership 12:20-1:30||12:20-1:00|1:00-1:30
338 | P6 1:35-3:00
339 | Collaboration 3:10-3:30
340 |
341 | 5
342 | P1 8:00-9:25
343 | P2 9:35-11:00
344 | Lunch||Soph Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
345 | P3 12:00-1:25
346 | P4 1:35-3:00
347 | Collaboration 3:10-3:30
348 |
349 | 7
350 | P1 8:00-9:25
351 | P4 9:35-11:00
352 | Lunch||Frosh Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
353 | P2 12:00-1:25
354 | P3 1:35-3:00
355 | Collaboration 3:10-3:30
356 |
357 | 7b
358 | P1 8:00-8:40
359 | P2 8:50-9:30
360 | Office Hours 9:35-9:55
361 | P3 10:00-10:40
362 | P4 10:50-11:30
363 | Lunch 11:35-12:35
364 | P5 12:40-1:20
365 | P6 1:30-2:10
366 | P7 2:20-3:00
367 | Collaboration 3:10-3:30
368 |
369 | 4a
370 | P5 8:00-9:25
371 | Office Hours 9:35-10:05
372 | Advisory 10:10-10:40
373 | P7 10:50-12:15
374 | Lunch||Lunch|Club Leaders 12:20-1:30||12:20-1:00|1:00-1:30
375 | P6 1:35-3:00
376 | Collaboration 3:10-3:30
377 |
378 | 7a
379 | P1 8:00-9:25
380 | P2 9:35-11:00
381 | Lunch||Frosh Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
382 | P3 12:00-1:25
383 | P4 1:35-3:00
384 | Collaboration 3:10-3:30
385 |
386 | 5a
387 | P1 8:00-9:25
388 | P4 9:35-11:00
389 | Lunch||Soph Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
390 | P2 12:00-1:25
391 | P3 1:35-3:00
392 | Collaboration 3:10-3:30
393 |
394 | 1a
395 | Late Start - No Classes 8:00-8:50
396 | P1 8:50-9:35
397 | Office Hours 9:45-10:45
398 | P4 10:55-11:40
399 | Lunch 11:45-12:35
400 | P2 12:45-1:30
401 | P3 1:40-2:25
402 | Collaboration 2:35-3:30
403 |
404 | 1b
405 | P1 8:00-9:25
406 | P2 9:35-11:00
407 | Lunch 11:10-11:55
408 | P3 12:00-1:25
409 | P4 1:35-3:00
410 | Collaboration 3:10-3:30
411 |
412 | XX
413 | P1 8:00-9:25
414 | P2 9:35-11:00
415 | Rally 11:10-12:10
416 | Lunch 12:15-1:25
417 | P4 1:35-3:00
418 | Collaboration 3:10-3:30
419 |
420 | 2b
421 | P5 8:00-9:25
422 | Advisee Reviews 9:35-10:40
423 | P6 10:50-12:15
424 | Lunch 12:20-1:30
425 | P7 1:35-3:00
426 | Collaboration 3:10-3:30
427 |
428 | 2c
429 | P5 8:00-9:25
430 | Senior Mtg. (Atrium)||School Mtg. (Gym, G9-11)|Office Hours 9:35-10:40||9:35-10:05|10:10-10:40
431 | P6 10:50-12:15
432 | Lunch 12:20-1:30
433 | P7 1:35-3:00
434 | Collaboration 3:10-3:30
435 |
436 | 2d
437 | P5 8:00-9:25
438 | Senior Mtg.||School Mtg. (G9-G11) 9:35-10:40||9:35-10:40
439 | P6 10:50-12:15
440 | Lunch 12:20-1:30
441 | P7 1:35-3:00
442 | Collaboration 3:10-3:30
443 |
444 | 2e
445 | Late Start - No Classes 8:00-8:50
446 | P5 8:50-9:35
447 | Office Hours 9:45-11:05
448 | P6 11:15-12:00
449 | Lunch 12:05-1:05
450 | P7 1:15-2:00
451 | Office Hours 2:10-3:00
452 |
453 | 2f
454 | P5 8:00-9:25
455 | School Meeting 9:35-10:05
456 | Office Hours 10:10-10:40
457 | P6 10:50-12:15
458 | Parade 12:20-12:45
459 | Lunch 12:45-1:30
460 | P7 1:35-3:00
461 | Collaboration 3:10-3:30
462 |
463 | 2g
464 | P5 8:00-9:25
465 | Frosh Mtg.||School Mtg. 9:35-10:05||9:35-10:05
466 | Office Hours 10:10-10:40
467 | P6 10:50-12:15
468 | Lunch 12:20-1:30
469 | P7 1:35-3:00
470 | Collaboration 3:10-3:30
471 |
472 | 2h
473 | P5 8:00-9:25
474 | School Mtg. 9:35-10:05
475 | Frosh Mtg.||Office Hours 10:10-10:40||10:10-10:40
476 | P6 10:50-12:15
477 | Lunch 12:20-1:30
478 | P7 1:35-3:00
479 | Collaboration 3:10-3:30
480 |
481 | 2i
482 | P5 8:00-9:25
483 | School Meeting 9:35-10:05
484 | Office Hours 10:10-10:25
485 | P6 10:35-12:15
486 | Lunch 12:20-1:30
487 | P7 1:35-3:00
488 | Collaboration 3:10-3:30
489 |
490 | 2j
491 | P5 8:00-9:25
492 | School Meeting 9:35-10:05
493 | Advisee Reviews 10:10-10:40
494 | P6 10:50-12:15
495 | Lunch 12:20-1:30
496 | P7 1:35-3:00
497 | Collaboration 3:10-3:30
498 |
499 | 2k
500 | P5 8:00-9:25
501 | School Meeting 9:35-10:05
502 | P6 10:15-12:15
503 | Lunch 12:20-1:30
504 | P7 1:35-3:00
505 | Collaboration 3:10-3:30
506 |
507 | 3b
508 | P1 8:00-9:25
509 | P4 9:35-11:00
510 | Lunch 11:10-11:55
511 | P2 12:00-1:25
512 | P3 1:35-3:00
513 | Collaboration 3:10-3:30
514 |
515 | 4b
516 | P5 8:00-9:25
517 | Advisee Reviews 9:35-10:40
518 | P7 10:50-12:15
519 | Lunch||Lunch|Club Leaders 12:20-1:30||12:20-1:00|1:00-1:30
520 | P6 1:35-3:00
521 | Collaboration 3:10-3:30
522 |
523 | 4c
524 | P5 8:00-9:25
525 | Senior Mtg.||School Mtg. 9:35-10:40||9:35-10:40
526 | P7 10:50-12:15
527 | Lunch||Lunch|Club Leaders 12:20-1:30||12:20-1:00|1:00-1:30
528 | P6 1:35-3:00
529 | Collaboration 3:10-3:30
530 |
531 | 4d
532 | P5 8:00-9:25
533 | Advisory 9:35-10:40
534 | P7 10:50-12:15
535 | Lunch||Lunch|Club Leaders 12:20-1:30||12:20-1:00|1:00-1:30
536 | P6 1:35-3:00
537 | Collaboration 3:10-3:30
538 |
539 | 4e
540 | P5 8:00-9:25
541 | Frosh Mtg.||Advisory|Office Hours 9:35-10:40||9:35-10:05|10:10-10:40
542 | P7 10:50-12:15
543 | Lunch 12:20-1:30
544 | P6 1:35-3:00
545 | Collaboration 3:10-3:30
546 |
547 | 4f
548 | P5 8:00-9:25
549 | Advisee Reviews 9:35-10:00
550 | Vigil + Activism 10:00-10:45
551 | P7 10:50-12:15
552 | Lunch||Lunch|Club Leaders 12:20-1:30||12:20-1:00|1:00-1:30
553 | P6 1:35-3:00
554 | Collaboration 3:10-3:30
555 |
556 | 4g
557 | P5 8:00-9:25
558 | Senior Mtg.||Advisory (G9-11)| 9:35-10:40||9:35-10:05|10:05-10:40
559 | P7 10:50-12:15
560 | Lunch 12:20-1:30
561 | P6 1:35-3:00
562 | Collaboration 3:10-3:30
563 |
564 | 4h
565 | P5 8:00-9:25
566 | Assembly 9:35-10:05
567 | Office Hours 10:10-10:40
568 | P7 10:50-12:15
569 | Lunch 12:20-1:30
570 | P6 1:35-3:00
571 | Collaboration 3:10-3:30
572 |
573 | 4i
574 | P5 8:00-9:25
575 | Assembly 9:35-10:50
576 | P7 11:00-12:25
577 | Lunch||Lunch|Club Leaders 12:30-1:30||12:30-1:00|1:00-1:30
578 | P6 1:35-3:00
579 | Collaboration 3:10-3:30
580 |
581 | 12
582 | P1 8:00-9:25
583 | Eagle Buddies (G11-12)||LIFE (G9-G10)|Lunch 9:35-11:50||9:35-10:35|10:35-11:50
584 | P3 12:00-1:25
585 | P4 1:35-3:00
586 | Collaboration 3:10-3:30
587 |
588 | 13
589 | Office Hours 9:00-9:25
590 | P2 9:35-11:00
591 | Lunch||Soph Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
592 | P3 12:00-1:25
593 | P4 1:35-3:00
594 | Collaboration 3:10-3:30
595 |
596 | 15
597 | P1 8:00-9:25
598 | P2 9:35-11:00
599 | Lunch||Junior Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
600 | P3 12:00-1:25
601 | P4 1:35-3:00
602 | Collaboration 3:10-3:30
603 |
604 | 20
605 | P5 8:00-9:25
606 | Advisory 9:35-10:05
607 | Office Hours 10:10-10:40
608 | P6 10:50-12:15
609 | Lunch 12:20-1:30
610 | P7 1:35-3:00
611 | Collaboration 3:10-3:30
612 |
613 | 20b
614 | P5 8:00-9:25
615 | Senior Mtg.||Class Speeches 9:35-10:40||9:35-10:40
616 | P6 10:50-12:15
617 | Lunch 12:20-1:30
618 | P7 1:35-3:00
619 | Collaboration 3:10-3:30
620 |
621 | 21
622 | P5 8:00-9:25
623 | P6 9:35-11:00
624 | Eagle Buddies (G11)||LIFE (G9-10/12)|Lunch 11:10-1:25||11:10-12:10|12:10-1:25
625 | P7 1:35-3:00
626 | Collaboration 3:10-3:30
627 |
628 | 21b
629 | P5 8:00-9:25
630 | P6 9:35-11:00
631 | Spirit Rally 11:10-12:10
632 | Lunch 12:20-1:30
633 | P7 1:35-3:00
634 | Collaboration 3:10-3:30
635 |
636 | 21c
637 | P5 8:00-9:25
638 | P6 9:35-11:00
639 | Eagle Buddies (10/12)||LIFE (G9/11)|Lunch 11:10-1:25||11:10-12:10|12:10-1:25
640 | P7 1:35-3:00
641 | Collaboration 3:10-3:30
642 |
643 | 21d
644 | P5 8:00-9:25
645 | P6 9:35-11:00
646 | Assembly 11:10-12:10
647 | Lunch 12:15-1:25
648 | P7 1:35-3:00
649 | Collaboration 3:10-3:30
650 |
651 | 21e
652 | P5 8:00-9:25
653 | P6 9:35-11:00
654 | Eagle Buddies (G10)||LIFE (G9,11-12)|Lunch 10:45-1:25||11:10-12:10|12:10-1:25
655 | P7 1:35-3:00
656 | Collaboration 3:10-3:30
657 |
658 | 21f
659 | P5 8:00-9:25
660 | P6 9:35-11:00
661 | Eagle Buddies (G11/12)||LIFE (G9/10)|Lunch 11:10-1:25||11:10-12:10|12:15-1:25
662 | P7 1:35-3:00
663 | Collaboration 3:10-3:30
664 |
665 | 22
666 | P5 8:00-9:25
667 | Eagle Buddies (G10)||LIFE (G9,11-12)|Lunch 9:35-11:50||9:35-10:35|10:35-11:50
668 | P6 12:00-1:25
669 | P7 1:35-3:00
670 | Collaboration 3:10-3:30
671 |
672 | 25
673 | P5 8:00-9:25
674 | P2 9:35-11:00
675 | Lunch||Senior Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
676 | P6 12:00-1:25
677 | P7 1:35-3:00
678 | Collaboration 3:10-3:30
679 |
680 | 27
681 | P5 8:00-9:25
682 | P2 9:35-11:00
683 | Lunch||Junior Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
684 | P6 12:00-1:25
685 | P7 1:35-3:00
686 | Collaboration 3:10-3:30
687 |
688 | 28
689 | P5 8:00-9:25
690 | P1 9:35-11:00
691 | Lunch 11:10-11:55
692 | P6 12:00-1:25
693 | P7 1:35-3:00
694 | Collaboration 3:10-3:30
695 |
696 | 28b
697 | P5 8:00-9:25
698 | P1 9:35-11:00
699 | Lunch||Senior Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
700 | P6 12:00-1:25
701 | P7 1:35-3:00
702 | Collaboration 3:10-3:30
703 |
704 | 29
705 | P5 8:00-9:25
706 | P2 9:35-11:00
707 | Lunch||Frosh Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
708 | P6 12:00-1:25
709 | P7 1:35-3:00
710 | Collaboration 3:10-3:30
711 |
712 | 31a
713 | P1 8:00-9:25
714 | P4 9:35-11:00
715 | Assembly (9/10)|Lunch||Lunch|Assembly (11/12) 11:10-12:00|12:00-1:25||11:10-12:20|12:30-1:25
716 | P3 1:35-3:00
717 | Collaboration 3:10-3:30
718 |
719 | 31b
720 | P1 8:00-9:25
721 | P4 9:35-11:00
722 | Rally 11:10-12:10
723 | Lunch 12:10-1:25
724 | P3 1:35-3:00
725 | Collaboration 3:10-3:30
726 |
727 | 31c
728 | P1 8:00-9:25
729 | P4 9:35-11:00
730 | Hoscars (9/10)|Lunch||Lunch|Hoscars (11/12) 11:10-12:00|12:00-1:25||11:10-12:20|12:30-1:25
731 | P3 1:35-3:00
732 | Collaboration 3:10-3:30
733 |
734 | 31d
735 | P1 8:00-9:25
736 | P4 9:35-11:00
737 | Lunch|Holiday Show (9/10)||Holiday Show (11/12)|Lunch 11:05-12:15|12:20-1:20||11:05-12:05|12:05-1:30
738 | P3 1:35-3:00
739 | Collaboration 3:10-3:30
740 |
741 | 31e
742 | P1 8:00-9:25
743 | P4 9:35-11:00
744 | Assembly (9/10)|Lunch||Lunch|Assembly (11/12) 11:10-12:10|12:10-1:25||11:10-12:15|12:25-1:25
745 | P3 1:35-3:00
746 | Collaboration 3:10-3:30
747 |
748 | 31f
749 | P1 8:00-9:25
750 | P4 9:35-11:00
751 | Lunch|Holiday Show (11/12)||Holiday Show (9/10)|Lunch 11:10-12:15|12:20-1:20||11:10-12:10|12:10-1:25
752 | P3 1:35-3:00
753 | Collaboration 3:10-3:30
754 |
755 | 32
756 | P1 8:00-9:20
757 | Holiday Show 9:30-10:40
758 | Lunch 10:50-11:50
759 | P2 12:00-1:25
760 | P3 1:35-3:00
761 |
762 | 32b
763 | P1 8:00-9:25
764 | Rally 9:35-10:35
765 | Lunch 10:35-11:50
766 | P2 12:00-1:25
767 | P3 1:35-3:00
768 |
769 | 33
770 | P1 8:50-9:35
771 | Office Hours 9:45-10:45
772 | P4 10:55-11:40
773 | Lunch 11:40-12:35
774 | P2 12:45-1:30
775 | P3 1:40-2:25
776 | Office Hours 2:35-3:00
777 |
778 | 36
779 | AMC|| |Office Hours 8:00-9:25||8:00-8:58|9:00-9:25
780 | P4 9:35-11:00
781 | Lunch||Frosh Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
782 | P2 12:00-1:25
783 | P3 1:35-3:00
784 | Collaboration 3:10-3:30
785 |
786 | 37
787 | P1 8:00-9:25
788 | P4 9:35-11:00
789 | Lunch 11:10-11:55
790 | P3 12:00-1:25
791 |
792 | 38
793 | P1 8:00-9:25
794 | P4 9:35-11:00
795 | Lunch 11:10-11:55
796 | P2 12:00-1:25
797 | P3 1:35-3:00
798 | Collaboration 3:10-3:30
799 |
800 | 40
801 | P5 8:00-9:25
802 | Honor Council Assembly 9:35-10:40
803 | P7 10:50-12:15
804 | Lunch||Lunch|Club Leaders 12:20-1:30||12:20-1:00|1:00-1:30
805 | P6 1:35-3:00
806 | Collaboration 3:10-3:30
807 |
808 | 40b
809 | P5 8:00-9:25
810 | P7 9:35-11:00
811 | Honor Council Assembly 11:10-12:15
812 | Lunch||Lunch|Club Leaders 12:20-1:30||12:20-1:00|1:00-1:30
813 | P6 1:35-3:00
814 | Collaboration 3:10-3:30
815 |
816 | 41
817 | P5 8:00-9:25
818 | P7 9:35-11:00
819 | Eagle Buddies (G11)||LIFE (G9-10/12)|Lunch 11:10-1:25||11:10-12:10|12:10-1:25
820 | P6 1:35-3:00
821 | Collaboration 3:10-3:30
822 |
823 | 41b
824 | P5 8:00-9:25
825 | P7 9:35-11:00
826 | Eagle Buddies (G11/12)||LIFE (G9/10)|Lunch 11:10-1:25||11:10-12:10|12:10-1:25
827 | P6 1:35-3:00
828 | Collaboration 3:10-3:30
829 |
830 | 41c
831 | P5 8:00-9:25
832 | P7 9:35-11:00
833 | Assembly 11:10-12:10
834 | Lunch 12:15-1:25
835 | P6 1:35-3:00
836 | Collaboration 3:10-3:30
837 |
838 | 41d
839 | P5 8:00-9:25
840 | P7 9:35-11:00
841 | Rally 11:10-12:10
842 | Lunch 12:15-1:25
843 | P6 1:35-3:00
844 | Collaboration 3:10-3:30
845 |
846 | 41f
847 | P5 8:00-9:25
848 | P7 9:35-11:00
849 | Eagle Buddies (G10/12)||LIFE (G9/11)|Lunch 11:10-1:25||11:10-12:10|12:10-1:25
850 | P6 1:35-3:00
851 | Collaboration 3:10-3:30
852 |
853 | 41g
854 | P5 8:00-9:25
855 | P7 9:35-11:00
856 | Eagle Buddies (G10)||LIFE (G9/11-12)|Lunch 11:10-1:25||11:10-12:10|12:10-1:25
857 | P6 1:35-3:00
858 | Collaboration 3:10-3:30
859 |
860 | 44
861 | P5 8:00-9:25
862 | P2 9:35-11:00
863 | Lunch||Frosh Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
864 | P7 12:00-1:25
865 | P6 1:35-3:00
866 | Collaboration 3:10-3:30
867 |
868 | 45
869 | P5 8:00-9:25
870 | P4 9:35-11:00
871 | Lunch||Junior Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
872 | P7 12:00-1:25
873 | P6 1:35-3:00
874 | Collaboration 3:10-3:30
875 |
876 | 46
877 | P5 8:00-9:25
878 | P4 9:35-11:00
879 | Lunch||Frosh Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
880 | P7 12:00-1:25
881 | P6 1:35-3:00
882 | Collaboration 3:10-3:30
883 |
884 | 47
885 | P5 8:00-9:25
886 | P7 9:35-11:00
887 | Lunch 11:10-11:50
888 | P6 12:00-1:25
889 |
890 | 48
891 | P5 8:50-9:35
892 | Office Hours 9:45-11:05
893 | P7 11:15-12:00
894 | Lunch 12:00-1:05
895 | P6 1:15-2:00
896 | Office Hours 2:10-3:30
897 |
898 | 49
899 | P5 8:00-9:25
900 | P7 9:35-11:00
901 | Assembly (G9-10)|Lunch||Lunch|Assembly (G11-12) 11:10-12:10|12:10-1:25||11:10-12:15|12:25-1:25
902 | P6 1:35-3:00
903 | Collaboration 3:10-3:30
904 |
905 | 51
906 | P1 8:00-9:25
907 | P2 9:35-11:00
908 | Lunch 11:10-11:55
909 | P3 12:00-1:25
910 | P4 1:35-3:00
911 | Collaboration 3:10-3:30
912 |
913 | 61
914 | P5 8:00-8:55
915 | P7 9:05-10:00
916 | Spirit Rally 10:10-11:10
917 | Lunch 11:10-12:00
918 | P6 12:05-1:00
919 |
920 | 62
921 | P5 8:00-9:25
922 | P3 9:35-11:00
923 | Lunch||Senior Mtg.|Lunch 11:10-11:55||11:10-11:25|11:25-11:55
924 | P7 12:00-1:25
925 | P6 1:35-3:00
926 | Collaboration 3:10-3:30
927 |
928 | 63
929 | P5 8:00-9:25
930 | P1 9:35-11:00
931 | Lunch 11:10-11:55
932 | P7 12:00-1:25
933 | P6 1:35-3:00
934 | Collaboration 3:10-3:30
935 |
936 | 71
937 | P5 8:00-9:25
938 | Office Hours 9:35-10:05
939 | P6 10:15-11:40
940 | Lunch 11:50-12:55
941 | P7 1:00-2:25
942 | Collaboration 2:35-3:30
943 |
944 | 72
945 | P5 8:00-9:25
946 | P6 9:35-11:00
947 | Lunch 11:10-11:50
948 | P7 12:00-1:25
949 |
950 | 73
951 | P5 8:00-9:25
952 | P6 9:35-11:00
953 | Hoscars (G11/12)|Lunch (G11/12)||Lunch (G9/10)|Hoscars (G9/10) 11:10-12:10|12:10-1:25||11:10-12:15|12:25-1:25
954 | P7 1:35-3:00
955 | Collaboration 3:10-3:30
956 |
957 | F16 s1d1
958 | English 8:00-12:30
959 | Lunch 12:30-1:30
960 | Language 1:30-3:30
961 |
962 | F16 s1d2
963 | Math 8:00-12:30
964 | Lunch 12:30-1:30
965 | Computer Science 1:30-3:30
966 |
967 | F16 s1d3
968 | Chemistry (G10)/Biology 8:00-12:30
969 | Lunch 12:30-1:30
970 | Physics (G9)/Economics 1:30-3:30
971 |
972 | F16 s1d4
973 | US/Euro/World 2 8:00-12:30
974 | Lunch 12:30-1:30
975 | World 1/Chem (G11)/Physics (G10-12) 1:30-3:30
976 |
977 | F17 s2d1
978 | World 1 / Hon World 1 8:00-10:00
979 | World 2 / Hon World 2 / US / Hon US 10:30-12:30
980 | Lunch 12:30-1:30
981 | Physics / Hon Physics / Prog / Adv Prog / Econ 1:30-3:30
982 |
983 | F17 s2d2
984 | Chinese / French / Japanese / Latin 8:00-10:00
985 | Spanish 10:30-12:30
986 | Lunch 12:30-1:30
987 | Chem / Hon Chem / Bio / Hon Bio 1:30-3:30
988 |
989 | F17 s2d3
990 | Alg 2 / Hon Alg 2 8:00-10:00
991 | Alg 1 / Geo / Hon Geo / Precalc| || |Hon Precalc 9:00-11:00|11:00-12:30||9:00-10:30|10:30-12:30
992 | Lunch 12:30-1:30
993 |
994 | F17 s1d1
995 | Geo / Alg 2 / Hon Alg 2 / Precalc (Momenian) 8:00-10:00
996 | AP Calc AB / AP Calc BC / Hon Calc C| || |Honors Precalc / Precalc (Thiele) / Calc 9:00-11:00|11:00-12:30||9:00-10:30|10:30-12:30
997 | Lunch 12:30-1:30
998 | Alg 1 / Hon Geo / Prog / Adv Prog / AP CSA DS 1:30-3:30
999 |
1000 | F17 s1d2
1001 | Hon Chem / AP Chem (10G) 8:00-10:00
1002 | Chemistry / Hon Biology (Pistacchi)| || |AP Bio (11G) / Biology / Hon Biology (Blickenstaff, Harley) 9:00-11:00|11:00-12:30||9:00-10:30|10:30-12:30
1003 | Lunch 12:30-1:30
1004 | Physics / Hon Physics / AP Physics C / AP Chem (11G) 1:30-3:30
1005 |
1006 | F17 s1d3
1007 | English 1 / Hon English 1 8:00-10:00
1008 | English 2 / Hon English 2 (Siraganian, Hurshman)| || |Hon English 2 (Manning) / English 3 / Hon English 3 9:00-11:00|11:00-12:30||9:00-10:30|10:30-12:30
1009 | Lunch 12:30-1:30
1010 | Chinese / French / Japanese / Latin / Spanish 1:30-3:30
1011 |
1012 | F17 s1d4
1013 | Hon US / AP US (Wheeler) / US History 8:00-10:00
1014 | AP US (Rees) / World History 2| || |Hon World 2 / AP Euro / AP World 9:00-11:00|11:00-12:30||9:00-10:30|10:30-12:30
1015 | Lunch 12:30-1:30
1016 | World 1 / Hon World 1 / Economics 1:30-3:30
1017 |
1018 | F18 s2d1
1019 | Econ / Physics / Hon Phys (Brada,Radice)| || |Prog / Adv Prog / Hon Phys (Allersma) 8:00-10:00|10:00-11:00||8:00-9:00|9:00-11:00
1020 | Lunch 11:00-12:30
1021 | World 1 / Hon World 1 / Hon World 2 / US His / Hon US His 12:30-2:30
1022 |
1023 | F18 s2d2
1024 | Spanish / French 2 / French 4 / Hon French 4| || |Chinese / Japanese / Latin / French 1 / French 3 / Hon Fr 3 8:00-10:00|10:00-11:00||8:00-9:00|9:00-11:00
1025 | Lunch 11:00-12:30
1026 | Chem / Hon Chem / Hon Bio 12:30-2:30
1027 |
1028 | F18 s2d3
1029 | Alg 1 / Geo / Hon Geo / Hon Alg 2| || |Alg 2 / Precalculus / Hon Precalc (Adler) 8:00-10:00|10:00-10:30||8:00-9:00|9:00-11:00
1030 | Hon Precalc (Lieberman, Muldrew, Stahl) / Calc| || |Lunch 10:30-12:30|12:30-1:00||10:30-11:00|11:00-1:00
1031 |
1032 | F18 s1d1
1033 | AP Bio (11) / Bio / Hon Bio / Hon Chem| || |Hon Bio (Harley P3, P6) / Chem / Hon Physics (Allersma) 8:00-10:00|10:00-10:30||8:00-9:00|9:00-11:00
1034 | AP Chem (10) / Hon Phys (Brada, Radice) / Physics 10:30-12:30
1035 | Lunch 12:30-1:30
1036 | Adv Prog / Prog / AP CS / Behav Econ / AP Chem (11) / AP Phys C 1:30-3:30
1037 |
1038 | F18 s1d2
1039 | Alg 1 / Alg 2 / Hon Alg 2 / Geo / Hon Geo| || |AP Calc AB / AP Calc BC / Calc C 8:00-10:00|10:00-10:30||8:00-9:00|9:00-11:00
1040 | Calc / Precalc / Hon Precalc 10:30-12:30
1041 | Lunch 12:30-1:30
1042 | Chinese / French / Japanese / Latin / Spanish 1:30-3:30
1043 |
1044 | F18 s1d3
1045 | Hon English 1 / Eng 3 (Manjoine) / Hon English 3| || |English 1 / English 3 (Barth, Docherty) 8:00-10:00|10:00-10:30||8:00-9:00|9:00-11:00
1046 | English 2 / Hon English 2 10:30-12:30
1047 | Lunch 12:30-1:30
1048 | World 1 / Hon World 1 / World 2 / Hon World 2 / AP Euro / AP World 1:30-3:30
1049 |
1050 | F18 s1d4
1051 | US History / Hon US History / AP US History 8:00-10:00
1052 |
1053 | F19 s2d1
1054 | World 1 / Hon World 1 (Meyer, Milius)| || |Hon World 1 (Hull) / Chem / Hon Chem 8:00-10:00|10:00-11:00||8:00-9:00|9:00-11:00
1055 | Lunch 11:00-12:30
1056 | Prog / Adv Prog / Econ / Behav Econ 12:30-2:30
1057 |
1058 | F19 s2d2
1059 | Hon Bio (Artiss, Harley) / Hon Phys (Allersma, Brada)| || |Bio / Hon Bio (Pistacchi) / Physics / Hon Physics (Radice) 8:00-10:00|10:00-11:00||8:00-9:00|9:00-11:00
1060 | Lunch 11:00-12:30
1061 | Math 12:30-2:30
1062 |
1063 | F19 s2d3
1064 | Mandarin / Spanish| || |French / Latin 8:00-10:00|10:00-10:30||8:00-9:00|9:00-11:00
1065 | Lunch 11:00-12:30
1066 |
1067 | F19 s1d1
1068 | Alg 1 / Alg 2 / Hon Alg 2 / Geo / Hon Geo| || |AP Calc AB / AP Calc BC 8:00-10:00|10:00-10:30||8:00-9:00|9:00-11:00
1069 | Calc / Precalc / Hon Precalc 10:30-12:30
1070 | Lunch 12:30-1:30
1071 | AP CS / Behav Econ / Econ 1:30-3:30
1072 |
1073 | F19 s1d2
1074 | Chem / Hon Chem / Hon Bio| || |AP Bio (G11) / Bio / Hon Phys (Allersma) 8:00-10:00|10:00-10:30||8:00-9:00|9:00-11:00
1075 | AP Chem (10) / Hon Phys (Brada, Radice) / Physics 10:30-12:30
1076 | Lunch 12:30-1:30
1077 | Chinese / French / Japanese / Latin / Spanish 1:30-3:30
1078 |
1079 | F19 s1d3
1080 | AP Euro (Gilbert) / APUSH (Rees, Wheeler) / US Hist / Hon US Hist| || |AP Euro (Stevens) / APUSH (Halback) / World 2 / Hon World 2 8:00-10:00|10:00-10:30||8:00-9:00|9:00-11:00
1081 | AP World / World 1 / Hon World 1 10:30-12:30
1082 | Lunch 12:30-1:30
1083 | AP Phys C 1:30-3:30
1084 |
1085 | F19 s1d4
1086 | Hon Eng 1 / Hon Eng 2| || |Eng 1 / Eng 2 8:00-10:00|10:00-10:30||8:00-9:00|9:00-11:00
1087 | Eng 3 / Hon Eng 3 10:30-12:30
1088 |
1089 | Club Fair
1090 | P5 8:00-9:25
1091 | Advisory 9:35-10:05
1092 | Office Hours 10:10-10:40
1093 | P7 10:50-12:15
1094 | Lunch|| |Club Fair 12:20-1:30||12:20-12:30|12:30-1:30
1095 | P6 1:35-3:00
1096 | Collaboration 3:10-3:30
1097 |
1098 | Matriculation
1099 | Matriculation Ceremony 9:45-10:30
1100 | Advisory 10:30-11:30
1101 | Lunch 11:30-12:15
1102 | Meeting/Photos 12:15-1:00
1103 | Photos 1:00-2:00
1104 |
1105 | ReCreate
1106 | P5 8:00-9:25
1107 | Advisory/ReCreate 9:35-10:40
1108 | P7 10:50-12:15
1109 | Lunch||Lunch|Club Leaders 12:20-1:30||12:20-1:00|1:00-1:30
1110 | P6 1:35-3:00
1111 | Collaboration 3:10-3:30
1112 |
1113 | Matriculation
1114 | Matriculation (All Students) 9:00-1:00
1115 | Matriculation (Grades 9-11) 1:00-2:00
1116 | Matriculation (New Students) 2:00-2:30
1117 |
1118 | Matriculation 2017
1119 | Matriculation (All Students) 9:00-2:00
1120 | Matriculation (Grade 9) 2:00-2:30
1121 | Matriculation (New Students) 2:30-3:00xx
1122 |
1123 | APASchedule
1124 | P1 8:00-9:25
1125 | P2 9:35-11:00
1126 | Lunch 11:10-11:55
1127 | P3 12:00-1:25
1128 | P4 1:35-3:00
1129 | Collaboration 3:10-3:30
1130 |
1131 | APCSchedule
1132 | P1 8:00-9:25
1133 | P4 9:35-11:00
1134 | Lunch 11:10-11:55
1135 | P2 12:00-1:25
1136 | P3 1:35-3:00
1137 | Collaboration 3:10-3:30
1138 |
1139 | Lockers
1140 | P5 8:00-9:25
1141 | Advisory 9:35-10:40
1142 | P7 10:50-12:15
1143 | Lunch||Lunch|Club Leaders 12:20-1:30||12:20-1:00|1:00-1:30
1144 | P6 1:35-3:00
1145 | After School 3:10-3:30
1146 |
--------------------------------------------------------------------------------
/ms/scripts/script.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Primary script for the Harker Bell Schedule
3 | * Hosted at http://harkerdev.github.io/bellschedule
4 | **/
5 |
6 | /**
7 | * CSS things
8 | */
9 | addEventListener("scroll", function (event) {
10 | document.getElementById("header").style.left = scrollX + "px";
11 | });
12 |
13 | /**
14 | * Returns an array of values in the array that aren't in a.
15 | */
16 | Array.prototype.diff = function (a) {
17 | return this.filter(function (i) {
18 | return a.indexOf(i) < 0;
19 | });
20 | };
21 |
22 | /**
23 | * Constants
24 | */
25 | var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; //days of the week in string form
26 | var schedules; //array of schedules (each schedule is an array in this array
27 | var mobile = isMobile();
28 | var rawSchedule = "";
29 | var titles = [];
30 | var titleStr = "";
31 | var forceTitle = false;
32 | var futureWarning = "
Future schedules may be incorrect.";
33 | var dogeCounter = 7;
34 | /**
35 | * Globals
36 | */
37 | var displayDate; //beginning of time period currently being displayed by the schedule
38 | var updateScheduleID; //ID of interval of updateSchedule
39 | var hasFocus = true; //document.hasFocus() seems to be unreliable; assumes window has focus on page load
40 | var options = {};
41 |
42 | var urlParams; //object with GET variables as properties and their respective values as values
43 |
44 | var inputStr = "";
45 |
46 | var KEY_LEFT = 37;
47 | var KEY_UP = 38;
48 | var KEY_RIGHT = 39;
49 | var KEY_DOWN = 40;
50 | var KEY_A = 65;
51 | var KEY_B = 66;
52 | var KONAMI = "" + KEY_UP + KEY_UP + KEY_DOWN + KEY_DOWN + KEY_LEFT + KEY_RIGHT + KEY_LEFT + KEY_RIGHT + KEY_B + KEY_A;
53 | var isDoge;
54 |
55 | var START_DATE = new Date('September 24, 2018'); //The start day of the school year. This should be a weekday.
56 |
57 | var START_SCHEDULE = 1; //The schedule on the first day
58 |
59 | var LINKS = {
60 | "Lunch 1 (7/8th)": "https://bokken12.github.io/harker-lunch/#$DAYNAMELOWER$",
61 | "Lunch 2 (6th)": "https://bokken12.github.io/harker-lunch/#$DAYNAMELOWER$"
62 | }
63 |
64 | // //On a given day, independent of rotation, after school has a fixed function. This array maps the day (0 for Monday, etc.)
65 | // //to the particular function (e.g. Extra Help). This ultimately piggybacks on the replacement system.
66 | // var COLLABORATION_REPLACEMENTS = [
67 | // "Collaboration -> Office Hours",
68 | // "Collaboration -> Office Hours",
69 | // "Collaboration -> Faculty Meeting",
70 | // "Collaboration -> Office Hours",
71 | // "Collaboration -> After School"
72 | // ];
73 |
74 | var TOTAL_SCHEDULES = 5; //The number of schedules to be cycled
75 |
76 | var SCHEDULES = ["Meeting", "Regular/Advisory", "Faculty", "Regular/Advisory", "Advisory/Assembly"];
77 |
78 | var MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
79 |
80 | /**
81 | * Gets GET variables from URL and sets them as properties of the urlParams object.
82 | * Then updates the state of the current history entry with the appropriate week.
83 | */
84 | (function () {
85 | //decode GET vars in URL
86 | updateUrlParams();
87 |
88 | //update history state
89 | window.history.replaceState(getDateFromUrlParams(), document.title, document.location);
90 | }());
91 |
92 | /**
93 | * Event listeners
94 | */
95 | document.addEventListener("visibilitychange", function (event) {
96 | if (!document.hidden) { //only slightly redundant; on un-minimize, document gains visibility without focus
97 | updateSchedule();
98 | // updateClock();
99 | }
100 | updateUpdateInterval();
101 | });
102 |
103 | addEventListener("focus", function (event) {
104 | updateSchedule();
105 | //updateClock();
106 | hasFocus = true;
107 | updateUpdateInterval();
108 | });
109 |
110 | addEventListener("blur", function (event) {
111 | hasFocus = false;
112 | updateUpdateInterval();
113 | });
114 |
115 | /**
116 | * Event listener for navigating through history.
117 | * (onload event will not fire when navigating through history items pushed by history.pushState, because the page does not reload)
118 | */
119 | addEventListener("popstate", function (event) {
120 | updateUrlParams();
121 | updateSchedule(event.state);
122 | });
123 |
124 | /**
125 | * Updates urlParams object based on the GET variables in the URL.
126 | * (variables as properties and values as values)
127 | */
128 | function updateUrlParams() {
129 | urlParams = {};
130 |
131 | var match,
132 | pl = /(?!^)\+/g, //regex for replacing non-leading + with space
133 | search = /([^&=]+)=?([^&]*)/g,
134 | decode = function (s) {
135 | return decodeURIComponent(s.replace(pl, " "));
136 | },
137 | query = location.search.substring(1);
138 |
139 | while (match = search.exec(query)) {
140 | urlParams[decode(match[1])] = decode(match[2]);
141 | }
142 | }
143 |
144 | /**
145 | * Parses schedules, creates schedule for correct week, sets title title on page load.
146 | */
147 | addEventListener("load", function (event) {
148 | initViewport();
149 | initTitle();
150 | $.ajax({url: "special.txt", success: function(data) {
151 | parseRawSchedule(data);
152 | $.ajax({url: "options.json", success: function(data) {
153 | createOptions(JSON.stringify(data));
154 | }, cache: false});
155 | }, cache: false});
156 | });
157 |
158 | function error() {
159 | console.log("error downloading");
160 | }
161 |
162 | function initViewport() {
163 | if (mobile) {
164 | var meta = document.createElement("meta");
165 | meta.name = "viewport";
166 | meta.content = 'user-scalable=no, initial-scale=1.0, maximum-scale=1.0';
167 | document.getElementsByTagName("head")[0].appendChild(meta);
168 | document.getElementsByTagName("body")[0].class = "mobile";
169 | }
170 | }
171 |
172 | /**
173 | * Adds appropriate event listeners to items in the schedule title.
174 | */
175 | log = console.log.bind(console);
176 | function initTitle() {
177 | document.getElementById("leftArrow").addEventListener("click", goLast);
178 | document.getElementById("rightArrow").addEventListener("click", goNext);
179 |
180 | document.getElementById("refresh").addEventListener("click", function () {
181 | updateSchedule(null, true);
182 | });
183 |
184 | $.ajax({url: "titles.txt", success: function(data) {
185 | titleStr = data;
186 | }, cache: false});
187 | }
188 |
189 | function checkDoge() {
190 | dogeCounter -= 1;
191 | if(dogeCounter == 0) {
192 | setDoge(true);
193 | setTitleTitle("doge")
194 | } else if(dogeCounter < 0) {
195 | setDoge(false);
196 | dogeCounter = 7;
197 | setTitleTitle(titleStr)
198 | } else {
199 | setTitleTitle("You are " + dogeCounter + (dogeCounter == 1 ? " step" : " steps") + " away from becoming a developer!")
200 | }
201 | }
202 |
203 | /**
204 | * Parses raw schedule in body of page into schedule array
205 | * Code is questionable <- ya think
206 | */
207 | function parseRawSchedule(data) {
208 | var rawSchedules = data.split("\n"); //get raw schedule text
209 | //var rawSchedules = document.getElementById("schedules").textContent.split("\n"); //get raw schedule text
210 | schedules = [];
211 | var x = 0; //index in schedules
212 | schedules[0] = []; //create array of special schedule days
213 |
214 | //loop through all lines in raw schedule text
215 | while (rawSchedules.length > 0) {
216 | if (rawSchedules[0].length === 0) {
217 | //if line is empty, move to next index in schedules
218 | schedules[++x] = []; //could probably use id as index instead, or just properties
219 | rawSchedules.shift();
220 | } else {
221 | //if line has text, save in current location in schedules
222 | var str = rawSchedules.shift();
223 | if (x === 0 && str.indexOf("|") >= 0) {
224 | //behavior for blocks of dates with the same schedule
225 | var start = new Date(str.substring(0, str.indexOf("|")));
226 | var end = new Date(str.substring(str.indexOf("|") + 1, str.indexOf("\t")));
227 | for (; start <= end; start.setDate(start.getDate() + 1)) {
228 | schedules[0].push(start.getMonth().valueOf() + 1 +
229 | "/" + start.getDate() +
230 | "/" + start.getFullYear().toString().substr(-2) +
231 | str.substring(str.indexOf("\t")));
232 | }
233 | } else {
234 | schedules[x].push(str);
235 | }
236 | }
237 | }
238 | //callback();
239 | }
240 |
241 | /**
242 | * Displays schedule of the week of the given date/time
243 | */
244 | function setDisplayDate(time, force) {
245 | var date = (time ? new Date(time) : getDateFromUrlParams()); //variable to keep track of current day in loop
246 | setDayBeginning(date);
247 | if (force || !displayDate || (date.valueOf() != displayDate.valueOf())) {
248 | var schedule = document.getElementById("schedule"); //get schedule table
249 | displayDate = new Date(date);
250 | while (schedule.rows.length) {
251 | schedule.deleteRow(-1); //clear existing weeks (rows); there should only be one, but just in case...
252 | }
253 |
254 | var week = schedule.insertRow(-1); //create new week (row)
255 |
256 | if (!options.enableDayView) {
257 | date = getMonday(date);
258 | for (var d = 0; d < 5; d++) {
259 | //for each day Monday through Friday (inclusive)
260 | createDay(week, date);
261 | date.setDate(date.getDate() + 1); //increment day
262 | }
263 | } else {
264 | createDay(week, date);
265 | }
266 | }
267 | }
268 |
269 | /**
270 | * Returns a Date object based on the current urlParams (GET variables in the URL).
271 | * If any part of the date is not specified, defaults to the current date/month/year.
272 | * If in week view, uses the Monday of the week instead of the day.
273 | */
274 | function getDateFromUrlParams() {
275 | var date = new Date();
276 |
277 | if (urlParams.y > 0 && urlParams.m > 0 && urlParams.d > 0) {
278 | date = new Date("20" + urlParams.y, urlParams.m - 1, urlParams.d);
279 | }
280 |
281 | if (urlParams.m > 0 && urlParams.d > 0) {
282 | date = new Date((new Date()).getFullYear(), urlParams.m - 1, urlParams.d);
283 | }
284 |
285 | if (!options.enableDayView) {
286 | date = getMonday(date);
287 | }
288 | return date;
289 | }
290 |
291 | /**
292 | * Displays the given warning or hides the warning div if no warning text is given.
293 | */
294 | function warn(text) {
295 | var warning = document.getElementById("warning");
296 |
297 | if (text) {
298 | warning.style.display = "block";
299 | } else {
300 | warning.style.display = "none";
301 | }
302 |
303 | warning.innerHTML = text;
304 | }
305 |
306 | /**
307 | * Creates the day for the given date and appends it to the given week
308 | */
309 | function createDay(week, date) {
310 | var daySchedule = getDayInfo(date); //get schedule for that day
311 |
312 | var col = week.insertCell(-1); //create cell for day
313 | col.date = date.valueOf(); //store date in cell element
314 |
315 | //check Halloween
316 | if (date.getMonth() == 9 && date.getDate() == 31) {
317 | col.classList.add("halloween");
318 | }
319 |
320 | var head = document.createElement("div"); //create header div in cell
321 | head.classList.add("head");
322 | var headWrapper = document.createElement("div");
323 | headWrapper.classList.add("headWrapper");
324 | var scheduleString = ""; //Should we display the schedule id (e.g. A) next to the date
325 | //Make sure not to display anything if daySchedule is empty
326 | if (typeof daySchedule.name != 'undefined' || daySchedule.name !== null) {
327 | //Not a weekend, so add
328 | scheduleString = "(" + daySchedule.name + ")";
329 | }
330 | if (scheduleString === "()") {
331 | //It's a holiday so delete the extra parenthesis
332 | scheduleString = "";
333 | }
334 | headWrapper.innerHTML = days[date.getDay()] + "" +
335 | daySchedule.dateString + " " + scheduleString + "
"; //Portion commented out represents schedule id of that day
336 | head.appendChild(headWrapper);
337 | col.appendChild(head);
338 |
339 | var prevEnd = "8:00"; //set start of day to 8:00AM
340 | // for sub periods, passing periods are already handled and do not need to be added in the next iteration
341 |
342 | if (daySchedule.index > 0) { //populates cell with day's schedule (a bit messily)
343 | for (var i = 1; i < schedules[daySchedule.index].length; i++) {
344 | var text = schedules[daySchedule.index][i];
345 | var periodName = makePeriodNameReplacements(text.substring(0, text.indexOf("\t")), daySchedule.replacements);
346 | var periodTime = text.substring(text.indexOf("\t") + 1);
347 |
348 | var start = periodTime.substring(0, periodTime.indexOf("-"));
349 | var end = periodTime.substring(periodTime.lastIndexOf("-") + 1);
350 |
351 | // only creates a new passing period before the period if either 1) it's a split lunch period in the new schedule or
352 | // 2) the date is not within the bounds of the new schedule
353 | if (options.showPassingPeriods) {
354 | var passing = document.createElement("div");
355 | passing.classList.add("period");
356 | createPeriod(passing, "", prevEnd, start, date);
357 | col.appendChild(passing);
358 | }
359 |
360 | prevEnd = end;
361 |
362 | var period = document.createElement("div");
363 | period.classList.add("period");
364 |
365 | if (periodName.indexOf("|") >= 0) {
366 | //handle split periods (i.e. lunches)
367 | var table = document.createElement("table");
368 | table.classList.add("lunch");
369 | var row = table.insertRow(-1);
370 |
371 | var period1 = row.insertCell(-1);
372 | var period2 = row.insertCell(-1);
373 |
374 | var period1Time = periodTime.substring(0, periodTime.indexOf("||"));
375 | var period2Time = periodTime.substring(periodTime.indexOf("||") + 2);
376 |
377 | var period1Name = periodName.substring(0, periodName.indexOf("||"));
378 | var period2Name = periodName.substring(periodName.indexOf("||") + 2);
379 |
380 | //If there are two sets of subperiods (i.e. a|b||c|d) there should be 4 "|"s
381 | if (findNumberOfOccurences(periodName, "|") == 4) {
382 | show1Time = true;
383 | show2Time = true;
384 |
385 | createSubPeriods(
386 | period1,
387 | period1Name,
388 | start,
389 | period1Time.substring(period1Time.indexOf("-") + 1, period1Time.indexOf("|")),
390 | period1Time.substring(period1Time.indexOf("|") + 1, period1Time.lastIndexOf("-")),
391 | end,
392 | date,
393 | show1Time,
394 | show2Time
395 | );
396 |
397 | createSubPeriods(
398 | period2,
399 | period2Name,
400 | start,
401 | period2Time.substring(period2Time.indexOf("-") + 1, period2Time.indexOf("|")),
402 | period2Time.substring(period2Time.indexOf("|") + 1, period2Time.lastIndexOf("-")),
403 | end,
404 | date,
405 | show1Time,
406 | show2Time
407 | );
408 | } else {
409 | //parent, name, start, end, date
410 | createPeriod(
411 | period1,
412 | period1Name,
413 | start,
414 | end,
415 | date,
416 | true,
417 | true
418 | );
419 |
420 | show1Time = daySchedule.id == 4 || daySchedule.id == "ReCreate";
421 | show2Time = !(show1Time);
422 | //parent, name, start1, end1, start2, end2, date, show 1st, show 2nd
423 | createSubPeriods(
424 | period2,
425 | period2Name,
426 | start,
427 | period2Time.substring(period2Time.indexOf("-") + 1, period2Time.indexOf("|")),
428 | period2Time.substring(period2Time.indexOf("|") + 1, period2Time.lastIndexOf("-")),
429 | end,
430 | date,
431 | show1Time,
432 | show2Time
433 | );
434 | }
435 | period.appendChild(table);
436 | } else {
437 | createPeriod(period, periodName, start, end, date);
438 | //parent, name, start, end, date
439 | //createSubPeriods(
440 | //lunch2,
441 | //periodName.substring(periodName.indexOf("||")+2),
442 | //start,
443 | //lunch2Time.substring(lunch2Time.indexOf("-")+1,lunch2Time.indexOf("|")),
444 | //lunch2Time.substring(lunch2Time.indexOf("|")+1,lunch2Time.lastIndexOf("-")),
445 | //end,
446 | //date
447 | //);
448 | }
449 | col.appendChild(period);
450 | }
451 | }
452 | }
453 |
454 | /**
455 | * Returns new name for period based on array of replacements.
456 | * If the current period name is listed in the array of replacements, returns the new, replaced name; otherwise, returns current name.
457 | * replacements is an array of strings of the form "OldName->NewName"
458 | */
459 | function makePeriodNameReplacements(periodName, replacements) {
460 | if (replacements.length > 0) {
461 | for (var i = 0; i < replacements.length; i++) {
462 | if (!replacements[i].indexOf(periodName)) {
463 | return replacements[i].substring(replacements[i].indexOf("->") + 2);
464 | }
465 | }
466 | }
467 | return periodName;
468 | }
469 |
470 | /**
471 | * Sets the title of the title to a random line from the title titles list
472 | */
473 | function setTitleTitle(data, time) {
474 | var date = (time ? new Date(time) : getDateFromUrlParams());
475 | var titles = data.split("\n");
476 | if (titles.length > 1) titles.pop();
477 | displayMessage = titles[Math.floor(Math.random() * titles.length)];
478 | // displayMessage = "Join HarkerDev at tiny.cc/joindev";
479 | if (getMonday(date) > getMonday(new Date())) {
480 | displayMessage += futureWarning; //display warning if date is in the future
481 | }
482 | warn(displayMessage);
483 | }
484 |
485 | /**
486 | * Returns the Monday of next week if date is Saturday; else returns the Monday of that week
487 | */
488 | function getMonday(d) {
489 | var date = new Date(d);
490 | if (date.getDay() >= 6) {
491 | date.setDate(date.getDate() + 2); //set date to next Monday if today is Saturday
492 | } else {
493 | date.setDate(date.getDate() - date.getDay() + 1); //else set date Monday of this week
494 | }
495 | setDayBeginning(date); //set to beginning of day
496 | return date;
497 | }
498 |
499 | /**
500 | * Sets given date to beginning of the day (12:00 AM).
501 | */
502 | function setDayBeginning(date) {
503 | date.setHours(0, 0, 0, 0);
504 | }
505 |
506 | /**
507 | * Takes in a date and a string of form "hh:MM" and turns it into a time on the day of the given date.
508 | * Assumes hours less than 7 are PM and hours 7 or greater are AM.
509 | */
510 | function getDateFromString(string, date) {
511 | var hour = string.substring(0, string.indexOf(":"));
512 | var min = string.substring(string.indexOf(":") + 1);
513 | if (hour < 7) {
514 | hour = parseInt(hour, 10) + 12; //assumes hours less than seven are PM and hours 7 or greater are AM
515 | }
516 | return new Date(date.getFullYear(), date.getMonth(), date.getDate(), hour, min);
517 | }
518 |
519 | /**
520 | * For given day, returns index of schedule id in schedules, schedule id, and formatted date (mm/dd/yy).
521 | * Schedule id index is 0 if not found in schedules.
522 | */
523 | function getDayInfo(day) {
524 | var dateString = day.getMonth().valueOf() + 1 + "/" + day.getDate().valueOf() + "/" + day.getFullYear().toString().substr(-2); //format in mm/dd/YY
525 |
526 | var id;
527 | var index = 0;
528 | var replacements = [];
529 |
530 | //search for special schedule on day
531 | for (var i = 0; i < schedules[0].length; i++) {
532 | if (!schedules[0][i].indexOf(dateString)) {
533 | //found special schedule
534 | if (schedules[0][i].indexOf("[") >= 0) { //check for period replacements
535 | //cut replacements and space character out of id and save separately
536 | id = schedules[0][i].substring(schedules[0][i].indexOf("\t") + 1, schedules[0][i].indexOf("[") - 1);
537 | replacements = schedules[0][i].substring(schedules[0][i].indexOf("[") + 1, schedules[0][i].indexOf("]")).split(",");
538 | } else {
539 | // no replacements to be made
540 | id = schedules[0][i].substring(schedules[0][i].indexOf("\t") + 1);
541 | }
542 | index = getScheduleIndex(id);
543 | }
544 | }
545 |
546 | if (id === undefined) { //no special schedule found
547 | id = day.getDay();
548 | if (id === 0 || id == 6) {
549 | index = id = 0; //no school on weekends
550 | } else { //default schedule for that day
551 | id = calculateScheduleRotationID(day);
552 | index = getScheduleIndex(id);
553 | //Utilizes the replacement system and the fixed mapping to determine
554 | //and display the particular after school function on a given day.
555 | //Note that this is completely independent of the rotation of the
556 | //schdule.
557 | }
558 | }
559 |
560 | var name = "";
561 |
562 | if (id <= TOTAL_SCHEDULES) {
563 | name = SCHEDULES[id - 1];
564 | }
565 |
566 | // replacements.push(COLLABORATION_REPLACEMENTS[day.getDay() - 1]);
567 |
568 | return {
569 | "index": index,
570 | "id": id,
571 | "dateString": dateString,
572 | "replacements": replacements,
573 | "name": name
574 | };
575 | }
576 |
577 | /**
578 | * Gets the index in the list of schedules of the schedule with the given schedule id (or 0 if no matching schedules were found)
579 | */
580 | function getScheduleIndex(id) {
581 | if (id === 0) {
582 | return 0; //schedule id 0 represents no school
583 | }
584 | for (var i = 1; i < schedules.length; i++) { //find index of schedule id
585 | if (id == schedules[i][0]) {
586 | return i; //found specified schedule id
587 | }
588 | }
589 | return 0; //couldn't find specified schedule
590 | }
591 |
592 | /**
593 | * Determines which schedule should be displayed given the four block rotation.
594 | * This futher factors out weekends and holidays when
595 | * considering which day to display. Relies on a known starting anchor day with
596 | * a given schedule and continues the cycle from there.
597 | */
598 | function calculateScheduleRotationID(date) {
599 | var daysDifference = Math.round((date.getTime() - START_DATE.getTime()) / MILLIS_PER_DAY);
600 | //Factor out weekends
601 | daysDifference -= countWeekendDays(START_DATE, date);
602 | //Factor out holidays
603 | var dateExp = /\d{1,2}\/\d{1,2}\/\d{2}/; //Finds dates of the format M(M)/D(D)/YY
604 | for (var i = 0; i < schedules[0].length; i++) {
605 | var entry = schedules[0][i];
606 |
607 | if (entry.search(dateExp) != -1) {
608 | //Parse entry into date and id
609 | var dateString = entry.split("\t")[0];
610 | //Convert the date to format M(M)/D(D)/YYYY because Date defaults to 1900s
611 | dateString = dateString.substring(0, dateString.length - 2) + "20" + dateString.substring(dateString.length - 2);
612 | var entryDate = new Date(dateString);
613 | var entryId = entry.split("\t")[1];
614 | //If the checked schedule "entry" is a holiday in the future that occurs
615 | //before the date that is being calculated "date" but after the start of
616 | //schedule rotation, don't consider it in the cycle. Furthermore, if the holiday
617 | //in question is on a weekend (as can happen for long breaks) do not consider it
618 | //as it has already been factored in in the prior weekend exclusion.
619 | if (getScheduleIndex(entryId) === 0 && date >= entryDate && entryDate >= START_DATE && entryDate.getDay() !== 0 && entryDate.getDay() != 6) {
620 | daysDifference--;
621 | }
622 | }
623 | }
624 |
625 | var id;
626 |
627 | if (daysDifference < 0) { //Different formula needed for events before start day
628 | id = START_SCHEDULE + Math.floor(daysDifference % TOTAL_SCHEDULES) + TOTAL_SCHEDULES;
629 | } else {
630 | id = START_SCHEDULE + Math.floor(daysDifference % TOTAL_SCHEDULES);
631 | }
632 |
633 | if (id > 5) {
634 | id -= 5;
635 | }
636 |
637 | //Even schedules repeat (2 and 6 are the same and 4 and 8 are the same)
638 | // if (id > 4 && id % 2 === 0) {
639 | // id = id - 4;
640 | // }
641 |
642 | return id;
643 | }
644 |
645 | /**
646 | * Creates and returns a new period wrapper with the given content and start/end times.
647 | * Also applies any special properties based on period length (text on single line if too short, block period if longer than regular).
648 | */
649 | function createPeriod(parent, name, start, end, date, showTime, isDouble) {
650 | //Do not show time for very small periods (e.g. class meetings)
651 | if (typeof (showTime) === "undefined") {
652 | showTime = true;
653 | }
654 | if (typeof (isDouble) == "undefined") {
655 | isDouble = false;
656 | }
657 | startDate = getDateFromString(start, date);
658 | endDate = getDateFromString(end, date);
659 |
660 | var periodWrapper = document.createElement("div");
661 | periodWrapper.classList.add("periodWrapper");
662 | periodWrapper.periodName = name;
663 | periodWrapper.start = startDate;
664 | periodWrapper.end = endDate;
665 | var length = (endDate - startDate) / 60000;
666 | if(isDouble && length < 34){
667 | length = 34;
668 | }
669 | if (options.color === true) {
670 |
671 |
672 | switch(periodWrapper.periodName) {
673 | case "P1":
674 | periodWrapper.classList.add("periodone");
675 | break;
676 | case "P2":
677 | periodWrapper.classList.add("periodtwo");
678 | break;
679 | case "P3":
680 | periodWrapper.classList.add("periodthree");
681 | break;
682 | case "P4":
683 | periodWrapper.classList.add("periodfour");
684 | break;
685 | case "P5":
686 | periodWrapper.classList.add("periodfive");
687 | break;
688 | case "P6":
689 | periodWrapper.classList.add("periodsix");
690 | break;
691 | case "P7":
692 | periodWrapper.classList.add("periodseven");
693 | break;
694 | }
695 | }
696 | if (length > 0) {
697 | periodWrapper.style.height = (length - 1) + "px"; //minus 1 to account for 1px border
698 |
699 | // if (length >= 15) {
700 | if (name) {
701 | periodWrapper.innerHTML = name;
702 | }
703 | //Force long periods (30 minutes and up) to have a time
704 | if (showTime && length > 20) {
705 | periodWrapper.innerHTML += (length < 30 ? " " : "
") + start + " – " + end;
706 | }
707 | //if(length>50 && !name.indexOf("P")) //handle block periods (class=long, i.e. bold text)
708 | //periodWrapper.classList.add("long");
709 | // }
710 |
711 | if(LINKS[name]) {
712 | var linkWrapper = document.createElement("a");
713 | linkWrapper.className = "plain";
714 | linkWrapper.href = LINKS[name].split("$DATE$").join(date.getDate()).split("$DAY$").join(date.getDay() + 1).split("$FULLYEAR$").join(date.getFullYear()).split("$MONTH$").join(date.getMonth() + 1).split("$DAYNAME$").join(days[date.getDay()]).split("$DAYNAMELOWER$").join(days[date.getDay()].toLowerCase());
715 | linkWrapper.appendChild(periodWrapper);
716 | periodWrapper = linkWrapper;
717 | }
718 | return parent.appendChild(periodWrapper);
719 | }
720 | }
721 |
722 | /**
723 | * Creates and appends two new sub-periods and passing period to parent period with given start and end times.
724 | */
725 | function createSubPeriods(parent, name, start1, end1, start2, end2, date, showFirstTime, showSecondTime) {
726 | if (typeof (showFirstTime) === "undefined") {
727 | showFirstTime = true;
728 | }
729 | if (typeof (showSecondTime) === "undefined") {
730 | showSecondTime = true;
731 | }
732 |
733 | var p1 = document.createElement("div");
734 | p1.classList.add("period");
735 | createPeriod(
736 | p1,
737 | name.substring(0, name.indexOf("|")),
738 | start1,
739 | end1,
740 | date,
741 | showFirstTime
742 | );
743 | parent.appendChild(p1);
744 |
745 | if (options.showPassingPeriods) {
746 | var lunchPassing = document.createElement("div");
747 | lunchPassing.classList.add("period");
748 | createPeriod(lunchPassing, "", end1, start2, date);
749 | parent.appendChild(lunchPassing);
750 | }
751 |
752 | var p2 = document.createElement("div");
753 | p2.classList.add("period");
754 | //var w2 = document.createElement("div");
755 | //w2.classList.add("periodWrapper");
756 | createPeriod(
757 | p2,
758 | name.substring(name.indexOf("|") + 1),
759 | start2,
760 | end2,
761 | date,
762 | showSecondTime
763 | );
764 | parent.appendChild(p2);
765 | }
766 |
767 | /**
768 | * Creates and appends just three new sub-periods (passing periods added manually) with given start and end times.
769 | */
770 | function create3SubPeriods(parent, name1, start1, end1, name2, start2, end2, name3, start3, end3, date) {
771 | var p1 = document.createElement("div");
772 | p1.classList.add("period");
773 | createPeriod(
774 | p1,
775 | name1,
776 | start1,
777 | end1,
778 | date
779 | );
780 | parent.appendChild(p1);
781 |
782 | var p2 = document.createElement("div");
783 | p2.classList.add("period");
784 | //var w2 = document.createElement("div");
785 | //w2.classList.add("periodWrapper");
786 | createPeriod(
787 | p2,
788 | name2,
789 | start2,
790 | end2,
791 | date
792 | );
793 | parent.appendChild(p2);
794 |
795 | var p3 = document.createElement("div");
796 | p3.classList.add("period");
797 | createPeriod(
798 | p3,
799 | name3,
800 | start3,
801 | end3,
802 | date
803 | );
804 | parent.appendChild(p3);
805 | }
806 |
807 | /**
808 | * Navigates schedule to previous date.
809 | */
810 | function goLast() {
811 | var date = new Date(displayDate);
812 | date.setDate(date.getDate() - (options.enableDayView ? 1 : 7));
813 | updateSchedule(date, false, true);
814 | updateSearch(date);
815 | }
816 |
817 | /**
818 | * Navigates schedule to next date.
819 | */
820 | function goNext() {
821 | var date = new Date(displayDate);
822 | date.setDate(date.getDate() + (options.enableDayView ? 1 : 7));
823 | updateSchedule(date, false, true);
824 | updateSearch(date);
825 | }
826 |
827 | /**
828 | * Navigates schedule to current date.
829 | */
830 | function goCurr() {
831 | var date = new Date();
832 | updateSchedule(date);
833 | updateSearch(date);
834 | }
835 |
836 | /**
837 | * Updates GET variables and urlParams to reflect date in week and pushes corresponding history state.
838 | */
839 | function updateSearch(week, noHistory) {
840 | var curr = new Date();
841 |
842 | if (!options.enableDayView) {
843 | curr = getMonday(curr);
844 | }
845 |
846 | if (week.getDate() != curr.getDate() || week.getMonth() != curr.getMonth()) {
847 | urlParams.m = week.getMonth() + 1;
848 | urlParams.d = week.getDate();
849 | } else {
850 | delete urlParams.m;
851 | delete urlParams.d;
852 | }
853 | if (week.getYear() != curr.getYear()) {
854 | urlParams.y = week.getFullYear().toString().substr(-2);
855 | } else {
856 | delete urlParams.y;
857 | }
858 |
859 | var search = "?";
860 | for (var param in urlParams) {
861 | search += param + "=" + urlParams[param] + "&";
862 | }
863 | search = search.slice(0, -1);
864 |
865 | history.pushState(week, document.title, location.protocol + "//" + location.host + location.pathname + search + location.hash);
866 | }
867 |
868 | /**
869 | * Highlights given date/time on the schedule; defaults to now if none is given
870 | */
871 | function setHighlightedPeriod(time) {
872 | //set default time argument
873 | if (!time) {
874 | time = Date.now();
875 | }
876 |
877 | //set date based on time (for finding day to highlight)
878 | var date = new Date(time);
879 | date.setHours(0, 0, 0, 0);
880 |
881 | //clear previous highlighted day/periods
882 | var prevDay = document.getElementById("today");
883 | var prevPeriods = [];
884 | if (prevDay) {
885 | //clear previous highlighted periods
886 | prevPeriods = Array.prototype.slice.call(prevDay.getElementsByClassName("now")); //get copy of array, not reference to it (needed to check for period changes later)
887 |
888 | for (var i = prevPeriods.length - 1; i >= 0; i--) {
889 | var prevPeriod = prevPeriods[i];
890 | prevPeriod.classList.remove("now");
891 | //remove period length
892 | var periodLength = prevPeriod.getElementsByClassName("periodLength")[0];
893 | if (periodLength) {
894 | prevPeriod.removeChild(periodLength);
895 | }
896 | }
897 |
898 | //clear previous highlighted day
899 | //needs to be done after getting prevPeriods, or else prevDay no longer points anywhere
900 | prevDay.id = "";
901 | }
902 |
903 | //set new highlighted day/period
904 | var days = document.getElementById("schedule").rows[0].cells;
905 | for (var d = 0; d < days.length; d++) {
906 | var day = days[d];
907 | if (date.valueOf() == day.date) { //test if date should be highlighted
908 | //set new highlighted day
909 | day.id = "today";
910 |
911 | //set new highlighted periods
912 | var periods = day.getElementsByClassName("periodWrapper");
913 | for (var p = 0; p < periods.length; p++) {
914 | var period = periods[p];
915 | if (time - period.start >= 0 && time - period.end < 0) { //test if period should be highlighted
916 | period.classList.add("now");
917 | //add period length if it fits
918 | if ((period.end - period.start) / 60000 >= 40) {
919 | var length = (period.end - time) / 60000;
920 | period.innerHTML += "" +
921 | (length > 1 ? Math.round(length) + " min. left
" : Math.round(length * 60) + " sec. left");
922 | }
923 | }
924 | }
925 | }
926 | }
927 |
928 | if (options.enablePeriodNotifications) {
929 | var currPeriods = Array.prototype.slice.call(document.getElementsByClassName("now")); //needs to be an array and not an HTML
930 |
931 | var diff1 = currPeriods.diff(prevPeriods);
932 | var diff2 = prevPeriods.diff(currPeriods);
933 |
934 | for (var j = 0; j < diff1.length; j++) {
935 | var name = currPeriods[j].periodName;
936 | if (name && !hasFocus) {
937 | sendNotification(name + " has started.", options.notificationDuration);
938 | }
939 | }
940 | for (var k = 0; k < diff2.length; k++) {
941 | name = prevPeriods[k].periodName;
942 | if (name && !hasFocus) {
943 | sendNotification(name + " has ended.", options.notificationDuration);
944 | }
945 | }
946 | }
947 | }
948 |
949 | /**
950 | * Updates schedule to display as it would on the given date/time; defaults to now if none is given.
951 | * Also updates
952 | */
953 | function updateSchedule(time, force, title) {
954 | setDisplayDate(time, force);
955 | document.getElementById("warning").removeEventListener("click", (options.enableDoge ? null : checkDoge));
956 | document.getElementById("warning").addEventListener("click", (options.enableDoge ? checkDoge : null));
957 | if (title || forceTitle) {
958 | forceTitle = true;
959 | setTitleTitle(titleStr, time);
960 | }
961 | setHighlightedPeriod();
962 | }
963 |
964 | /**
965 | * Expands the options div and changes the options arrow to point down and to the right.
966 | */
967 | function expandOptions() {
968 | document.getElementById("options").classList.add("expanded");
969 | document.getElementById("optionsArrow").innerHTML = "↘";
970 | }
971 |
972 | /**
973 | * Contracts the options div and changes the options arrow to point up and to the left.
974 | */
975 | function contractOptions() {
976 | document.getElementById("options").classList.remove("expanded");
977 | document.getElementById("optionsArrow").innerHTML = "↖";
978 | }
979 |
980 | /**
981 | * Toggles the options div between extended and contracted and updates options arrow accordingly.
982 | */
983 | function toggleOptions() {
984 | if (document.getElementById("options").classList.contains("expanded")) {
985 | contractOptions();
986 | } else {
987 | expandOptions();
988 | }
989 | }
990 |
991 | /**
992 | * Initializes automatic option saving and sets options to previously-saved values, if any.
993 | * If no previous saved value exists, sets current (default) value as saved value.
994 | */
995 | function initOptions() {
996 | var opt = document.getElementById("options");
997 | opt.addEventListener("mouseover", expandOptions);
998 | opt.addEventListener("mouseout", contractOptions);
999 |
1000 | if (mobile) {
1001 | opt.classList.add("mobile");
1002 | }
1003 |
1004 | document.getElementById("optionsArrow").addEventListener("click", toggleOptions);
1005 |
1006 | var inputs = opt.getElementsByTagName("input");
1007 |
1008 | if (localStorage.msupdateScheduleInterval) {
1009 | //rename key
1010 | localStorage.msactiveUpdateInterval = localStorage.msupdateScheduleInterval;
1011 | localStorage.removeItem("updateScheduleInterval");
1012 | }
1013 |
1014 | for (var i = 0; i < inputs.length; i++) {
1015 | var input = inputs[i];
1016 | //special cases because localStorage saves values as strings
1017 | if (input.type == "checkbox") { //booleans
1018 | input.addEventListener("change", function (event) {
1019 | options[event.target.name] = localStorage["ms"+event.target.name] = event.target.checked;
1020 | });
1021 |
1022 | if (localStorage["ms"+input.name]) {
1023 | options[input.name] = input.checked = localStorage["ms"+input.name] == "true";
1024 | } else {
1025 | options[input.name] = localStorage["ms"+input.name] = input.checked;
1026 | }
1027 | } else if (input.type == "number") { //numbers
1028 | input.addEventListener("change", function (event) {
1029 | options[event.target.name] = parseInt(localStorage["ms"+event.target.name] = event.target.value);
1030 | });
1031 |
1032 | if (localStorage["ms"+input.name]) {
1033 | options[input.name] = parseInt(input.value = localStorage["ms"+input.name]);
1034 | } else {
1035 | options[input.name] = parseInt(localStorage["ms"+input.name] = input.value);
1036 | }
1037 | } else { //strings
1038 | input.addEventListener("change", function (event) {
1039 | options[event.target.name] = localStorage["ms"+event.target.name] = event.target.value;
1040 | });
1041 |
1042 | if (localStorage["ms"+input.name]) {
1043 | options[input.name] = input.value = localStorage["ms"+input.name];
1044 | } else {
1045 | options[input.name] = localStorage["ms"+input.name] = input.value;
1046 | }
1047 | }
1048 | }
1049 | }
1050 |
1051 | /**
1052 | * Creates options in the options div.
1053 | */
1054 | function createOptions(data) {
1055 | // just assume the file has everything for now
1056 | JSON.parse(data).sections.forEach(function (section) {
1057 | if (!section.hasOwnProperty("platforms") ||
1058 | ((mobile && section.platforms.indexOf("mobile") >= 0) || !mobile)) {
1059 | createOptionSection(section);
1060 | }
1061 | });
1062 |
1063 | initOptions();
1064 | attachOptionActions();
1065 | updateSchedule(null, true, true);
1066 | }
1067 |
1068 | /**
1069 | * Displays error about retrieving schedule.
1070 | */
1071 | function displayOptionsError(timeout, status) {
1072 | updateSchedule();
1073 | if (timeout) {
1074 | warn("Retrieval of options.json timed out!");
1075 | } else {
1076 | warn("Something went wrong while retrieving options.json!");
1077 | }
1078 | }
1079 |
1080 | /**
1081 | * Create and insert options section.
1082 | */
1083 | function createOptionSection(section) {
1084 | createOptionSectionTitle(section);
1085 | section.options.forEach(function (option) {
1086 | if (!option.hasOwnProperty("platforms") ||
1087 | ((mobile && option.platforms.indexOf("mobile") >= 0) || !mobile)) {
1088 | createOption(option);
1089 | }
1090 | });
1091 | }
1092 |
1093 | /**
1094 | * Create and insert options section title.
1095 | */
1096 | function createOptionSectionTitle(section) {
1097 | var tr = document.createElement("tr");
1098 | var th = document.createElement("th");
1099 | th.colspan = 2;
1100 | if (section.hasOwnProperty("tooltip")) {
1101 | var span = document.createElement("span");
1102 | span.title = section.tooltip;
1103 | span.innerHTML = section.name + '?';
1104 | th.appendChild(span);
1105 | } else {
1106 | th.textContent = section.name;
1107 | }
1108 | tr.appendChild(th);
1109 | document.getElementById("optionsContent").appendChild(tr);
1110 | }
1111 |
1112 | /**
1113 | * Create and insert option into options.
1114 | */
1115 | function createOption(option) {
1116 | var tr = document.createElement("tr");
1117 | var tddesc = document.createElement("td");
1118 | var tdinput = document.createElement("td");
1119 | if (option.hasOwnProperty("tooltip")) {
1120 | var span = document.createElement("span");
1121 | span.title = option.tooltip;
1122 | span.innerHTML = option.description + '?:';
1123 | tddesc.appendChild(span);
1124 | } else {
1125 | tddesc.textContent = option.description + ":";
1126 | }
1127 | var input = document.createElement("input");
1128 | input.name = option.name;
1129 | input.type = option.type;
1130 | var defaultValue = (mobile && option.hasOwnProperty("mobileDefault")) ? option.mobileDefault : option.default; //choose desktop or mobile default value
1131 | if (input.type == "number") {
1132 | input.min = 0; //may as well keep this here until any options can take negative
1133 | input.value = defaultValue;
1134 | } else if (input.type == "checkbox") {
1135 | if (defaultValue) input.checked = "checked";
1136 | }
1137 | tdinput.appendChild(input);
1138 | tr.appendChild(tddesc);
1139 | tr.appendChild(tdinput);
1140 | document.getElementById("optionsContent").appendChild(tr);
1141 | }
1142 |
1143 | /**
1144 | * Creates event listeners for option-specific actions on option change and applies option-specific actions on page load.
1145 | */
1146 | function attachOptionActions() {
1147 | updateUpdateInterval();
1148 | document.getElementsByName("activeUpdateInterval")[0].addEventListener("change", function (event) {
1149 | updateUpdateInterval();
1150 | });
1151 | document.getElementsByName("showPassingPeriods")[0].addEventListener("change", function (event) {
1152 | updateSchedule(null, true);
1153 | });
1154 | document.getElementsByName("color")[0].addEventListener("change", function (event) {
1155 | updateSchedule(null, true);
1156 | });
1157 |
1158 | document.getElementsByName("enablePeriodNotifications")[0].addEventListener("change", function (event) {
1159 | if (options.enablePeriodNotifications) {
1160 | var permission = Notification.permission;
1161 | if (!("Notification" in window)) {
1162 | alert("This browser does not support desktop notifications.");
1163 | } else if (permission == "denied") {
1164 | alert("Please allow desktop notifications for this site to enable this feature.");
1165 | } else if (permission == "default") {
1166 | Notification.requestPermission();
1167 | }
1168 | }
1169 | });
1170 |
1171 | document.getElementsByName("enableDoge")[0].addEventListener("change", function(event) {
1172 | document.getElementById("warning").removeEventListener("click", (options.enableDoge ? null : checkDoge));
1173 | document.getElementById("warning").addEventListener("click", (options.enableDoge ? checkDoge : null));
1174 | })
1175 |
1176 | document.body.classList.add(options.enableDayView ? "day" : "week");
1177 | document.getElementsByName("enableDayView")[0].addEventListener("change", function (event) {
1178 | updateSchedule(null, true);
1179 |
1180 | document.body.classList.remove("week");
1181 | document.body.classList.remove("day");
1182 | document.body.classList.add(options.enableDayView ? "day" : "week");
1183 |
1184 | scrollTo(0, 0); //scroll back to top-left corner
1185 | });
1186 |
1187 | if (!mobile) {
1188 | document.addEventListener("keydown", function (event) {
1189 | switch (event.keyCode) {
1190 | case 116: //F5
1191 | if (options.interceptF5) {
1192 | //enabled
1193 | event.preventDefault();
1194 | updateSchedule();
1195 | }
1196 | break;
1197 | case 82: //R key
1198 | if (options.interceptCtrlR && (event.ctrlKey || event.metaKey)) {
1199 | //enabled and control/cmd (meta)
1200 | event.preventDefault();
1201 | updateSchedule();
1202 | }
1203 | break;
1204 | case 37: //Left arrow
1205 | goLast();
1206 | break;
1207 | case 39: //Right arrow
1208 | goNext();
1209 | break;
1210 | case 40: //Down arrow
1211 | goCurr();
1212 | break;
1213 | }
1214 | inputStr += event.keyCode;
1215 | if (inputStr.indexOf(KONAMI) != -1) {
1216 | //isDoge = !isDoge;
1217 | //setDoge(isDoge);
1218 | inputStr = "";
1219 | }
1220 | });
1221 |
1222 | /*setDoge(options.enableDoge);
1223 | document.getElementsByName("enableDoge")[0].addEventListener("change", function(event) {
1224 | setDoge(event.target.checked);
1225 | });*/ // in light of recent complaints, doge mode has been discontinued (1/28/2015)
1226 | }
1227 | }
1228 |
1229 | /**
1230 | * Retrieve file data via XMLHttpRequest.
1231 | *
1232 | * cb is for successful retrieval and takes a String as a parameter.
1233 | * errcb is for an error on retrieval and takes:
1234 | * 1. a boolean representing whether or not the error was a timeout.
1235 | * 2. an integer representing the status of the response (this is null on timeout).
1236 | */
1237 | function download(url, cb, errcb) {
1238 | var xmlhttp = new XMLHttpRequest();
1239 | xmlhttp.open("GET", url, true);
1240 | xmlhttp.onreadystatechange = function () {
1241 | if (xmlhttp.readyState == 4) {
1242 | if (xmlhttp.status == 200) {
1243 | cb(xmlhttp.responseText);
1244 | } else if (errcb) {
1245 | errcb(false, xmlhttp.status);
1246 | }
1247 | }
1248 | };
1249 | xmlhttp.ontimeout = function () {
1250 | errcb(true, null);
1251 | };
1252 | xmlhttp.send();
1253 | }
1254 |
1255 | /**
1256 | * Sets the correct update interval based on the current state (focus and visibility) of the document.
1257 | */
1258 | function updateUpdateInterval() {
1259 | if (document.hidden) {
1260 | setUpdateInterval(options.hiddenUpdateInterval); //assume that hidden implies no focus
1261 | } else if (hasFocus) {
1262 | setUpdateInterval(options.activeUpdateInterval);
1263 | } else {
1264 | setUpdateInterval(options.inactiveUpdateInterval);
1265 | }
1266 | }
1267 |
1268 | /**
1269 | * Updates the interval for automatically refreshing the page.
1270 | * seconds is the new interval in seconds.
1271 | */
1272 | function setUpdateInterval(seconds) {
1273 | clearInterval(updateScheduleID);
1274 | if (seconds > 0) {
1275 | updateScheduleID = setInterval(function () {
1276 | //updateClock();
1277 | updateSchedule();
1278 | }, seconds * 1000); //convert to milliseconds
1279 | } else {
1280 | updateScheduleID = null;
1281 | }
1282 | }
1283 |
1284 | /**
1285 | * Creates a desktop notification with the given text for a title and removes it after the given duration in seconds.
1286 | * A duration of 0 or less will disable auto-closing the notification.
1287 | */
1288 | function sendNotification(text, duration) {
1289 | if ("Notification" in window) { //check that browser supports notifications
1290 | var notification = new Notification(text);
1291 | if (duration > 0) {
1292 | setTimeout(function () {
1293 | notification.close();
1294 | }, duration * 1000);
1295 | }
1296 | }
1297 | }
1298 |
1299 | /**
1300 | * Function to detect whether the page is being displayed on a mobile device.
1301 | * Currently checks if the useragent/vendor matches a regex string for mobile phones.
1302 | */
1303 | function isMobile() {
1304 | var a = navigator.userAgent || navigator.vendor || window.opera;
1305 | if (window.innerWidth <= 800 && window.innerHeight <= 600) {
1306 | return true;
1307 | }
1308 | return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4));
1309 | }
1310 |
1311 | /**
1312 | * Updates the time on the bell schedule
1313 | */
1314 | function updateClock() {
1315 | var now = new Date();
1316 | var h = now.getHours();
1317 | var h12 = h % 12;
1318 | var m = now.getMinutes();
1319 | document.getElementById('currentTime').innerHTML = (h12 === 0 ? 12 : h12) + ":" + addLeadingZero(m) + (h >= 12 ? " PM" : " AM");
1320 | }
1321 |
1322 | /**
1323 | * Adds leading zeroes as necessary to make output (at least) 2 characters long
1324 | * (Assumes that n is an integer.)
1325 | */
1326 | function addLeadingZero(n) {
1327 | return (n < 10) ? "0" + n : n;
1328 | }
1329 |
1330 | /**
1331 | * Checks if two dates are the same, ignoring hours, minutes, and seconds
1332 | */
1333 | function isSameDate(d1, d2) {
1334 | return (
1335 | d1.getFullYear() === d2.getFullYear() &&
1336 | d2.getMonth() === d2.getMonth() &&
1337 | d1.getDate() === d2.getDate()
1338 | );
1339 | }
1340 |
1341 | /**
1342 | * Determines how many times the character or character sequence
1343 | * char appears in str.
1344 | */
1345 | function findNumberOfOccurences(str, char) {
1346 | for (var count = -1, index = -2; index != -1; count++, index = str.indexOf(char, index + 1));
1347 | return count;
1348 | }
1349 |
1350 | function countWeekendDays(start, end) {
1351 | var numDays = 1 + Math.round((end.getTime() - start.getTime()) / MILLIS_PER_DAY);
1352 | var numSat = Math.floor((start.getDay() + numDays) / 7);
1353 | return 2 * numSat + (start.getDay() === 0) - (end.getDay() == 6);
1354 | }
1355 |
--------------------------------------------------------------------------------
/scripts/script.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Primary script for the Harker Bell Schedule
3 | * Hosted at http://harkerdev.github.io/bellschedule
4 | **/
5 |
6 | /**
7 | * CSS things
8 | */
9 | addEventListener("scroll", function (event) {
10 | document.getElementById("header").style.left = scrollX + "px";
11 | });
12 |
13 | /**
14 | * Returns an array of values in the array that aren't in a.
15 | */
16 | Array.prototype.diff = function (a) {
17 | return this.filter(function (i) {
18 | return a.indexOf(i) < 0;
19 | });
20 | };
21 |
22 | /**
23 | * Constants
24 | */
25 | var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; //days of the week in string form
26 | var schedules; //array of schedules (each schedule is an array in this array
27 | var mobile = isMobile();
28 | var rawSchedule = "";
29 | var titles = [];
30 | var titleStr = "";
31 | var forceTitle = false;
32 | var futureWarning = "
Future schedules may be incorrect.";
33 | var dogeCounter = 7;
34 | /**
35 | * Globals
36 | */
37 | var displayDate; //beginning of time period currently being displayed by the schedule
38 | var updateScheduleID; //ID of interval of updateSchedule
39 | var hasFocus = true; //document.hasFocus() seems to be unreliable; assumes window has focus on page load
40 | var options = {};
41 |
42 | var urlParams; //object with GET variables as properties and their respective values as values
43 |
44 | var inputStr = "";
45 |
46 | var KEY_LEFT = 37;
47 | var KEY_UP = 38;
48 | var KEY_RIGHT = 39;
49 | var KEY_DOWN = 40;
50 | var KEY_A = 65;
51 | var KEY_B = 66;
52 | var KONAMI = "" + KEY_UP + KEY_UP + KEY_DOWN + KEY_DOWN + KEY_LEFT + KEY_RIGHT + KEY_LEFT + KEY_RIGHT + KEY_B + KEY_A;
53 | var isDoge;
54 |
55 | var START_DATE = new Date('August 25, 2019'); //The start day of the school year. This should be a weekday.
56 |
57 | var START_SCHEDULE = 1; //The schedule on the first day
58 |
59 | var LINKS = {
60 | "Lunch": "https://harkerdev.github.io/harker-lunch/#$DAYNAMELOWER$",
61 | "School Meeting": "https://docs.google.com/forms/d/e/1FAIpQLSeZoCFQhzPqiX-Tbcc0qRUuw7_rjMgUxkiR97GN6aNB8Ulfsg/viewform?entry.1033439092=$MONTH$%2F$DATE$"
62 | }
63 |
64 | //On a given day, independent of rotation, after school has a fixed function. This array maps the day (0 for Monday, etc.)
65 | //to the particular function (e.g. Extra Help). This ultimately piggybacks on the replacement system.
66 | var COLLABORATION_REPLACEMENTS = [
67 | "Collaboration -> Office Hours",
68 | "Collaboration -> Office Hours",
69 | "Collaboration -> Faculty Meeting",
70 | "Collaboration -> Office Hours",
71 | "Collaboration -> After School"
72 | ];
73 |
74 | var TOTAL_SCHEDULES = 8; //The number of schedules to be cycled
75 |
76 | var SCHEDULES = ["A", "B", "C", "D", "A", "B", "C", "D"];
77 |
78 | var MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
79 |
80 | /**
81 | * Gets GET variables from URL and sets them as properties of the urlParams object.
82 | * Then updates the state of the current history entry with the appropriate week.
83 | */
84 | (function () {
85 | //decode GET vars in URL
86 | updateUrlParams();
87 |
88 | //update history state
89 | window.history.replaceState(getDateFromUrlParams(), document.title, document.location);
90 | }());
91 |
92 | /**
93 | * Event listeners
94 | */
95 | document.addEventListener("visibilitychange", function (event) {
96 | if (!document.hidden) { //only slightly redundant; on un-minimize, document gains visibility without focus
97 | updateSchedule();
98 | // updateClock();
99 | }
100 | updateUpdateInterval();
101 | });
102 |
103 | addEventListener("focus", function (event) {
104 | updateSchedule();
105 | //updateClock();
106 | hasFocus = true;
107 | updateUpdateInterval();
108 | });
109 |
110 | addEventListener("blur", function (event) {
111 | hasFocus = false;
112 | updateUpdateInterval();
113 | });
114 |
115 | /**
116 | * Event listener for navigating through history.
117 | * (onload event will not fire when navigating through history items pushed by history.pushState, because the page does not reload)
118 | */
119 | addEventListener("popstate", function (event) {
120 | updateUrlParams();
121 | updateSchedule(event.state);
122 | });
123 |
124 | /**
125 | * Updates urlParams object based on the GET variables in the URL.
126 | * (variables as properties and values as values)
127 | */
128 | function updateUrlParams() {
129 | urlParams = {};
130 |
131 | var match,
132 | pl = /(?!^)\+/g, //regex for replacing non-leading + with space
133 | search = /([^&=]+)=?([^&]*)/g,
134 | decode = function (s) {
135 | return decodeURIComponent(s.replace(pl, " "));
136 | },
137 | query = location.search.substring(1);
138 |
139 | while (match = search.exec(query)) {
140 | urlParams[decode(match[1])] = decode(match[2]);
141 | }
142 | }
143 |
144 | /**
145 | * Parses schedules, creates schedule for correct week, sets title title on page load.
146 | */
147 | addEventListener("load", function (event) {
148 | initViewport();
149 | initTitle();
150 | $.ajax({url: "special.txt", success: function(data) {
151 | parseRawSchedule(data);
152 | $.ajax({url: "options.json", success: function(data) {
153 | createOptions(JSON.stringify(data));
154 | }, cache: false});
155 | }, cache: false});
156 | });
157 |
158 | function error() {
159 | console.log("error downloading");
160 | }
161 |
162 | function initViewport() {
163 | if (mobile) {
164 | var meta = document.createElement("meta");
165 | meta.name = "viewport";
166 | meta.content = 'user-scalable=no, initial-scale=1.0, maximum-scale=1.0';
167 | document.getElementsByTagName("head")[0].appendChild(meta);
168 | document.getElementsByTagName("body")[0].class = "mobile";
169 | }
170 | }
171 |
172 | /**
173 | * Adds appropriate event listeners to items in the schedule title.
174 | */
175 | log = console.log.bind(console);
176 | function initTitle() {
177 | document.getElementById("leftArrow").addEventListener("click", goLast);
178 | document.getElementById("rightArrow").addEventListener("click", goNext);
179 |
180 | document.getElementById("refresh").addEventListener("click", function () {
181 | updateSchedule(null, true);
182 | });
183 |
184 | $.ajax({url: "titles.txt", success: function(data) {
185 | titleStr = data;
186 | }, cache: false});
187 | }
188 |
189 | function checkDoge() {
190 | dogeCounter -= 1;
191 | if(dogeCounter == 0) {
192 | setDoge(true);
193 | setTitleTitle("doge")
194 | } else if(dogeCounter < 0) {
195 | setDoge(false);
196 | dogeCounter = 7;
197 | setTitleTitle(titleStr)
198 | } else {
199 | setTitleTitle("You are " + dogeCounter + (dogeCounter == 1 ? " step" : " steps") + " away from becoming a developer!")
200 | }
201 | }
202 |
203 | /**
204 | * Parses raw schedule in body of page into schedule array
205 | * Code is questionable <- ya think
206 | */
207 | function parseRawSchedule(data) {
208 | var rawSchedules = data.split("\n"); //get raw schedule text
209 | //var rawSchedules = document.getElementById("schedules").textContent.split("\n"); //get raw schedule text
210 | schedules = [];
211 | var x = 0; //index in schedules
212 | schedules[0] = []; //create array of special schedule days
213 |
214 | //loop through all lines in raw schedule text
215 | while (rawSchedules.length > 0) {
216 | if (rawSchedules[0].length === 0) {
217 | //if line is empty, move to next index in schedules
218 | schedules[++x] = []; //could probably use id as index instead, or just properties
219 | rawSchedules.shift();
220 | } else {
221 | //if line has text, save in current location in schedules
222 | var str = rawSchedules.shift();
223 | if (x === 0 && str.indexOf("|") >= 0) {
224 | //behavior for blocks of dates with the same schedule
225 | var start = new Date(str.substring(0, str.indexOf("|")));
226 | var end = new Date(str.substring(str.indexOf("|") + 1, str.indexOf("\t")));
227 | for (; start <= end; start.setDate(start.getDate() + 1)) {
228 | schedules[0].push(start.getMonth().valueOf() + 1 +
229 | "/" + start.getDate() +
230 | "/" + start.getFullYear().toString().substr(-2) +
231 | str.substring(str.indexOf("\t")));
232 | }
233 | } else {
234 | schedules[x].push(str);
235 | }
236 | }
237 | }
238 | //callback();
239 | }
240 |
241 | /**
242 | * Displays schedule of the week of the given date/time
243 | */
244 | function setDisplayDate(time, force) {
245 | var date = (time ? new Date(time) : getDateFromUrlParams()); //variable to keep track of current day in loop
246 | setDayBeginning(date);
247 | if (force || !displayDate || (date.valueOf() != displayDate.valueOf())) {
248 | var schedule = document.getElementById("schedule"); //get schedule table
249 | displayDate = new Date(date);
250 | while (schedule.rows.length) {
251 | schedule.deleteRow(-1); //clear existing weeks (rows); there should only be one, but just in case...
252 | }
253 |
254 | var week = schedule.insertRow(-1); //create new week (row)
255 |
256 | if (!options.enableDayView) {
257 | date = getMonday(date);
258 | for (var d = 0; d < 5; d++) {
259 | //for each day Monday through Friday (inclusive)
260 | createDay(week, date);
261 | date.setDate(date.getDate() + 1); //increment day
262 | }
263 | } else {
264 | if(date.getDay()==0 || date.getDay==6) goNext();
265 | date = getNextWeekday(date);
266 | createDay(week, date);
267 | }
268 | }
269 | }
270 |
271 | /**
272 | * Returns a Date object based on the current urlParams (GET variables in the URL).
273 | * If any part of the date is not specified, defaults to the current date/month/year.
274 | * If in week view, uses the Monday of the week instead of the day.
275 | */
276 | function getDateFromUrlParams() {
277 | var date = new Date();
278 |
279 | if (urlParams.y > 0 && urlParams.m > 0 && urlParams.d > 0) {
280 | date = new Date("20" + urlParams.y, urlParams.m - 1, urlParams.d);
281 | }
282 |
283 | else if (urlParams.m > 0 && urlParams.d > 0) {
284 | date = new Date((new Date()).getFullYear(), urlParams.m - 1, urlParams.d);
285 | }
286 |
287 | if (!options.enableDayView) {
288 | date = getMonday(date);
289 | }
290 | return date;
291 | }
292 |
293 | /**
294 | * Displays the given warning or hides the warning div if no warning text is given.
295 | */
296 | function warn(text) {
297 | var warning = document.getElementById("warning");
298 |
299 | if (text) {
300 | warning.style.display = "block";
301 | } else {
302 | warning.style.display = "none";
303 | }
304 |
305 | warning.innerHTML = text;
306 | }
307 |
308 | /**
309 | * Creates the day for the given date and appends it to the given week
310 | */
311 | function createDay(week, date) {
312 | var daySchedule = getDayInfo(date); //get schedule for that day
313 |
314 | var col = week.insertCell(-1); //create cell for day
315 | col.date = date.valueOf(); //store date in cell element
316 |
317 | //check Halloween
318 | if (date.getMonth() == 9 && date.getDate() == 31) {
319 | col.classList.add("halloween");
320 | }
321 |
322 | var head = document.createElement("div"); //create header div in cell
323 | head.classList.add("head");
324 | var headWrapper = document.createElement("div");
325 | headWrapper.classList.add("headWrapper");
326 | var scheduleString = ""; //Should we display the schedule id (e.g. A) next to the date
327 | //Make sure not to display anything if daySchedule is empty
328 | if (typeof daySchedule.name != 'undefined' || daySchedule.name !== null) {
329 | //Not a weekend, so add
330 | scheduleString = "(" + daySchedule.name + ")";
331 | }
332 | if (scheduleString === "()") {
333 | //It's a holiday so delete the extra parenthesis
334 | scheduleString = "";
335 | }
336 | headWrapper.innerHTML = days[date.getDay()] + "" +
337 | daySchedule.dateString + " " + scheduleString + "
"; //Portion commented out represents schedule id of that day
338 | head.appendChild(headWrapper);
339 | col.appendChild(head);
340 |
341 | var prevEnd = "8:00"; //set start of day to 8:00AM
342 | // for sub periods, passing periods are already handled and do not need to be added in the next iteration
343 |
344 | if (daySchedule.index > 0) { //populates cell with day's schedule (a bit messily)
345 | for (var i = 1; i < schedules[daySchedule.index].length; i++) {
346 | var text = schedules[daySchedule.index][i];
347 | var periodName = makePeriodNameReplacements(text.substring(0, text.indexOf("\t")), daySchedule.replacements);
348 | var periodTime = text.substring(text.indexOf("\t") + 1);
349 |
350 | var start = periodTime.substring(0, periodTime.indexOf("-"));
351 | var end = periodTime.substring(periodTime.lastIndexOf("-") + 1);
352 |
353 | // only creates a new passing period before the period if either 1) it's a split lunch period in the new schedule or
354 | // 2) the date is not within the bounds of the new schedule
355 | if (options.showPassingPeriods) {
356 | var passing = document.createElement("div");
357 | passing.classList.add("period");
358 | createPeriod(passing, "", prevEnd, start, date);
359 | col.appendChild(passing);
360 | }
361 |
362 | prevEnd = end;
363 |
364 | var period = document.createElement("div");
365 | period.classList.add("period");
366 |
367 | if (periodName.indexOf("|") >= 0) {
368 | //handle split periods (i.e. lunches)
369 | var table = document.createElement("table");
370 | table.classList.add("lunch");
371 | var row = table.insertRow(-1);
372 |
373 | var period1 = row.insertCell(-1);
374 | var period2 = row.insertCell(-1);
375 |
376 | var period1Time = periodTime.substring(0, periodTime.indexOf("||"));
377 | var period2Time = periodTime.substring(periodTime.indexOf("||") + 2);
378 |
379 | var period1Name = periodName.substring(0, periodName.indexOf("||"));
380 | var period2Name = periodName.substring(periodName.indexOf("||") + 2);
381 |
382 | //If there are two sets of subperiods (i.e. a|b||c|d) there should be 4 "|"s
383 | if (findNumberOfOccurences(periodName, "|") == 4) {
384 | show1Time = true;
385 | show2Time = true;
386 |
387 | createSubPeriods(
388 | period1,
389 | period1Name,
390 | start,
391 | period1Time.substring(period1Time.indexOf("-") + 1, period1Time.indexOf("|")),
392 | period1Time.substring(period1Time.indexOf("|") + 1, period1Time.lastIndexOf("-")),
393 | end,
394 | date,
395 | show1Time,
396 | show2Time
397 | );
398 |
399 | createSubPeriods(
400 | period2,
401 | period2Name,
402 | start,
403 | period2Time.substring(period2Time.indexOf("-") + 1, period2Time.indexOf("|")),
404 | period2Time.substring(period2Time.indexOf("|") + 1, period2Time.lastIndexOf("-")),
405 | end,
406 | date,
407 | show1Time,
408 | show2Time
409 | );
410 | } else {
411 | //parent, name, start, end, date
412 | createPeriod(
413 | period1,
414 | period1Name,
415 | start,
416 | end,
417 | date
418 | );
419 |
420 | show1Time = daySchedule.id == 4 || daySchedule.id == "ReCreate";
421 | show2Time = !(show1Time);
422 | //parent, name, start1, end1, start2, end2, date, show 1st, show 2nd
423 | createSubPeriods(
424 | period2,
425 | period2Name,
426 | start,
427 | period2Time.substring(period2Time.indexOf("-") + 1, period2Time.indexOf("|")),
428 | period2Time.substring(period2Time.indexOf("|") + 1, period2Time.lastIndexOf("-")),
429 | end,
430 | date,
431 | show1Time,
432 | show2Time
433 | );
434 | }
435 | period.appendChild(table);
436 | } else {
437 | createPeriod(period, periodName, start, end, date);
438 | //parent, name, start, end, date
439 | //createSubPeriods(
440 | //lunch2,
441 | //periodName.substring(periodName.indexOf("||")+2),
442 | //start,
443 | //lunch2Time.substring(lunch2Time.indexOf("-")+1,lunch2Time.indexOf("|")),
444 | //lunch2Time.substring(lunch2Time.indexOf("|")+1,lunch2Time.lastIndexOf("-")),
445 | //end,
446 | //date
447 | //);
448 | }
449 | col.appendChild(period);
450 | }
451 | }
452 | }
453 |
454 | /**
455 | * Returns new name for period based on array of replacements.
456 | * If the current period name is listed in the array of replacements, returns the new, replaced name; otherwise, returns current name.
457 | * replacements is an array of strings of the form "OldName->NewName"
458 | */
459 | function makePeriodNameReplacements(periodName, replacements) {
460 | if (replacements.length > 0) {
461 | for (var i = 0; i < replacements.length; i++) {
462 | if (!replacements[i].indexOf(periodName)) {
463 | return replacements[i].substring(replacements[i].indexOf("->") + 2);
464 | }
465 | }
466 | }
467 | return periodName;
468 | }
469 |
470 | /**
471 | * Sets the title of the title to a random line from the title titles list
472 | */
473 | function setTitleTitle(data, time) {
474 | var date = (time ? new Date(time) : getDateFromUrlParams());
475 | var titles = data.split("\n");
476 | if (titles.length > 1) titles.pop();
477 | displayMessage = titles[Math.floor(Math.random() * titles.length)];// <-- for rotating messages
478 | if (getMonday(date) > getMonday(new Date())) {
479 | displayMessage += futureWarning; //display warning if date is in the future
480 | }
481 | warn(displayMessage);
482 | }
483 |
484 | /**
485 | * Returns the Monday of next week if date is Saturday; else returns the Monday of that week
486 | */
487 | function getMonday(d) {
488 | var date = new Date(d);
489 | if (date.getDay() >= 6) {
490 | date.setDate(date.getDate() + 2); //set date to next Monday if today is Saturday
491 | } else {
492 | date.setDate(date.getDate() - date.getDay() + 1); //else set date Monday of this week
493 | }
494 | setDayBeginning(date); //set to beginning of day
495 | return date;
496 | }
497 |
498 | /**
499 | * Returns Monday if date is Saturday or Sunday; else returns the current day
500 | */
501 | function getNextWeekday(d) {
502 | var date = new Date(d);
503 | if (date.getDay() == 0 || date.getDay() == 6) {
504 | goNext();
505 | return getMonday(d);
506 | }
507 | setDayBeginning(date); //set to beginning of day
508 | return date;
509 | }
510 |
511 | /**
512 | * Sets given date to beginning of the day (12:00 AM).
513 | */
514 | function setDayBeginning(date) {
515 | date.setHours(0, 0, 0, 0);
516 | }
517 |
518 | /**
519 | * Takes in a date and a string of form "hh:MM" and turns it into a time on the day of the given date.
520 | * Assumes hours less than 7 are PM and hours 7 or greater are AM.
521 | */
522 | function getDateFromString(string, date) {
523 | var hour = string.substring(0, string.indexOf(":"));
524 | var min = string.substring(string.indexOf(":") + 1);
525 | if (hour < 7) {
526 | hour = parseInt(hour, 10) + 12; //assumes hours less than seven are PM and hours 7 or greater are AM
527 | }
528 | return new Date(date.getFullYear(), date.getMonth(), date.getDate(), hour, min);
529 | }
530 |
531 | /**
532 | * For given day, returns index of schedule id in schedules, schedule id, and formatted date (mm/dd/yy).
533 | * Schedule id index is 0 if not found in schedules.
534 | */
535 | function getDayInfo(day) {
536 | var dateString = day.getMonth().valueOf() + 1 + "/" + day.getDate().valueOf() + "/" + day.getFullYear().toString().substr(-2); //format in mm/dd/YY
537 |
538 | var id;
539 | var index = 0;
540 | var replacements = [];
541 |
542 | //search for special schedule on day
543 | for (var i = 0; i < schedules[0].length; i++) {
544 | if (!schedules[0][i].indexOf(dateString)) {
545 | //found special schedule
546 | if (schedules[0][i].indexOf("[") >= 0) { //check for period replacements
547 | //cut replacements and space character out of id and save separately
548 | id = schedules[0][i].substring(schedules[0][i].indexOf("\t") + 1, schedules[0][i].indexOf("[") - 1);
549 | replacements = schedules[0][i].substring(schedules[0][i].indexOf("[") + 1, schedules[0][i].indexOf("]")).split(",");
550 | } else {
551 | // no replacements to be made
552 | id = schedules[0][i].substring(schedules[0][i].indexOf("\t") + 1);
553 | }
554 | index = getScheduleIndex(id);
555 | }
556 | }
557 |
558 | if (id === undefined) { //no special schedule found
559 | id = day.getDay();
560 | if (id === 0 || id == 6) {
561 | index = id = 0; //no school on weekends
562 | } else { //default schedule for that day
563 | id = calculateScheduleRotationID(day);
564 | index = getScheduleIndex(id);
565 | //Utilizes the replacement system and the fixed mapping to determine
566 | //and display the particular after school function on a given day.
567 | //Note that this is completely independent of the rotation of the
568 | //schdule.
569 | }
570 | }
571 |
572 | var name = "";
573 |
574 | if (id <= TOTAL_SCHEDULES) {
575 | name = SCHEDULES[id - 1];
576 | }
577 |
578 | replacements.push(COLLABORATION_REPLACEMENTS[day.getDay() - 1]);
579 |
580 | return {
581 | "index": index,
582 | "id": id,
583 | "dateString": dateString,
584 | "replacements": replacements,
585 | "name": name
586 | };
587 | }
588 |
589 | /**
590 | * Gets the index in the list of schedules of the schedule with the given schedule id (or 0 if no matching schedules were found)
591 | */
592 | function getScheduleIndex(id) {
593 | if (id === 0) {
594 | return 0; //schedule id 0 represents no school
595 | }
596 | for (var i = 1; i < schedules.length; i++) { //find index of schedule id
597 | if (id == schedules[i][0]) {
598 | return i; //found specified schedule id
599 | }
600 | }
601 | return 0; //couldn't find specified schedule
602 | }
603 |
604 | /**
605 | * Determines which schedule should be displayed given the four block rotation.
606 | * This futher factors out weekends and holidays when
607 | * considering which day to display. Relies on a known starting anchor day with
608 | * a given schedule and continues the cycle from there.
609 | */
610 | function calculateScheduleRotationID(date) {
611 | var daysDifference = Math.round((date.getTime() - START_DATE.getTime()) / MILLIS_PER_DAY);
612 | //Factor out weekends
613 | daysDifference -= countWeekendDays(START_DATE, date);
614 | //Factor out holidays
615 | var dateExp = /\d{1,2}\/\d{1,2}\/\d{2}/; //Finds dates of the format M(M)/D(D)/YY
616 | for (var i = 0; i < schedules[0].length; i++) {
617 | var entry = schedules[0][i];
618 |
619 | if (entry.search(dateExp) != -1) {
620 | //Parse entry into date and id
621 | var dateString = entry.split("\t")[0];
622 | //Convert the date to format M(M)/D(D)/YYYY because Date defaults to 1900s
623 | dateString = dateString.substring(0, dateString.length - 2) + "20" + dateString.substring(dateString.length - 2);
624 | var entryDate = new Date(dateString);
625 | var entryId = entry.split("\t")[1];
626 | //If the checked schedule "entry" is a holiday in the future that occurs
627 | //before the date that is being calculated "date" but after the start of
628 | //schedule rotation, don't consider it in the cycle. Furthermore, if the holiday
629 | //in question is on a weekend (as can happen for long breaks) do not consider it
630 | //as it has already been factored in in the prior weekend exclusion.
631 | if (getScheduleIndex(entryId) === 0 && date >= entryDate && entryDate >= START_DATE && entryDate.getDay() !== 0 && entryDate.getDay() != 6) {
632 | daysDifference--;
633 | }
634 | }
635 | }
636 |
637 | var id;
638 |
639 | if (daysDifference < 0) { //Different formula needed for events before start day
640 | id = START_SCHEDULE + Math.floor(daysDifference % TOTAL_SCHEDULES) + TOTAL_SCHEDULES;
641 | } else {
642 | id = START_SCHEDULE + Math.floor(daysDifference % TOTAL_SCHEDULES);
643 | }
644 |
645 | if (id > 8) {
646 | id -= 8;
647 | }
648 |
649 | //Even schedules repeat (2 and 6 are the same and 4 and 8 are the same)
650 | if (id > 4 && id % 2 === 0) {
651 | id = id - 4;
652 | }
653 |
654 | return id;
655 | }
656 |
657 | /**
658 | * Creates and returns a new period wrapper with the given content and start/end times.
659 | * Also applies any special properties based on period length (text on single line if too short, block period if longer than regular).
660 | */
661 | function createPeriod(parent, name, start, end, date, showTime) {
662 | //Do not show time for very small periods (e.g. class meetings)
663 | if (typeof (showTime) === "undefined") {
664 | showTime = true;
665 | }
666 | startDate = getDateFromString(start, date);
667 | endDate = getDateFromString(end, date);
668 |
669 | var periodWrapper = document.createElement("div");
670 | periodWrapper.classList.add("periodWrapper");
671 | periodWrapper.periodName = name;
672 | periodWrapper.start = startDate;
673 | periodWrapper.end = endDate;
674 | var length = (endDate - startDate) / 60000;
675 | if (options.color === true) {
676 |
677 |
678 | switch(periodWrapper.periodName) {
679 | case "P1":
680 | periodWrapper.classList.add("periodone");
681 | break;
682 | case "P2":
683 | periodWrapper.classList.add("periodtwo");
684 | break;
685 | case "P3":
686 | periodWrapper.classList.add("periodthree");
687 | break;
688 | case "P4":
689 | periodWrapper.classList.add("periodfour");
690 | break;
691 | case "P5":
692 | periodWrapper.classList.add("periodfive");
693 | break;
694 | case "P6":
695 | periodWrapper.classList.add("periodsix");
696 | break;
697 | case "P7":
698 | periodWrapper.classList.add("periodseven");
699 | break;
700 | }
701 | }
702 | if (length > 0) {
703 | periodWrapper.style.height = (length - 1) + "px"; //minus 1 to account for 1px border
704 |
705 | if (length >= 15) {
706 | if (name) {
707 | if (name.length == 2 && name[0] == 'P' && name[1] >= 1 && name[1] <= 7 && options["period" + name[1]] != "") {
708 | name = options["period" + name[1]] + " (" + name + ")";
709 | }
710 | periodWrapper.innerHTML = name;
711 | }
712 | //Force long periods (30 minutes and up) to have a time
713 | if (showTime) {
714 | periodWrapper.innerHTML += (length < 30 ? " " : "
") + start + " – " + end;
715 | }
716 | //if(length>50 && !name.indexOf("P")) //handle block periods (class=long, i.e. bold text)
717 | //periodWrapper.classList.add("long");
718 | }
719 |
720 | if(LINKS[name]) {
721 | var linkWrapper = document.createElement("a");
722 | linkWrapper.className = "plain";
723 | linkWrapper.href = LINKS[name].split("$DATE$").join(date.getDate()).split("$DAY$").join(date.getDay() + 1).split("$FULLYEAR$").join(date.getFullYear()).split("$MONTH$").join(date.getMonth() + 1).split("$DAYNAME$").join(days[date.getDay()]).split("$DAYNAMELOWER$").join(days[date.getDay()].toLowerCase());
724 | linkWrapper.appendChild(periodWrapper);
725 | periodWrapper = linkWrapper;
726 | }
727 | return parent.appendChild(periodWrapper);
728 | }
729 | }
730 |
731 | /**
732 | * Creates and appends two new sub-periods and passing period to parent period with given start and end times.
733 | */
734 | function createSubPeriods(parent, name, start1, end1, start2, end2, date, showFirstTime, showSecondTime) {
735 | if (typeof (showFirstTime) === "undefined") {
736 | showFirstTime = true;
737 | }
738 | if (typeof (showSecondTime) === "undefined") {
739 | showSecondTime = true;
740 | }
741 |
742 | var p1 = document.createElement("div");
743 | p1.classList.add("period");
744 | createPeriod(
745 | p1,
746 | name.substring(0, name.indexOf("|")),
747 | start1,
748 | end1,
749 | date,
750 | showFirstTime
751 | );
752 | parent.appendChild(p1);
753 |
754 | if (options.showPassingPeriods) {
755 | var lunchPassing = document.createElement("div");
756 | lunchPassing.classList.add("period");
757 | createPeriod(lunchPassing, "", end1, start2, date);
758 | parent.appendChild(lunchPassing);
759 | }
760 |
761 | var p2 = document.createElement("div");
762 | p2.classList.add("period");
763 | //var w2 = document.createElement("div");
764 | //w2.classList.add("periodWrapper");
765 | createPeriod(
766 | p2,
767 | name.substring(name.indexOf("|") + 1),
768 | start2,
769 | end2,
770 | date,
771 | showSecondTime
772 | );
773 | parent.appendChild(p2);
774 | }
775 |
776 | /**
777 | * Creates and appends just three new sub-periods (passing periods added manually) with given start and end times.
778 | */
779 | function create3SubPeriods(parent, name1, start1, end1, name2, start2, end2, name3, start3, end3, date) {
780 | var p1 = document.createElement("div");
781 | p1.classList.add("period");
782 | createPeriod(
783 | p1,
784 | name1,
785 | start1,
786 | end1,
787 | date
788 | );
789 | parent.appendChild(p1);
790 |
791 | var p2 = document.createElement("div");
792 | p2.classList.add("period");
793 | //var w2 = document.createElement("div");
794 | //w2.classList.add("periodWrapper");
795 | createPeriod(
796 | p2,
797 | name2,
798 | start2,
799 | end2,
800 | date
801 | );
802 | parent.appendChild(p2);
803 |
804 | var p3 = document.createElement("div");
805 | p3.classList.add("period");
806 | createPeriod(
807 | p3,
808 | name3,
809 | start3,
810 | end3,
811 | date
812 | );
813 | parent.appendChild(p3);
814 | }
815 |
816 | /**
817 | * Navigates schedule to previous date.
818 | */
819 | function goLast() {
820 | var date = new Date(displayDate);
821 | if(!options.enableDayView) {
822 | date.setDate(date.getDate() - 7);
823 | }
824 | else {
825 | date.setDate(date.getDate() - 1);
826 | while(date.getDay() == 0 || date.getDay() == 6) date.setDate(date.getDate() - 1);
827 | }
828 | updateSchedule(date, false, true);
829 | updateSearch(date);
830 | }
831 |
832 | /**
833 | * Navigates schedule to next date.
834 | */
835 | function goNext() {
836 | var date = new Date(displayDate);
837 | if(!options.enableDayView) {
838 | date.setDate(date.getDate() + 7);
839 | }
840 | else {
841 | date.setDate(date.getDate() + 1);
842 | while(date.getDay()==0 || date.getDay()==6) date.setDate(date.getDate() + 1);
843 | }
844 | updateSchedule(date, false, true);
845 | updateSearch(date);
846 | }
847 |
848 | /**
849 | * Navigates schedule to current date.
850 | */
851 | function goCurr() {
852 | var date = new Date();
853 | updateSchedule(date);
854 | updateSearch(date);
855 | }
856 |
857 | /**
858 | * Updates GET variables and urlParams to reflect date in week and pushes corresponding history state.
859 | */
860 | function updateSearch(week, noHistory) {
861 | var curr = new Date();
862 |
863 | if (!options.enableDayView) {
864 | curr = getMonday(curr);
865 | }
866 |
867 | if (week.getDate() != curr.getDate() || week.getMonth() != curr.getMonth()) {
868 | urlParams.m = week.getMonth() + 1;
869 | urlParams.d = week.getDate();
870 | } else {
871 | delete urlParams.m;
872 | delete urlParams.d;
873 | }
874 | if (week.getYear() != curr.getYear()) {
875 | urlParams.y = week.getFullYear().toString().substr(-2);
876 | } else {
877 | delete urlParams.y;
878 | }
879 |
880 | var search = "?";
881 | for (var param in urlParams) {
882 | search += param + "=" + urlParams[param] + "&";
883 | }
884 | search = search.slice(0, -1);
885 |
886 | history.pushState(week, document.title, location.protocol + "//" + location.host + location.pathname + search + location.hash);
887 | }
888 |
889 | /**
890 | * Highlights given date/time on the schedule; defaults to now if none is given
891 | */
892 | function setHighlightedPeriod(time) {
893 | //set default time argument
894 | if (!time) {
895 | time = new Date(new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"}));
896 | }
897 |
898 | //set date based on time (for finding day to highlight)
899 | var date = new Date(time);
900 | date.setHours(0, 0, 0, 0);
901 |
902 | //clear previous highlighted day/periods
903 | var prevDay = document.getElementById("today");
904 | var prevPeriods = [];
905 | if (prevDay) {
906 | //clear previous highlighted periods
907 | prevPeriods = Array.prototype.slice.call(prevDay.getElementsByClassName("now")); //get copy of array, not reference to it (needed to check for period changes later)
908 |
909 | for (var i = prevPeriods.length - 1; i >= 0; i--) {
910 | var prevPeriod = prevPeriods[i];
911 | prevPeriod.classList.remove("now");
912 | //remove period length
913 | var periodLength = prevPeriod.getElementsByClassName("periodLength")[0];
914 | if (periodLength) {
915 | prevPeriod.removeChild(periodLength);
916 | }
917 | }
918 |
919 | //clear previous highlighted day
920 | //needs to be done after getting prevPeriods, or else prevDay no longer points anywhere
921 | prevDay.id = "";
922 | }
923 |
924 | //set new highlighted day/period
925 | var days = document.getElementById("schedule").rows[0].cells;
926 | for (var d = 0; d < days.length; d++) {
927 | var day = days[d];
928 | if (date.valueOf() == day.date) { //test if date should be highlighted
929 | //set new highlighted day
930 | day.id = "today";
931 |
932 | //set new highlighted periods
933 | var periods = day.getElementsByClassName("periodWrapper");
934 | for (var p = 0; p < periods.length; p++) {
935 | var period = periods[p];
936 | if (time - period.start >= 0 && time - period.end < 0) { //test if period should be highlighted
937 | period.classList.add("now");
938 | //add period length if it fits
939 | if ((period.end - period.start) / 60000 >= 40) {
940 | var length = (period.end - time) / 60000;
941 | period.innerHTML += "" +
942 | (length > 1 ? Math.round(length) + " min. left
" : Math.round(length * 60) + " sec. left");
943 | }
944 | }
945 | }
946 | }
947 | }
948 |
949 | if (options.enablePeriodNotifications) {
950 | var currPeriods = Array.prototype.slice.call(document.getElementsByClassName("now")); //needs to be an array and not an HTML
951 |
952 | var diff1 = currPeriods.diff(prevPeriods);
953 | var diff2 = prevPeriods.diff(currPeriods);
954 |
955 | for (var j = 0; j < diff1.length; j++) {
956 | var name = currPeriods[j].periodName;
957 | if (name && !hasFocus) {
958 | sendNotification(name + " has started.", options.notificationDuration);
959 | }
960 | }
961 | for (var k = 0; k < diff2.length; k++) {
962 | name = prevPeriods[k].periodName;
963 | if (name && !hasFocus) {
964 | sendNotification(name + " has ended.", options.notificationDuration);
965 | }
966 | }
967 | }
968 | }
969 |
970 | /**
971 | * Updates schedule to display as it would on the given date/time; defaults to now if none is given.
972 | * Also updates
973 | */
974 | function updateSchedule(time, force, title) {
975 | setDisplayDate(time, force);
976 | document.getElementById("warning").removeEventListener("click", (options.enableDoge ? null : checkDoge));
977 | document.getElementById("warning").addEventListener("click", (options.enableDoge ? checkDoge : null));
978 | if (title || forceTitle) {
979 | forceTitle = true;
980 | setTitleTitle(titleStr, time);
981 | }
982 | setHighlightedPeriod();
983 | }
984 |
985 | /**
986 | * Expands the options div and changes the options arrow to point down and to the right.
987 | */
988 | function expandOptions() {
989 | document.getElementById("options").classList.add("expanded");
990 | document.getElementById("optionsArrow").innerHTML = "↘";
991 | }
992 |
993 | /**
994 | * Contracts the options div and changes the options arrow to point up and to the left.
995 | */
996 | function contractOptions() {
997 | document.getElementById("options").classList.remove("expanded");
998 | document.getElementById("optionsArrow").innerHTML = "↖";
999 | }
1000 |
1001 | /**
1002 | * Toggles the options div between extended and contracted and updates options arrow accordingly.
1003 | */
1004 | function toggleOptions() {
1005 | if (document.getElementById("options").classList.contains("expanded")) {
1006 | contractOptions();
1007 | } else {
1008 | expandOptions();
1009 | }
1010 | }
1011 |
1012 | /**
1013 | * Initializes automatic option saving and sets options to previously-saved values, if any.
1014 | * If no previous saved value exists, sets current (default) value as saved value.
1015 | */
1016 | function initOptions() {
1017 | var opt = document.getElementById("options");
1018 | opt.addEventListener("mouseover", expandOptions);
1019 | opt.addEventListener("mouseout", contractOptions);
1020 |
1021 | if (mobile) {
1022 | opt.classList.add("mobile");
1023 | }
1024 |
1025 | document.getElementById("optionsArrow").addEventListener("click", toggleOptions);
1026 |
1027 | var inputs = opt.getElementsByTagName("input");
1028 |
1029 | if (localStorage.updateScheduleInterval) {
1030 | //rename key
1031 | localStorage.activeUpdateInterval = localStorage.updateScheduleInterval;
1032 | localStorage.removeItem("updateScheduleInterval");
1033 | }
1034 |
1035 | for (var i = 0; i < inputs.length; i++) {
1036 | var input = inputs[i];
1037 | //special cases because localStorage saves values as strings
1038 | if (input.type == "checkbox") { //booleans
1039 | input.addEventListener("change", function (event) {
1040 | options[event.target.name] = localStorage[event.target.name] = event.target.checked;
1041 | });
1042 |
1043 | if (localStorage[input.name]) {
1044 | options[input.name] = input.checked = localStorage[input.name] == "true";
1045 | } else {
1046 | options[input.name] = localStorage[input.name] = input.checked;
1047 | }
1048 | } else if (input.type == "number") { //numbers
1049 | input.addEventListener("change", function (event) {
1050 | options[event.target.name] = parseInt(localStorage[event.target.name] = event.target.value);
1051 | });
1052 |
1053 | if (localStorage[input.name]) {
1054 | options[input.name] = parseInt(input.value = localStorage[input.name]);
1055 | } else {
1056 | options[input.name] = parseInt(localStorage[input.name] = input.value);
1057 | }
1058 | } else { //strings
1059 | input.addEventListener("change", function (event) {
1060 | options[event.target.name] = localStorage[event.target.name] = event.target.value;
1061 | });
1062 |
1063 | if (localStorage[input.name]) {
1064 | options[input.name] = input.value = localStorage[input.name];
1065 | } else {
1066 | options[input.name] = localStorage[input.name] = input.value;
1067 | }
1068 | }
1069 | }
1070 | }
1071 |
1072 | /**
1073 | * Creates options in the options div.
1074 | */
1075 | function createOptions(data) {
1076 | // just assume the file has everything for now
1077 | JSON.parse(data).sections.forEach(function (section) {
1078 | if (!section.hasOwnProperty("platforms") ||
1079 | ((mobile && section.platforms.indexOf("mobile") >= 0) || !mobile)) {
1080 | createOptionSection(section);
1081 | }
1082 | });
1083 |
1084 | initOptions();
1085 | attachOptionActions();
1086 | updateSchedule(null, true, true);
1087 | }
1088 |
1089 | /**
1090 | * Displays error about retrieving schedule.
1091 | */
1092 | function displayOptionsError(timeout, status) {
1093 | updateSchedule();
1094 | if (timeout) {
1095 | warn("Retrieval of options.json timed out!");
1096 | } else {
1097 | warn("Something went wrong while retrieving options.json!");
1098 | }
1099 | }
1100 |
1101 | /**
1102 | * Create and insert options section.
1103 | */
1104 | function createOptionSection(section) {
1105 | createOptionSectionTitle(section);
1106 | section.options.forEach(function (option) {
1107 | if (!option.hasOwnProperty("platforms") ||
1108 | ((mobile && option.platforms.indexOf("mobile") >= 0) || !mobile)) {
1109 | createOption(option);
1110 | }
1111 | });
1112 | }
1113 |
1114 | /**
1115 | * Create and insert options section title.
1116 | */
1117 | function createOptionSectionTitle(section) {
1118 | var tr = document.createElement("tr");
1119 | var th = document.createElement("th");
1120 | th.colspan = 2;
1121 | if (section.hasOwnProperty("tooltip")) {
1122 | var span = document.createElement("span");
1123 | span.title = section.tooltip;
1124 | span.innerHTML = section.name + '?';
1125 | th.appendChild(span);
1126 | } else {
1127 | th.textContent = section.name;
1128 | }
1129 | tr.appendChild(th);
1130 | document.getElementById("optionsContent").appendChild(tr);
1131 | }
1132 |
1133 | /**
1134 | * Create and insert option into options.
1135 | */
1136 | function createOption(option) {
1137 | var tr = document.createElement("tr");
1138 | var tddesc = document.createElement("td");
1139 | var tdinput = document.createElement("td");
1140 | if (option.hasOwnProperty("tooltip")) {
1141 | var span = document.createElement("span");
1142 | span.title = option.tooltip;
1143 | span.innerHTML = option.description + '?:';
1144 | tddesc.appendChild(span);
1145 | } else {
1146 | tddesc.textContent = option.description + ":";
1147 | }
1148 | var input = document.createElement("input");
1149 | input.name = option.name;
1150 | input.type = option.type;
1151 | var defaultValue = (mobile && option.hasOwnProperty("mobileDefault")) ? option.mobileDefault : option.default; //choose desktop or mobile default value
1152 | if (input.type == "number") {
1153 | input.min = 0; //may as well keep this here until any options can take negative
1154 | input.value = defaultValue;
1155 | } else if (input.type == "checkbox") {
1156 | if (defaultValue) input.checked = "checked";
1157 | }
1158 | tdinput.appendChild(input);
1159 | tr.appendChild(tddesc);
1160 | tr.appendChild(tdinput);
1161 | document.getElementById("optionsContent").appendChild(tr);
1162 | }
1163 |
1164 | /**
1165 | * Creates event listeners for option-specific actions on option change and applies option-specific actions on page load.
1166 | */
1167 | function attachOptionActions() {
1168 | updateUpdateInterval();
1169 | document.getElementsByName("activeUpdateInterval")[0].addEventListener("change", function (event) {
1170 | updateUpdateInterval();
1171 | });
1172 | document.getElementsByName("showPassingPeriods")[0].addEventListener("change", function (event) {
1173 | updateSchedule(null, true);
1174 | });
1175 | document.getElementsByName("color")[0].addEventListener("change", function (event) {
1176 | updateSchedule(null, true);
1177 | });
1178 | //Adds listeners to update schedule when period names are changed
1179 | for (var i = 1; i <= 7; i++) {
1180 | document.getElementsByName("period" + i)[0].addEventListener("change", function (event) {
1181 | updateSchedule(null, true);
1182 | });
1183 | }
1184 |
1185 | document.getElementsByName("enablePeriodNotifications")[0].addEventListener("change", function (event) {
1186 | if (options.enablePeriodNotifications) {
1187 | var permission = Notification.permission;
1188 | if (!("Notification" in window)) {
1189 | alert("This browser does not support desktop notifications.");
1190 | } else if (permission == "denied") {
1191 | alert("Please allow desktop notifications for this site to enable this feature.");
1192 | } else if (permission == "default") {
1193 | Notification.requestPermission();
1194 | }
1195 | }
1196 | });
1197 |
1198 | document.getElementsByName("enableDoge")[0].addEventListener("change", function(event) {
1199 | document.getElementById("warning").removeEventListener("click", (options.enableDoge ? null : checkDoge));
1200 | document.getElementById("warning").addEventListener("click", (options.enableDoge ? checkDoge : null));
1201 | })
1202 |
1203 | document.body.classList.add(options.enableDayView ? "day" : "week");
1204 | document.getElementsByName("enableDayView")[0].addEventListener("change", function (event) {
1205 | updateSchedule(null, true);
1206 |
1207 | document.body.classList.remove("week");
1208 | document.body.classList.remove("day");
1209 | document.body.classList.add(options.enableDayView ? "day" : "week");
1210 |
1211 | scrollTo(0, 0); //scroll back to top-left corner
1212 | });
1213 |
1214 | if (!mobile) {
1215 | document.addEventListener("keydown", function (event) {
1216 | switch (event.keyCode) {
1217 | case 116: //F5
1218 | if (options.interceptF5) {
1219 | //enabled
1220 | event.preventDefault();
1221 | updateSchedule();
1222 | }
1223 | break;
1224 | case 82: //R key
1225 | if (options.interceptCtrlR && (event.ctrlKey || event.metaKey)) {
1226 | //enabled and control/cmd (meta)
1227 | event.preventDefault();
1228 | updateSchedule();
1229 | }
1230 | break;
1231 | case 37: //Left arrow
1232 | goLast();
1233 | break;
1234 | case 39: //Right arrow
1235 | goNext();
1236 | break;
1237 | case 40: //Down arrow
1238 | goCurr();
1239 | break;
1240 | }
1241 | inputStr += event.keyCode;
1242 | if (inputStr.indexOf(KONAMI) != -1) {
1243 | //isDoge = !isDoge;
1244 | //setDoge(isDoge);
1245 | inputStr = "";
1246 | }
1247 | });
1248 |
1249 | /*setDoge(options.enableDoge);
1250 | document.getElementsByName("enableDoge")[0].addEventListener("change", function(event) {
1251 | setDoge(event.target.checked);
1252 | });*/ // in light of recent complaints, doge mode has been discontinued (1/28/2015)
1253 | }
1254 | }
1255 |
1256 | /**
1257 | * Retrieve file data via XMLHttpRequest.
1258 | *
1259 | * cb is for successful retrieval and takes a String as a parameter.
1260 | * errcb is for an error on retrieval and takes:
1261 | * 1. a boolean representing whether or not the error was a timeout.
1262 | * 2. an integer representing the status of the response (this is null on timeout).
1263 | */
1264 | function download(url, cb, errcb) {
1265 | var xmlhttp = new XMLHttpRequest();
1266 | xmlhttp.open("GET", url, true);
1267 | xmlhttp.onreadystatechange = function () {
1268 | if (xmlhttp.readyState == 4) {
1269 | if (xmlhttp.status == 200) {
1270 | cb(xmlhttp.responseText);
1271 | } else if (errcb) {
1272 | errcb(false, xmlhttp.status);
1273 | }
1274 | }
1275 | };
1276 | xmlhttp.ontimeout = function () {
1277 | errcb(true, null);
1278 | };
1279 | xmlhttp.send();
1280 | }
1281 |
1282 | /**
1283 | * Sets the correct update interval based on the current state (focus and visibility) of the document.
1284 | */
1285 | function updateUpdateInterval() {
1286 | if (document.hidden) {
1287 | setUpdateInterval(options.hiddenUpdateInterval); //assume that hidden implies no focus
1288 | } else if (hasFocus) {
1289 | setUpdateInterval(options.activeUpdateInterval);
1290 | } else {
1291 | setUpdateInterval(options.inactiveUpdateInterval);
1292 | }
1293 | }
1294 |
1295 | /**
1296 | * Updates the interval for automatically refreshing the page.
1297 | * seconds is the new interval in seconds.
1298 | */
1299 | function setUpdateInterval(seconds) {
1300 | clearInterval(updateScheduleID);
1301 | if (seconds > 0) {
1302 | updateScheduleID = setInterval(function () {
1303 | //updateClock();
1304 | updateSchedule();
1305 | }, seconds * 1000); //convert to milliseconds
1306 | } else {
1307 | updateScheduleID = null;
1308 | }
1309 | }
1310 |
1311 | /**
1312 | * Creates a desktop notification with the given text for a title and removes it after the given duration in seconds.
1313 | * A duration of 0 or less will disable auto-closing the notification.
1314 | */
1315 | function sendNotification(text, duration) {
1316 | if ("Notification" in window) { //check that browser supports notifications
1317 | var notification = new Notification(text);
1318 | if (duration > 0) {
1319 | setTimeout(function () {
1320 | notification.close();
1321 | }, duration * 1000);
1322 | }
1323 | }
1324 | }
1325 |
1326 | /**
1327 | * Function to detect whether the page is being displayed on a mobile device.
1328 | * Currently checks if the useragent/vendor matches a regex string for mobile phones.
1329 | */
1330 | function isMobile() {
1331 | var a = navigator.userAgent || navigator.vendor || window.opera;
1332 | if (window.innerWidth <= 800 && window.innerHeight <= 600) {
1333 | return true;
1334 | }
1335 | return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4));
1336 | }
1337 |
1338 | /**
1339 | * Updates the time on the bell schedule
1340 | */
1341 | function updateClock() {
1342 | var now = new Date();
1343 | var h = now.getHours();
1344 | var h12 = h % 12;
1345 | var m = now.getMinutes();
1346 | document.getElementById('currentTime').innerHTML = (h12 === 0 ? 12 : h12) + ":" + addLeadingZero(m) + (h >= 12 ? " PM" : " AM");
1347 | }
1348 |
1349 | /**
1350 | * Adds leading zeroes as necessary to make output (at least) 2 characters long
1351 | * (Assumes that n is an integer.)
1352 | */
1353 | function addLeadingZero(n) {
1354 | return (n < 10) ? "0" + n : n;
1355 | }
1356 |
1357 | /**
1358 | * Checks if two dates are the same, ignoring hours, minutes, and seconds
1359 | */
1360 | function isSameDate(d1, d2) {
1361 | return (
1362 | d1.getFullYear() === d2.getFullYear() &&
1363 | d2.getMonth() === d2.getMonth() &&
1364 | d1.getDate() === d2.getDate()
1365 | );
1366 | }
1367 |
1368 | /**
1369 | * Determines how many times the character or character sequence
1370 | * char appears in str.
1371 | */
1372 | function findNumberOfOccurences(str, char) {
1373 | for (var count = -1, index = -2; index != -1; count++, index = str.indexOf(char, index + 1));
1374 | return count;
1375 | }
1376 |
1377 | function countWeekendDays(start, end) {
1378 | var numDays = 1 + Math.round((end.getTime() - start.getTime()) / MILLIS_PER_DAY);
1379 | var numSat = Math.floor((start.getDay() + numDays) / 7);
1380 | return 2 * numSat + (start.getDay() === 0) - (end.getDay() == 6);
1381 | }
1382 |
--------------------------------------------------------------------------------