├── .DS_Store ├── ms ├── .DS_Store ├── asset │ ├── doge.jpg │ ├── logo.png │ └── favicon.ico ├── titles.txt ├── special.txt ├── options.json ├── index.html ├── scripts │ ├── doge.js │ ├── script.min.js │ └── script.js └── style.css ├── asset ├── .DS_Store ├── doge.jpg ├── logo.png └── favicon.ico ├── titles.txt ├── README.md ├── LICENSE ├── scripts ├── doge.js ├── script.min.js └── script.js ├── index.html ├── CODE_OF_CONDUCT.md ├── options.json ├── style.css └── special.txt /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/bellschedule/HEAD/.DS_Store -------------------------------------------------------------------------------- /ms/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/bellschedule/HEAD/ms/.DS_Store -------------------------------------------------------------------------------- /asset/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/bellschedule/HEAD/asset/.DS_Store -------------------------------------------------------------------------------- /asset/doge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/bellschedule/HEAD/asset/doge.jpg -------------------------------------------------------------------------------- /asset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/bellschedule/HEAD/asset/logo.png -------------------------------------------------------------------------------- /asset/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/bellschedule/HEAD/asset/favicon.ico -------------------------------------------------------------------------------- /ms/asset/doge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/bellschedule/HEAD/ms/asset/doge.jpg -------------------------------------------------------------------------------- /ms/asset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/bellschedule/HEAD/ms/asset/logo.png -------------------------------------------------------------------------------- /ms/asset/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/bellschedule/HEAD/ms/asset/favicon.ico -------------------------------------------------------------------------------- /titles.txt: -------------------------------------------------------------------------------- 1 | You will be redirected to bell.harker.org on January 1st, 2020. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bell Schedule 2 | A bell schedule web application for Harker Upper School students. 3 | 4 | ## Usage 5 | * Go to [the Github IO page](http://harkerdev.github.io/bellschedule/) or [tiny.cc/hsbell](http://tiny.cc/hsbell) to access the schedule 6 | * Click the left and right arrows to switch between weeks 7 | * Periods are highlighted as the day progresses 8 | * Time left until the current period is over is displayed in small, bold text underneath the general period title 9 | -------------------------------------------------------------------------------- /ms/titles.txt: -------------------------------------------------------------------------------- 1 | This is a title! 2 | Hi! 3 | 33 million pixels is many! 4 | In the style of Minecraft! 5 | May contain small parts. 6 | Use at your own risk! 7 | To-scale! 8 | Hidden divs! 9 | Randomly chosen text! 10 | No easter eggs! 11 | Unofficial! 12 | HHMS + PCR 13 | Hosted on GitHub! 14 | Open source! 15 | Now with favicon! 16 | Not completely inefficient! 17 | Tested in Chrome! 18 | Exclamation marks! 19 | Doesn't steal your password! 20 | Does anyone know the schedule? 21 | Somewhat OP! 22 | Only in English! 23 | Object-oriented! 24 | Mostly JavaScript! 25 | Not explosive! 26 | Don't be late to class! 27 | Not a poem! 28 | Two words long! 29 | Time travel! 30 | Made in USA! 31 | Grayscale! 32 | Unobfuscated! 33 | Sans-serif! 34 | floor >> parseInt 35 | Mediocre! 36 | This is what happens when I procrastinate! 37 | DRM-free! 38 | Nested tables! 39 | Mobile-friendly! (ish) 40 | Metro! 41 | Mildly interesting! 42 | User-friendly! 43 | Incomplete sentences! 44 | OP! 45 | wow 46 | such title 47 | Now with options! 48 | Thoughtful! 49 | Uses JSON! 50 | Functional! 51 | Gluten free! 52 | Not infallible! 53 | Click me! 54 | Now with async! 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Harker Development 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ms/special.txt: -------------------------------------------------------------------------------- 1 | 11/19/18|11/23/18 Thanksgiving Break 2 | 12/24/18|1/4/19 Winter Break 3 | 4 | 1 5 | P1 8:05-8:49 6 | P2 8:54-9:38 7 | Break 9:38-9:51 8 | P3 9:54-10:38 9 | P4 10:43-11:27 10 | Meeting 11:32-11:50 11 | Lunch 1 (7/8th) 11:50-12:22 12 | Lunch 2 (6th) 12:22-12:52 13 | P5 12:57-1:41 14 | P6 1:46-2:30 15 | P7 2:35-3:19 16 | Extra Help 3:19-3:35 17 | 18 | 2 19 | P1 8:05-8:47 20 | P2 8:52-9:34 21 | Break 9:34-9:47 22 | Advisory 9:50-10:05 23 | P3 10:10-10:52 24 | P4 10:57-11:39 25 | Lunch 1 (7/8th) 11:39-12:11 26 | Lunch 2 (6th) 12:11-12:41 27 | P5 12:46-1:28 28 | P6 1:33-2:15 29 | P7 2:20-3:02 30 | Extra Help||Extra Help|Clubs 3:02-3:35||3:02-3:15|3:15-3:35 31 | 32 | 3 33 | Faculty Meeting 7:45-8:20 34 | P1 8:30-9:12 35 | P2 9:17-9:59 36 | Break 9:59-10:12 37 | P3 10:15-10:57 38 | P4 11:02-11:44 39 | Lunch 1 (7/8th) 11:44-12:16 40 | Lunch 2 (6th) 12:16-12:46 41 | P5 12:51-1:33 42 | P6 1:38-2:20 43 | P7 2:25-3:07 44 | Extra Help 3:07-3:20 45 | 46 | 4 47 | P1 8:05-8:47 48 | P2 8:52-9:34 49 | Break 9:34-9:47 50 | Advisory 9:50-10:05 51 | P3 10:10-10:52 52 | P4 10:57-11:39 53 | Lunch 1 (7/8th) 11:39-12:11 54 | Lunch 2 (6th) 12:11-12:41 55 | P5 12:46-1:28 56 | P6 1:33-2:15 57 | P7 2:20-3:02 58 | Extra Help||Extra Help|Clubs 3:02-3:35||3:02-3:15|3:15-3:35 59 | 60 | 5 61 | P1 8:05-8:46 62 | P2 8:51-9:32 63 | Break 9:32-9:42 64 | P3 9:45-10:26 65 | P4 10:31-11:12 66 | Lunch 1 (7/8th) 11:12-11:42 67 | Lunch 2 (6th) 11:42-12:10 68 | P5 12:15-12:56 69 | P6 1:01-1:42 70 | P7 1:47-2:28 71 | Advisory 2:33-2:45 72 | Assembly 2:50-3:35 73 | -------------------------------------------------------------------------------- /ms/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": [ 3 | { 4 | "name": "General", 5 | "options": [ 6 | { 7 | "name": "color", 8 | "type": "checkbox", 9 | "default": true, 10 | "description": "Show color" 11 | }, 12 | { 13 | "name": "showPassingPeriods", 14 | "type": "checkbox", 15 | "default": false, 16 | "description": "Show passing periods" 17 | }, 18 | { 19 | "name": "enablePeriodNotifications", 20 | "type": "checkbox", 21 | "default": false, 22 | "description": "Enable period change notifications" 23 | }, 24 | { 25 | "name": "notificationDuration", 26 | "type": "number", 27 | "default": 5, 28 | "description": "Notification display time (seconds)" 29 | }, 30 | { 31 | "name": "enableDayView", 32 | "type": "checkbox", 33 | "default": false, 34 | "mobileDefault": true, 35 | "description": "Enable day view" 36 | }, 37 | { 38 | "name": "enableDoge", 39 | "type": "checkbox", 40 | "default": false, 41 | "mobileDefault": false, 42 | "description": "Enable doge" 43 | } 44 | ] 45 | }, 46 | { 47 | "name": "Automatic Updating", 48 | "tooltip": "Set to 0 to disable automatic updating", 49 | "options": [ 50 | { 51 | "name": "activeUpdateInterval", 52 | "type": "number", 53 | "default": 10, 54 | "description": "Active update interval (seconds)", 55 | "tooltip": "Applies while tab is selected and window is active" 56 | }, 57 | { 58 | "name": "inactiveUpdateInterval", 59 | "type": "number", 60 | "default": 30, 61 | "description": "Inactive update interval (seconds)", 62 | "tooltip": "Applies while tab is selected, but window is not active" 63 | }, 64 | { 65 | "name": "hiddenUpdateInterval", 66 | "type": "number", 67 | "default": 30, 68 | "description": "Hidden update interval (seconds)", 69 | "tooltip": "Applies while tab is not selected or window is minimized" 70 | } 71 | ] 72 | }, 73 | { 74 | "name": "Override Refresh", 75 | "tooltip": "Override default keyboard shortcuts for refresh and update the schedule instead. (Only really useful with automatic updating disabled.)", 76 | "platforms": ["desktop"], 77 | "options": [ 78 | { 79 | "name": "interceptF5", 80 | "type": "checkbox", 81 | "default": false, 82 | "description": "Override F5" 83 | }, 84 | { 85 | "name": "interceptCtrlR", 86 | "type": "checkbox", 87 | "default": false, 88 | "description": "Override Ctrl/Cmd-R" 89 | } 90 | ] 91 | } 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /ms/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Bell Schedule 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 44 | 45 | 48 | 49 | 50 |
51 |
52 |
53 |
54 |
V1.6.0
55 | 56 |
57 |
58 |

Options

59 |
60 |
61 | GitHub repository 62 |
63 |
64 | 65 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ms/scripts/doge.js: -------------------------------------------------------------------------------- 1 | //javascript:(function(){document.body.appendChild(document.createElement('script')).src="http://harkerdev.github.io/bellschedule/doge.js";setTimeout("startDoge(2000)",500);})(); 2 | //bookmarklet to run doge mode on any page (nouns will be the same, though) 3 | 4 | var suchAdjectives = ["such","very","much","many","so"]; 5 | 6 | var suchNouns = ["schedule","time","date","table","class","periods","lines","title","color","day","organize","impress", "harkerdev"]; 7 | 8 | var suchDelay = 2000; //delay between adding new div in ms 9 | var maxDoge = 5; //max number of dogeDivs 10 | var suchIntervalID; //interval ID for doge adding 11 | 12 | function setDoge(state) { 13 | if(state) startDoge(suchDelay); 14 | else stopDoge(); 15 | } 16 | 17 | function setDogeDelay(delay) { suchDelay = delay; } 18 | function setDogeMax(max) { maxDoge = max; } 19 | 20 | function startDoge(delay) { 21 | document.body.style.backgroundImage = "url('asset/doge.jpg')"; 22 | 23 | suchIntervalID = setInterval("swapDogeDiv()", delay); 24 | swapDogeDiv(); 25 | } 26 | 27 | function stopDoge() { 28 | document.body.style.backgroundImage = ""; 29 | 30 | clearInterval(suchIntervalID); 31 | suchIntervalID = null; 32 | 33 | var doge = document.getElementsByClassName("doge"); 34 | for(var i=0;i= maxDoge) 74 | removeDogeDiv(choose(doges)); 75 | createDogeDiv(); 76 | } 77 | 78 | function choose(array) { 79 | return array[randInt(array.length)]; 80 | } 81 | 82 | function randInt(x,y) { 83 | if(y==null) 84 | return Math.floor(Math.random()*x); 85 | else return Math.floor(Math.random()*(y-x) + x) 86 | } 87 | -------------------------------------------------------------------------------- /scripts/doge.js: -------------------------------------------------------------------------------- 1 | //javascript:(function(){document.body.appendChild(document.createElement('script')).src="http://harkerdev.github.io/bellschedule/doge.js";setTimeout("startDoge(2000)",500);})(); 2 | //bookmarklet to run doge mode on any page (nouns will be the same, though) 3 | setDoge(true); 4 | 5 | var suchAdjectives = ["such","very","much","many","so"]; 6 | 7 | var suchNouns = ["schedule","time","date","table","class","periods","lines","title","color","day","organize","impress", "harkerdev"]; 8 | 9 | var suchDelay = 2000; //delay between adding new div in ms 10 | var maxDoge = 5; //max number of dogeDivs 11 | var suchIntervalID; //interval ID for doge adding 12 | 13 | function setDoge(state) { 14 | if(state) startDoge(suchDelay); 15 | else stopDoge(); 16 | } 17 | 18 | function setDogeDelay(delay) { suchDelay = delay; } 19 | function setDogeMax(max) { maxDoge = max; } 20 | 21 | function startDoge(delay) { 22 | document.body.style.backgroundImage = "url('asset/doge.jpg')"; 23 | 24 | suchIntervalID = setInterval("swapDogeDiv()", delay); 25 | swapDogeDiv(); 26 | } 27 | 28 | function stopDoge() { 29 | document.body.style.backgroundImage = ""; 30 | 31 | clearInterval(suchIntervalID); 32 | suchIntervalID = null; 33 | 34 | var doge = document.getElementsByClassName("doge"); 35 | for(var i=0;i= maxDoge) 75 | removeDogeDiv(choose(doges)); 76 | createDogeDiv(); 77 | } 78 | 79 | function choose(array) { 80 | return array[randInt(array.length)]; 81 | } 82 | 83 | function randInt(x,y) { 84 | if(y==null) 85 | return Math.floor(Math.random()*x); 86 | else return Math.floor(Math.random()*(y-x) + x) 87 | } 88 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Bell Schedule 15 | 16 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 38 | 39 | 40 | 41 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 65 | 66 | 69 | 70 | 71 |
72 |
73 |
Made with <> by HarkerDev
74 |
75 |
76 | 77 | 78 |
79 |
80 |

Options

81 |
82 |
83 | GitHub repository 84 |
85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dev@harker.org. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /options.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": [ 3 | { 4 | "name": "General", 5 | "options": [ 6 | { 7 | "name": "color", 8 | "type": "checkbox", 9 | "default": false, 10 | "description": "Show color" 11 | }, 12 | { 13 | "name": "showPassingPeriods", 14 | "type": "checkbox", 15 | "default": true, 16 | "description": "Show passing periods" 17 | }, 18 | { 19 | "name": "enablePeriodNotifications", 20 | "type": "checkbox", 21 | "default": false, 22 | "description": "Enable period change notifications" 23 | }, 24 | { 25 | "name": "notificationDuration", 26 | "type": "number", 27 | "default": 5, 28 | "description": "Notification display time (seconds)" 29 | }, 30 | { 31 | "name": "enableDayView", 32 | "type": "checkbox", 33 | "default": false, 34 | "mobileDefault": true, 35 | "description": "Enable day view" 36 | }, 37 | { 38 | "name": "enableDoge", 39 | "type": "checkbox", 40 | "default": false, 41 | "mobileDefault": false, 42 | "description": "Enable doge" 43 | } 44 | ] 45 | }, 46 | { 47 | "name": "Course Names", 48 | "tooltip": "Replaces period numbers with your course names.", 49 | "options": [ 50 | { 51 | "name": "period1", 52 | "type": "text", 53 | "default": "", 54 | "description": "Period 1 Class" 55 | }, 56 | { 57 | "name": "period2", 58 | "type": "text", 59 | "default": "", 60 | "description": "Period 2 Class" 61 | }, 62 | { 63 | "name": "period3", 64 | "type": "text", 65 | "default": "", 66 | "description": "Period 3 Class" 67 | }, 68 | { 69 | "name": "period4", 70 | "type": "text", 71 | "default": "", 72 | "description": "Period 4 Class" 73 | }, 74 | { 75 | "name": "period5", 76 | "type": "text", 77 | "default": "", 78 | "description": "Period 5 Class" 79 | }, 80 | { 81 | "name": "period6", 82 | "type": "text", 83 | "default": "", 84 | "description": "Period 6 Class" 85 | }, 86 | { 87 | "name": "period7", 88 | "type": "text", 89 | "default": "", 90 | "description": "Period 7 Class" 91 | } 92 | ] 93 | }, 94 | { 95 | "name": "Automatic Updating", 96 | "tooltip": "Set to 0 to disable automatic updating", 97 | "options": [ 98 | { 99 | "name": "activeUpdateInterval", 100 | "type": "number", 101 | "default": 10, 102 | "description": "Active update interval (seconds)", 103 | "tooltip": "Applies while tab is selected and window is active" 104 | }, 105 | { 106 | "name": "inactiveUpdateInterval", 107 | "type": "number", 108 | "default": 30, 109 | "description": "Inactive update interval (seconds)", 110 | "tooltip": "Applies while tab is selected, but window is not active" 111 | }, 112 | { 113 | "name": "hiddenUpdateInterval", 114 | "type": "number", 115 | "default": 30, 116 | "description": "Hidden update interval (seconds)", 117 | "tooltip": "Applies while tab is not selected or window is minimized" 118 | } 119 | ] 120 | }, 121 | { 122 | "name": "Override Refresh", 123 | "tooltip": "Override default keyboard shortcuts for refresh and update the schedule instead. (Only really useful with automatic updating disabled.)", 124 | "platforms": ["desktop"], 125 | "options": [ 126 | { 127 | "name": "interceptF5", 128 | "type": "checkbox", 129 | "default": false, 130 | "description": "Override F5" 131 | }, 132 | { 133 | "name": "interceptCtrlR", 134 | "type": "checkbox", 135 | "default": false, 136 | "description": "Override Ctrl/Cmd-R" 137 | } 138 | ] 139 | } 140 | ] 141 | } 142 | -------------------------------------------------------------------------------- /ms/style.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------ */ 2 | /* General */ 3 | /* ------------------------------ */ 4 | 5 | body { 6 | margin: 0; 7 | font: .75em "Segoe UI", "Arial", sans-serif; 8 | text-align: center; 9 | background-position-x: center; 10 | } 11 | 12 | h1 { 13 | margin-bottom: .25em; 14 | } 15 | 16 | a { 17 | color: #5043E4; 18 | } 19 | 20 | a.plain { 21 | color: #000000; 22 | text-decoration: none; 23 | width: 158px; 24 | } 25 | 26 | button { 27 | color: #000; 28 | font-size: 12px; 29 | text-decoration: none; 30 | } 31 | 32 | #container { 33 | margin: auto; 34 | padding: 0 10px; 35 | } 36 | 37 | @keyframes fadeIO { 38 | 0% { 39 | opacity: 0; 40 | } 41 | 30% { 42 | opacity: 100%; 43 | } 44 | 70% { 45 | opacity: 100%; 46 | } 47 | 100% { 48 | opacity: 0; 49 | } 50 | } 51 | 52 | @-webkit-keyframes fadeIO { 53 | 0% { 54 | opacity: 0; 55 | } 56 | 30% { 57 | opacity: 100%; 58 | } 59 | 70% { 60 | opacity: 100%; 61 | } 62 | 100% { 63 | opacity: 0; 64 | } 65 | } 66 | 67 | 68 | /* iPhone */ 69 | 70 | @media only screen and (width:320px) and (device-width:320px) and (device-height:480px) and (orientation: landscape) { 71 | body { 72 | transform: (0.667); 73 | -webkit-transform: scale(0.667); 74 | transform-origin: top right; 75 | -webkit-transform-origin: top right; 76 | position: absolute; 77 | right: 0; 78 | top: 0; 79 | width: 480px; 80 | } 81 | } 82 | 83 | 84 | /* iPad */ 85 | 86 | @media only screen and (width:768px) and (device-width:768px) and (device-height:1024px) and (orientation: landscape) { 87 | body { 88 | transform: scale(0.75); 89 | -webkit-transform: scale(0.75); 90 | transform-origin: top right; 91 | -webkit-transform-origin: top right; 92 | position: absolute; 93 | right: 0; 94 | top: 0; 95 | width: 1024px; 96 | } 97 | } 98 | 99 | 100 | /* Apply absolute formatting on small screens so that there's padding on both sides of the schedule */ 101 | 102 | @media only screen and (max-width:814px) { 103 | .week #container { 104 | position: absolute; 105 | } 106 | } 107 | 108 | #titleTitles, 109 | #schedules, 110 | #changelog { 111 | display: none; 112 | } 113 | 114 | 115 | /* ------------------------------ */ 116 | /* Header */ 117 | /* ------------------------------ */ 118 | 119 | .noHighlight { 120 | /*-webkit-touch-callout: none; (disables context menu on mobile? perhaps this also disables highlighting on mobile?)*/ 121 | -webkit-user-select: none; 122 | -khtml-user-select: none; 123 | -moz-user-select: none; 124 | -ms-user-select: none; 125 | user-select: none; 126 | } 127 | 128 | #header { 129 | position: relative; 130 | margin: 1.25em auto; 131 | width: 100%; 132 | max-width: 400px; 133 | } 134 | 135 | #title { 136 | display: inline; 137 | /*-webkit-touch-callout: initial;*/ 138 | -webkit-user-select: initial; 139 | -khtml-user-select: initial; 140 | -moz-user-select: initial; 141 | -ms-user-select: initial; 142 | user-select: initial; 143 | } 144 | 145 | #leftArrow { 146 | float: left; 147 | } 148 | 149 | #leftArrow:hover { 150 | padding: 0 20px 0 4px; 151 | } 152 | 153 | #rightArrow { 154 | float: right; 155 | } 156 | 157 | #rightArrow:hover { 158 | padding: 0 4px 0 20px; 159 | } 160 | 161 | #leftArrow, 162 | #rightArrow { 163 | cursor: pointer; 164 | padding: 0 12px; 165 | font-weight: normal; 166 | transition: padding .2s; 167 | -webkit-transition: padding .2s; 168 | } 169 | 170 | #leftArrow:hover, 171 | #rightArrow:hover { 172 | font-weight: bold; 173 | } 174 | 175 | #warning { 176 | display: none; 177 | margin-bottom: 1em; 178 | } 179 | 180 | 181 | /* ------------------------------ */ 182 | /* Schedule */ 183 | /* ------------------------------ */ 184 | 185 | table { 186 | border-collapse: collapse; 187 | } 188 | 189 | #schedule { 190 | margin: auto; 191 | height: 300px; 192 | /* acts like min-height */ 193 | background-color: rgba(255, 255, 255, .5) 194 | } 195 | 196 | #schedule td { 197 | width: 158px; 198 | /* 2px short to account for 2px border */ 199 | border: 2px solid black; 200 | padding: 0 0 15px; 201 | vertical-align: top; 202 | } 203 | 204 | .periodone { 205 | background-color: #f9d689; 206 | } 207 | 208 | .periodtwo { 209 | background-color: #efa7cc; 210 | } 211 | 212 | .periodthree { 213 | background-color: #9df2aa; 214 | } 215 | 216 | .periodfour { 217 | background-color: #dd8d8d; 218 | } 219 | 220 | .periodfive { 221 | background-color: #9bccef; 222 | } 223 | 224 | .periodsix { 225 | background-color: #abaaf7; 226 | } 227 | 228 | .periodseven { 229 | background-color: #aeefde; 230 | } 231 | 232 | .period, 233 | .head { 234 | width: inherit; 235 | overflow: hidden; 236 | line-height: 110%; 237 | } 238 | 239 | .periodWrapper, 240 | .headWrapper { 241 | display: table-cell; 242 | width: inherit; 243 | border-bottom: 1px solid black; 244 | vertical-align: middle; 245 | } 246 | 247 | .periodLength { 248 | font-size: .8em; 249 | } 250 | 251 | .head { 252 | height: 3.25em; 253 | border-bottom: 2px solid black; 254 | font-size: 1.33em; 255 | } 256 | 257 | .headWrapper { 258 | height: inherit; 259 | } 260 | 261 | .headDate { 262 | font-size: .8em; 263 | } 264 | 265 | .headSchedId { 266 | font-size: .7em; 267 | } 268 | 269 | #today .head { 270 | font-weight: bold; 271 | } 272 | 273 | #today .head { 274 | background: rgba(128, 128, 128, .75); 275 | /* looks like #ddd on white background */ 276 | } 277 | 278 | #today .now { 279 | background-color: rgba(208, 208, 208, .75); 280 | /* looks close to #999 on #ddd background */ 281 | } 282 | 283 | .long { 284 | font-weight: bold; 285 | } 286 | 287 | #schedule .lunch { 288 | width: 100%; 289 | height: 100%; 290 | } 291 | 292 | #schedule .lunch td { 293 | width: 80px; 294 | /* half of day width */ 295 | padding: 0px; 296 | border-width: 0; 297 | } 298 | 299 | #schedule .lunch td:first-child { 300 | border-right: 1px solid black; 301 | } 302 | 303 | 304 | /* ------------------------------ */ 305 | /* Version */ 306 | /* ------------------------------ */ 307 | 308 | #version { 309 | position: fixed; 310 | bottom: 0px; 311 | left: 0px; 312 | padding: 0 3px; 313 | border-top-right-radius: 3px; 314 | color: #555; 315 | background-color: rgba(255, 255, 255, 0.5); 316 | } 317 | 318 | #version a { 319 | text-decoration: none; 320 | } 321 | 322 | 323 | /* ------------------------------ */ 324 | /* Options */ 325 | /* ------------------------------ */ 326 | 327 | #options { 328 | box-sizing: content-box; 329 | -webkit-box-sizing: content-box; 330 | -moz-box-sizing: content-box; 331 | position: fixed; 332 | right: -50%; 333 | bottom: -70%; 334 | border-left: 1px solid gray; 335 | border-top: 1px solid gray; 336 | border-top-left-radius: 5px; 337 | padding: 2.5em 0 0 2.5em; 338 | /*width: 30px; 339 | height: 30px;*/ 340 | width: 50%; 341 | height: 70%; 342 | background-color: rgba(207, 207, 207, 0.9); 343 | color: #333; 344 | transition: right .5s, bottom .5s, padding .5s, border-top-left-radius .5s; 345 | -webkit-transition: right .5s, bottom .5s, padding .5s border-top-left-radius .5s; 346 | z-index: 10; 347 | } 348 | 349 | #options.mobile { 350 | /* must be before #options.expanded for expanding to apply properly */ 351 | right: -100%; 352 | bottom: -100%; 353 | padding: 3.75em 0 0 3.75em; 354 | font-size: 1.2em; 355 | width: 100%; 356 | height: 100%; 357 | } 358 | 359 | #options.expanded { 360 | right: 0; 361 | bottom: 0; 362 | padding: 0; 363 | } 364 | 365 | #options.mobile.expanded { 366 | border-top-left-radius: 0; 367 | } 368 | 369 | #optionsTitle { 370 | margin: 0 0 5px; 371 | -webkit-transition: margin .5s; 372 | } 373 | 374 | .expanded #optionsTitle { 375 | margin-top: 30px; 376 | } 377 | 378 | #optionsArrow { 379 | position: absolute; 380 | left: 0; 381 | top: 0; 382 | padding: .2em .35em; 383 | font: 1.66em Arial; 384 | text-align: left; 385 | cursor: pointer; 386 | } 387 | 388 | .mobile #optionsArrow { 389 | font-size: 2.5em; 390 | } 391 | 392 | #options.mobile input[type=checkbox] { 393 | transform: scale(1.2); 394 | -webkit-transform: scale(1.2); 395 | } 396 | 397 | #options input[type=number], 398 | #options input[type=text] { 399 | border: 1px solid gray; 400 | background: rgba(255, 255, 255, .5); 401 | text-align: center; 402 | font-size: 1em; 403 | } 404 | 405 | #options input[type=number] { 406 | width: 2.5em; 407 | } 408 | 409 | #options input::-webkit-outer-spin-button, 410 | #options input::-webkit-inner-spin-button { 411 | /* hide spinner on number inputs in webkit browsers */ 412 | display: none; 413 | } 414 | 415 | #optionsContent { 416 | margin: 0 auto; 417 | } 418 | 419 | #optionsContent tr { 420 | height: 2em; 421 | } 422 | 423 | #optionsContent th { 424 | padding: 15px 0 5px; 425 | } 426 | 427 | #optionsContent td:first-child { 428 | padding-right: 15px; 429 | text-align: right; 430 | } 431 | 432 | .tooltipIndicator { 433 | text-decoration: underline; 434 | } 435 | 436 | #repo { 437 | position: absolute; 438 | right: 0; 439 | bottom: 0; 440 | padding: 0 5px 3px 0; 441 | } 442 | 443 | 444 | /* ------------------------------ */ 445 | /* Clock */ 446 | /* ------------------------------ */ 447 | 448 | #currentTime { 449 | position: absolute; 450 | right: 0; 451 | top: 0; 452 | padding: 7px 10px; 453 | border-bottom-left-radius: 5px; 454 | background: rgba(255, 255, 255, .5); 455 | font-size: 1.5em; 456 | font-weight: bold; 457 | /* letter-spacing: .05em; */ 458 | color: #333; 459 | } 460 | 461 | 462 | /* ------------------------------ */ 463 | /* Refresh */ 464 | /* ------------------------------ */ 465 | 466 | #refresh { 467 | display: none; 468 | position: fixed; 469 | top: 0; 470 | right: 0; 471 | padding-right: 3px; 472 | color: #AAA; 473 | font-size: 2em; 474 | cursor: pointer; 475 | transition: color .25s; 476 | -webkit-transition: color .25s; 477 | } 478 | 479 | #refresh:hover { 480 | color: #333; 481 | -webkit-user-select: none; 482 | -khtml-user-select: none; 483 | -moz-user-select: none; 484 | -ms-user-select: none; 485 | user-select: none; 486 | } 487 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------ */ 2 | /* General */ 3 | /* ------------------------------ */ 4 | 5 | body { 6 | margin: 0; 7 | font: .75em "Segoe UI", "Arial", sans-serif; 8 | text-align: center; 9 | background-position-x: center; 10 | 11 | } 12 | 13 | h1 { 14 | margin-bottom: .25em; 15 | } 16 | 17 | a { 18 | color: #5043E4; 19 | } 20 | 21 | a.plain { 22 | color: #000000; 23 | text-decoration: none; 24 | width: 158px; 25 | } 26 | 27 | button { 28 | color: #000; 29 | font-size: 12px; 30 | text-decoration: none; 31 | } 32 | 33 | #container { 34 | margin: auto; 35 | padding: 0 10px; 36 | } 37 | 38 | @keyframes fadeIO { 39 | 0% { 40 | opacity: 0; 41 | } 42 | 30% { 43 | opacity: 100%; 44 | } 45 | 70% { 46 | opacity: 100%; 47 | } 48 | 100% { 49 | opacity: 0; 50 | } 51 | } 52 | 53 | @-webkit-keyframes fadeIO { 54 | 0% { 55 | opacity: 0; 56 | } 57 | 30% { 58 | opacity: 100%; 59 | } 60 | 70% { 61 | opacity: 100%; 62 | } 63 | 100% { 64 | opacity: 0; 65 | } 66 | } 67 | 68 | /* iPhone */ 69 | 70 | @media only screen and (width:320px) and (device-width:320px) and (device-height:480px) and (orientation: landscape) { 71 | body { 72 | transform: (0.667); 73 | -webkit-transform: scale(0.667); 74 | transform-origin: top right; 75 | -webkit-transform-origin: top right; 76 | position: absolute; 77 | right: 0; 78 | top: 0; 79 | width: 480px; 80 | } 81 | } 82 | 83 | 84 | /* iPad */ 85 | 86 | @media only screen and (width:768px) and (device-width:768px) and (device-height:1024px) and (orientation: landscape) { 87 | body { 88 | transform: scale(0.75); 89 | -webkit-transform: scale(0.75); 90 | transform-origin: top right; 91 | -webkit-transform-origin: top right; 92 | position: absolute; 93 | right: 0; 94 | top: 0; 95 | width: 1024px; 96 | } 97 | } 98 | 99 | 100 | /* Apply absolute formatting on small screens so that there's padding on both sides of the schedule */ 101 | 102 | @media only screen and (max-width:814px) { 103 | .week #container { 104 | position: absolute; 105 | } 106 | } 107 | 108 | #titleTitles, 109 | #schedules, 110 | #changelog { 111 | display: none; 112 | } 113 | 114 | 115 | /* ------------------------------ */ 116 | /* Header */ 117 | /* ------------------------------ */ 118 | 119 | .noHighlight { 120 | /*-webkit-touch-callout: none; (disables context menu on mobile? perhaps this also disables highlighting on mobile?)*/ 121 | -webkit-user-select: none; 122 | -khtml-user-select: none; 123 | -moz-user-select: none; 124 | -ms-user-select: none; 125 | user-select: none; 126 | } 127 | 128 | #header { 129 | position: relative; 130 | margin: 1.25em auto; 131 | width: 100%; 132 | max-width: 400px; 133 | } 134 | 135 | #title { 136 | display: inline; 137 | /*-webkit-touch-callout: initial;*/ 138 | -webkit-user-select: initial; 139 | -khtml-user-select: initial; 140 | -moz-user-select: initial; 141 | -ms-user-select: initial; 142 | user-select: initial; 143 | } 144 | 145 | #leftArrow { 146 | float: left; 147 | } 148 | 149 | #leftArrow:hover { 150 | padding: 0 20px 0 4px; 151 | } 152 | 153 | #rightArrow { 154 | float: right; 155 | } 156 | 157 | #rightArrow:hover { 158 | padding: 0 4px 0 20px; 159 | } 160 | 161 | #leftArrow, 162 | #rightArrow { 163 | cursor: pointer; 164 | padding: 0 12px; 165 | font-weight: normal; 166 | transition: padding .2s; 167 | -webkit-transition: padding .2s; 168 | } 169 | 170 | #leftArrow:hover, 171 | #rightArrow:hover { 172 | font-weight: bold; 173 | } 174 | 175 | #warning { 176 | display: none; 177 | margin-bottom: 1em; 178 | } 179 | 180 | 181 | /* ------------------------------ */ 182 | /* Schedule */ 183 | /* ------------------------------ */ 184 | 185 | table { 186 | border-collapse: collapse; 187 | } 188 | 189 | #schedule { 190 | margin: auto; 191 | height: 300px; 192 | /* acts like min-height */ 193 | } 194 | 195 | #schedule td { 196 | width: 158px; 197 | /* 2px short to account for 2px border */ 198 | border: 2px solid black; 199 | padding: 0 0 15px; 200 | vertical-align: top; 201 | } 202 | 203 | .periodone { 204 | background-color: #f9d689; 205 | } 206 | 207 | .periodtwo { 208 | background-color: #efa7cc; 209 | } 210 | 211 | .periodthree { 212 | background-color: #9df2aa; 213 | } 214 | 215 | .periodfour { 216 | background-color: #dd8d8d; 217 | } 218 | 219 | .periodfive { 220 | background-color: #9bccef; 221 | } 222 | 223 | .periodsix { 224 | background-color: #abaaf7; 225 | } 226 | 227 | .periodseven { 228 | background-color: #aeefde; 229 | } 230 | 231 | .period, 232 | .head { 233 | width: inherit; 234 | overflow: hidden; 235 | line-height: 110%; 236 | } 237 | 238 | .periodWrapper, 239 | .headWrapper { 240 | display: table-cell; 241 | width: inherit; 242 | border-bottom: 1px solid black; 243 | vertical-align: middle; 244 | } 245 | 246 | .periodLength { 247 | font-size: .8em; 248 | } 249 | 250 | .head { 251 | height: 3.25em; 252 | border-bottom: 2px solid black; 253 | font-size: 1.33em; 254 | } 255 | 256 | .headWrapper { 257 | height: inherit; 258 | } 259 | 260 | .headDate { 261 | font-size: .8em; 262 | } 263 | 264 | .headSchedId { 265 | font-size: .7em; 266 | } 267 | 268 | #today .head { 269 | font-weight: bold; 270 | } 271 | 272 | #today .head { 273 | background: rgba(128, 128, 128, .75); 274 | /* looks like #ddd on white background */ 275 | } 276 | 277 | #today .now { 278 | background-color: rgba(208, 208, 208, .75); 279 | /* looks close to #999 on #ddd background */ 280 | } 281 | 282 | .long { 283 | font-weight: bold; 284 | } 285 | 286 | #schedule .lunch { 287 | width: 100%; 288 | height: 100%; 289 | } 290 | 291 | #schedule .lunch td { 292 | width: 80px; 293 | /* half of day width */ 294 | padding: 0px; 295 | border-width: 0; 296 | } 297 | 298 | #schedule .lunch td:first-child { 299 | border-right: 1px solid black; 300 | } 301 | 302 | 303 | /* ------------------------------ */ 304 | /* Version */ 305 | /* ------------------------------ */ 306 | 307 | #version { 308 | position: fixed; 309 | bottom: 0px; 310 | left: 0px; 311 | padding: 0 3px; 312 | border-top-right-radius: 3px; 313 | color: #555; 314 | background-color: rgba(255, 255, 255, 0.5); 315 | } 316 | 317 | #version a { 318 | text-decoration: none; 319 | } 320 | 321 | 322 | /* ------------------------------ */ 323 | /* Options */ 324 | /* ------------------------------ */ 325 | 326 | #options { 327 | box-sizing: content-box; 328 | -webkit-box-sizing: content-box; 329 | -moz-box-sizing: content-box; 330 | position: fixed; 331 | right: -50%; 332 | bottom: -75%; 333 | border-left: 1px solid gray; 334 | border-top: 1px solid gray; 335 | border-top-left-radius: 5px; 336 | padding: 2.5em 0 0 2.5em; 337 | /*width: 30px; 338 | height: 30px;*/ 339 | width: 50%; 340 | height: 75%; 341 | background-color: rgba(207, 207, 207, 0.9); 342 | color: #333; 343 | transition: right .5s, bottom .5s, padding .5s, border-top-left-radius .5s; 344 | -webkit-transition: right .5s, bottom .5s, padding .5s border-top-left-radius .5s; 345 | z-index: 10; 346 | overflow-y: scroll; 347 | } 348 | 349 | #options.mobile { 350 | /* must be before #options.expanded for expanding to apply properly */ 351 | right: -100%; 352 | bottom: -100%; 353 | padding: 3.75em 0 0 3.75em; 354 | font-size: 1.2em; 355 | width: 100%; 356 | height: 100%; 357 | } 358 | 359 | #options.expanded { 360 | right: 0; 361 | bottom: 0; 362 | padding: 0; 363 | } 364 | 365 | #options.mobile.expanded { 366 | border-top-left-radius: 0; 367 | } 368 | 369 | #optionsTitle { 370 | margin: 0 0 5px; 371 | -webkit-transition: margin .5s; 372 | } 373 | 374 | .expanded #optionsTitle { 375 | margin-top: 30px; 376 | } 377 | 378 | #optionsArrow { 379 | position: absolute; 380 | left: 0; 381 | top: 0; 382 | padding: .2em .35em; 383 | font: 1.66em Arial; 384 | text-align: left; 385 | cursor: pointer; 386 | } 387 | 388 | .mobile #optionsArrow { 389 | font-size: 2.5em; 390 | } 391 | 392 | #options.mobile input[type=checkbox] { 393 | transform: scale(1.2); 394 | -webkit-transform: scale(1.2); 395 | } 396 | 397 | #options input[type=number], 398 | #options input[type=text] { 399 | border: 1px solid gray; 400 | background: rgba(255, 255, 255, .5); 401 | text-align: center; 402 | font-size: 1em; 403 | } 404 | 405 | #options input[type=number] { 406 | width: 2.5em; 407 | } 408 | 409 | #options input[type=text] { 410 | width: 7.5em; 411 | } 412 | 413 | #options input::-webkit-outer-spin-button, 414 | #options input::-webkit-inner-spin-button { 415 | /* hide spinner on number inputs in webkit browsers */ 416 | display: none; 417 | } 418 | 419 | #optionsContent { 420 | margin: 0 auto; 421 | } 422 | 423 | #optionsContent tr { 424 | height: 2em; 425 | } 426 | 427 | #optionsContent th { 428 | padding: 15px 0 5px; 429 | } 430 | 431 | #optionsContent td:first-child { 432 | padding-right: 15px; 433 | text-align: right; 434 | } 435 | 436 | .tooltipIndicator { 437 | text-decoration: underline; 438 | } 439 | 440 | #repo { 441 | position: relative; 442 | text-align: right; 443 | right: 0; 444 | bottom: 0; 445 | padding: 0 5px 3px 0; 446 | } 447 | 448 | 449 | /* ------------------------------ */ 450 | /* Clock */ 451 | /* ------------------------------ */ 452 | 453 | #currentTime { 454 | position: absolute; 455 | right: 0; 456 | top: 0; 457 | padding: 7px 10px; 458 | border-bottom-left-radius: 5px; 459 | background: rgba(255, 255, 255, .5); 460 | font-size: 1.5em; 461 | font-weight: bold; 462 | /* letter-spacing: .05em; */ 463 | color: #333; 464 | } 465 | 466 | 467 | /* ------------------------------ */ 468 | /* Refresh */ 469 | /* ------------------------------ */ 470 | 471 | #refresh { 472 | display: none; 473 | position: fixed; 474 | top: 0; 475 | right: 0; 476 | padding-right: 3px; 477 | color: #AAA; 478 | font-size: 2em; 479 | cursor: pointer; 480 | transition: color .25s; 481 | -webkit-transition: color .25s; 482 | } 483 | 484 | #refresh:hover { 485 | color: #333; 486 | -webkit-user-select: none; 487 | -khtml-user-select: none; 488 | -moz-user-select: none; 489 | -ms-user-select: none; 490 | user-select: none; 491 | } 492 | -------------------------------------------------------------------------------- /ms/scripts/script.min.js: -------------------------------------------------------------------------------- 1 | addEventListener("scroll",function(e){document.getElementById("header").style.left=scrollX+"px"}),Array.prototype.diff=function(t){return this.filter(function(e){return t.indexOf(e)<0})};var schedules,displayDate,updateScheduleID,urlParams,isDoge,days=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],mobile=isMobile(),rawSchedule="",titles=[],titleStr="",forceTitle=!1,futureWarning="
Future 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("September 24, 2018"),START_SCHEDULE=1,LINKS={"Lunch 1 (7/8th)":"https://bokken12.github.io/harker-lunch/#$DAYNAMELOWER$","Lunch 2 (6th)":"https://bokken12.github.io/harker-lunch/#$DAYNAMELOWER$"},TOTAL_SCHEDULES=5,SCHEDULES=["Meeting","Regular/Advisory","Faculty","Regular/Advisory","Advisory/Assembly"],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]=[];0'+n.dateString+" "+r+"",i.appendChild(o),a.appendChild(i);var s="8:00";if(0")+2);return e}function setTitleTitle(e,t){var n=t?new Date(t):getDateFromUrlParams(),a=e.split("\n");1getMonday(new Date)&&(displayMessage+=futureWarning),warn(displayMessage)}function getMonday(e){var t=new Date(e);return 6<=t.getDay()?t.setDate(t.getDate()+2):t.setDate(t.getDate()-t.getDay()+1),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")+n+" – "+a),LINKS[t]){var l=document.createElement("a");l.className="plain",l.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()),l.appendChild(s),s=l}return e.appendChild(s)}}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 m=document.createElement("div");m.classList.add("period"),createPeriod(m,i,o,r,c),e.appendChild(m);var p=document.createElement("div");p.classList.add("period"),createPeriod(p,s,d,l,c),e.appendChild(p)}function goLast(){var e=new Date(displayDate);e.setDate(e.getDate()-(options.enableDayView?1:7)),updateSchedule(e,!1,!0),updateSearch(e)}function goNext(){var e=new Date(displayDate);e.setDate(e.getDate()+(options.enableDayView?1: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=Date.now());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;0<=i;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'+(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 | --------------------------------------------------------------------------------