jekyll-theme-open-course 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/_config.yml +2 -0
- data/_layouts/calendar.html +20 -18
- data/_layouts/default.html +19 -23
- data/_layouts/error.html +10 -0
- data/_layouts/projects.html +11 -11
- data/assets/css/screen.scss +10 -0
- data/assets/js/site.js +197 -5
- data/assets/js/sw.js +220 -0
- data/lib/jtoc.rb +1 -1
- data/lib/starter_files/_config.yml.erb +2 -0
- data/lib/starter_files/offline/index.md +7 -0
- data/lib/starter_files/syllabus/_policies/books.md +6 -2
- data/lib/starter_files/syllabus/_policies/materials.md +6 -2
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c44228f16269009a36ed91cb93d45493edda39485bce1bcb768a07a22945fb2
|
4
|
+
data.tar.gz: 74ee9d5d5fe34e59b200b6d865c764faa1c55cea6b2f940661ecc8ee7cf566f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7aebe324212b4fc8513f218d6298c05a8ea3f0a67f8941a58af242d81aa5169803668f0749d7bd254c6ac18e3828a4e91ccd227f24bbc297c8425046b126c48a
|
7
|
+
data.tar.gz: 29acc9e346bd4d78206878b98394d63b7474c12c1302f2d38da50559bc1e67d2cf02d5092e808db984df4e88863eda618285c53c8d50ac2aea2638b41463a637
|
data/_config.yml
CHANGED
data/_layouts/calendar.html
CHANGED
@@ -26,6 +26,7 @@ layout: default
|
|
26
26
|
</h2>
|
27
27
|
<small>Week {{ util.spelled_nums[week_int] }}</small>
|
28
28
|
</header>
|
29
|
+
<div class="agendas">
|
29
30
|
{% for meeting in week.meetings %}
|
30
31
|
<section class="agenda">
|
31
32
|
{% if site.course.days.size > 0 %}
|
@@ -43,7 +44,25 @@ layout: default
|
|
43
44
|
</ol>
|
44
45
|
</section>
|
45
46
|
{% endfor %}
|
46
|
-
|
47
|
+
</div>
|
48
|
+
{% assign be = entry.begins | date: "%s" %}
|
49
|
+
{% assign ex = entry.expires | date: "%s" %}
|
50
|
+
{% assign deliverables_li = "" %}
|
51
|
+
|
52
|
+
{%- capture deliverables_li -%}
|
53
|
+
{%- for project in projects -%}
|
54
|
+
{%- assign proj_int = forloop.index | times: 1 -%}
|
55
|
+
{%- capture proj_num -%}{%- if proj_int < 10 -%}0{%- endif -%}{{ proj_int }}{%- endcapture -%}
|
56
|
+
{%- for deliverable in project.deliverables -%}
|
57
|
+
{%- assign dl = deliverable.deadline | date: "%s" -%}
|
58
|
+
{%- if dl >= be and dl <= ex -%}
|
59
|
+
<li class="deadline">Project {{ util.spelled_nums[proj_int] }}: {{ deliverable.action | markdownify | remove: "<p>" | remove: "</p>" | strip_newlines }} (Due by {{ deliverable.deadline | date: "%A, %B %-e" }})</li>
|
60
|
+
{%- endif -%}
|
61
|
+
{%- endfor -%}
|
62
|
+
{%- endfor -%}
|
63
|
+
{%- endcapture -%}
|
64
|
+
|
65
|
+
{% if week.readings or week.tasks or deliverables_li.size > 1%}
|
47
66
|
<aside class="assigned">
|
48
67
|
<header>
|
49
68
|
<h3>Assigned Work</h3>
|
@@ -70,23 +89,6 @@ layout: default
|
|
70
89
|
</section>
|
71
90
|
{% endif %}
|
72
91
|
|
73
|
-
{% assign be = entry.begins | date: "%s" %}
|
74
|
-
{% assign ex = entry.expires | date: "%s" %}
|
75
|
-
{% assign deliverables_li = "" %}
|
76
|
-
|
77
|
-
{%- capture deliverables_li -%}
|
78
|
-
{%- for project in projects -%}
|
79
|
-
{%- assign proj_int = forloop.index | times: 1 -%}
|
80
|
-
{%- capture proj_num -%}{%- if proj_int < 10 -%}0{%- endif -%}{{ proj_int }}{%- endcapture -%}
|
81
|
-
{%- for deliverable in project.deliverables -%}
|
82
|
-
{%- assign dl = deliverable.deadline | date: "%s" -%}
|
83
|
-
{%- if dl >= be and dl <= ex -%}
|
84
|
-
<li class="deadline">Project {{ util.spelled_nums[proj_int] }}: {{ deliverable.action }} (Due by {{ deliverable.deadline | date: "%A, %B %-e" }})</li>
|
85
|
-
{%- endif -%}
|
86
|
-
{%- endfor -%}
|
87
|
-
{%- endfor -%}
|
88
|
-
{%- endcapture -%}
|
89
|
-
|
90
92
|
{% if deliverables_li.size > 1 %}
|
91
93
|
<!-- {{ deliverables_li.size }} -->
|
92
94
|
<section class="deadlines">
|
data/_layouts/default.html
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
|
-
<html lang="en" id="{{page.id}}" prefix="og: http://ogp.me/ns#">
|
2
|
+
<html lang="en" id="{{page.id}}" prefix="og: http://ogp.me/ns#" class="loading">
|
3
3
|
<head>{% assign course = site.course %}{% assign instructor = site.course.instructor %}
|
4
4
|
<meta charset="utf-8" />
|
5
5
|
<title>{{ course.number }}: {% if page.title %}{{ page.title}}{% else %}{{ course.title }}{% endif %}</title>
|
@@ -25,8 +25,8 @@
|
|
25
25
|
{{ course.term }}.
|
26
26
|
</p>
|
27
27
|
<nav id="quick-nav">
|
28
|
+
<h3 id="nav-con"><a href="#content"><span class="access">Jump to</span> {{ page.title }}</a></h3>
|
28
29
|
<ul class="nav">
|
29
|
-
<li id="nav-con"><a href="#content"><span class="access">Jump to</span> {{ page.title }}</a></li>
|
30
30
|
<li id="nav-nav"><a href="#full-nav"><span class="access">Jump to</span> Menu</a></li>
|
31
31
|
</ul>
|
32
32
|
</nav>
|
@@ -35,28 +35,26 @@
|
|
35
35
|
{{ content }}
|
36
36
|
</main>
|
37
37
|
|
38
|
+
<aside id="instructor">
|
39
|
+
<header>
|
40
|
+
<h3>Instructor</h3>
|
41
|
+
</header>
|
42
|
+
<ul>
|
43
|
+
<li>
|
44
|
+
<a href="{{ instructor.url }}">{{ instructor.honorific }} {{ instructor.name }}</a>,
|
45
|
+
{{ site.course.instructor.full_title | escape }}
|
46
|
+
</li>
|
47
|
+
<li><a href="mailto:{{ instructor.email }}">{{ instructor.email }}</a></li>
|
48
|
+
<li id="office-hours">
|
49
|
+
{{ instructor.office_hours }}
|
50
|
+
</li>
|
51
|
+
</ul>
|
52
|
+
</aside>
|
38
53
|
|
39
54
|
|
40
55
|
<footer id="footer">
|
41
|
-
<section id="instructor">
|
42
|
-
<header>
|
43
|
-
<h3>Instructor</h3>
|
44
|
-
</header>
|
45
|
-
<ul>
|
46
|
-
<li>
|
47
|
-
<a href="{{ instructor.url }}">{{ instructor.honorific }} {{ instructor.name }}</a>,
|
48
|
-
{{ site.course.instructor.full_title | escape }}
|
49
|
-
</li>
|
50
|
-
<li><a href="mailto:{{ instructor.email }}">{{ instructor.email }}</a></li>
|
51
|
-
<li id="office-hours">
|
52
|
-
{{ instructor.office_hours }}
|
53
|
-
</li>
|
54
|
-
</ul>
|
55
|
-
</section>
|
56
56
|
<nav id="full-nav">
|
57
|
-
<
|
58
|
-
<h3>Navigation</h3>
|
59
|
-
</header>
|
57
|
+
<h3>Navigation</h3>
|
60
58
|
<ul class="nav">
|
61
59
|
{% for nav in course.navigation %}
|
62
60
|
{% assign protocol = nav.href | slice: 0, 4 %}
|
@@ -70,9 +68,7 @@
|
|
70
68
|
</ul>
|
71
69
|
</nav>
|
72
70
|
<section id="colophon">
|
73
|
-
<
|
74
|
-
<h3>Colophon</h3>
|
75
|
-
</header>
|
71
|
+
<h3>Colophon</h3>
|
76
72
|
<p class="fineprint">
|
77
73
|
Course syllabus by <a href="{{ instructor.url }}">{{ instructor.name }}</a>.
|
78
74
|
Licensed under
|
data/_layouts/error.html
ADDED
data/_layouts/projects.html
CHANGED
@@ -19,22 +19,12 @@ layout: default
|
|
19
19
|
<small>Due by {{ project.due_date | date: "%A, %B %-e, %Y"}}</small>
|
20
20
|
</header>
|
21
21
|
|
22
|
-
<section class="description">
|
22
|
+
<section class="description {% if project.preview %}preview{% endif %}">
|
23
23
|
<h3>Project Description</h3>
|
24
24
|
{{ project.content }}
|
25
25
|
</section>
|
26
26
|
|
27
27
|
{% unless project.preview %}
|
28
|
-
{% if project.goals %}
|
29
|
-
<section class="goals">
|
30
|
-
<h3>Project Goals</h3>
|
31
|
-
<ul>
|
32
|
-
{% for goal in project.goals %}
|
33
|
-
<li>{{ goal | markdownify | remove: "<p>" | remove: "</p>" | strip_newlines }}</li>
|
34
|
-
{% endfor %}
|
35
|
-
</ul>
|
36
|
-
</section>
|
37
|
-
{% endif %}
|
38
28
|
{% if project.deliverables %}
|
39
29
|
<section class="deliverables">
|
40
30
|
<h3>Deliverables and Deadlines</h3>
|
@@ -66,6 +56,16 @@ layout: default
|
|
66
56
|
</ol>
|
67
57
|
</section>
|
68
58
|
{% endif %}
|
59
|
+
{% if project.goals %}
|
60
|
+
<section class="goals">
|
61
|
+
<h3>Project Goals</h3>
|
62
|
+
<ul>
|
63
|
+
{% for goal in project.goals %}
|
64
|
+
<li>{{ goal | markdownify | remove: "<p>" | remove: "</p>" | strip_newlines }}</li>
|
65
|
+
{% endfor %}
|
66
|
+
</ul>
|
67
|
+
</section>
|
68
|
+
{% endif %}
|
69
69
|
{% endunless %}
|
70
70
|
</article>
|
71
71
|
{% endfor %}
|
data/assets/css/screen.scss
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
---
|
2
2
|
---
|
3
|
+
@import url("{{site.course.css.font-url}}");
|
4
|
+
|
3
5
|
/* http://meyerweb.com/eric/tools/css/reset/
|
4
6
|
v2.0 | 20110126
|
5
7
|
License: none (public domain)
|
@@ -9,4 +11,12 @@
|
|
9
11
|
a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}html,.reset{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}table{border-collapse:collapse;border-spacing:0}
|
10
12
|
/* stylelint-enable */
|
11
13
|
|
14
|
+
html.loading,
|
15
|
+
html.loading * {
|
16
|
+
transition: none !important;
|
17
|
+
}
|
18
|
+
|
19
|
+
@import "fonts";
|
20
|
+
@import "colors";
|
12
21
|
@import "typography";
|
22
|
+
@import "base";
|
data/assets/js/site.js
CHANGED
@@ -1,5 +1,152 @@
|
|
1
1
|
// Add a .js utility class to <html>
|
2
|
-
document.querySelector('html')
|
2
|
+
var html = document.querySelector('html');
|
3
|
+
html.classList.add('js');
|
4
|
+
|
5
|
+
// Register service worker, if browser supports them
|
6
|
+
if ('serviceWorker' in navigator) {
|
7
|
+
var scope = detectSiteScope(location.href);
|
8
|
+
// console.log(scope.path);
|
9
|
+
navigator.serviceWorker.register(scope.path + 'assets/js/sw.js', { scope: scope.path })
|
10
|
+
.then(function(registration) {
|
11
|
+
console.log('Registered service worker scoped to', registration.scope);
|
12
|
+
})
|
13
|
+
.catch(function(error) {
|
14
|
+
console.error('Failed to register service worker', error)
|
15
|
+
});
|
16
|
+
|
17
|
+
function detectSiteScope(url) {
|
18
|
+
var scope = {};
|
19
|
+
scope.id = url.split('/')[3];
|
20
|
+
scope.path = '/';
|
21
|
+
if (scope.id.length > 0) {
|
22
|
+
scope.path = '/' + scope.id + '/';
|
23
|
+
}
|
24
|
+
return scope;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
function themeSwitcher() {
|
29
|
+
// Exit fast if no CSS properties support
|
30
|
+
if (!('supports' in CSS && CSS.supports("(--foo: bar)"))) {
|
31
|
+
return;
|
32
|
+
}
|
33
|
+
|
34
|
+
var header = document.querySelector('#header h1');
|
35
|
+
var toggle = document.createElement('a');
|
36
|
+
var html = document.querySelector('html');
|
37
|
+
// Icons from https://remixicon.com/
|
38
|
+
var icons = {
|
39
|
+
light: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></svg>',
|
40
|
+
dark: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.38 2.019a7.5 7.5 0 1 0 10.6 10.6C21.662 17.854 17.316 22 12.001 22 6.477 22 2 17.523 2 12c0-5.315 4.146-9.661 9.38-9.981z"/></svg>'
|
41
|
+
}
|
42
|
+
|
43
|
+
var dark_mode = false;
|
44
|
+
var modes = ['light','dark'];
|
45
|
+
|
46
|
+
if ('matchMedia' in window) {
|
47
|
+
dark_mode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
48
|
+
if (dark_mode) {
|
49
|
+
modes.reverse(); // ['dark','light']
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
if (storageAvailable('localStorage')) {
|
54
|
+
if (!localStorage.getItem('modes')) {
|
55
|
+
localStorage.setItem('modes',modes.join(','));
|
56
|
+
} else {
|
57
|
+
modes = localStorage.getItem('modes').split(',');
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
html.classList.add(modes[0]);
|
62
|
+
|
63
|
+
toggle.id = 'theme-toggle';
|
64
|
+
toggle.href = '#null';
|
65
|
+
toggle.title = 'Switch to ' + modes[1] + ' theme';
|
66
|
+
toggle.innerHTML = icons[modes[1]];
|
67
|
+
header.appendChild(toggle);
|
68
|
+
|
69
|
+
toggle.addEventListener('click', function(e) {
|
70
|
+
e.preventDefault();
|
71
|
+
html.classList.replace(modes[0],modes[1]);
|
72
|
+
modes.reverse();
|
73
|
+
if (storageAvailable('localStorage')) {
|
74
|
+
localStorage.setItem('modes',modes.join(','));
|
75
|
+
}
|
76
|
+
toggle.title = 'Switch to ' + modes[1] + ' theme';
|
77
|
+
toggle.innerHTML = icons[modes[1]];
|
78
|
+
});
|
79
|
+
|
80
|
+
function storageAvailable(type) {
|
81
|
+
try {
|
82
|
+
var storage = window[type];
|
83
|
+
var x = '__storage_test__';
|
84
|
+
storage.setItem(x, x);
|
85
|
+
storage.removeItem(x);
|
86
|
+
return true;
|
87
|
+
}
|
88
|
+
catch(e) {
|
89
|
+
return false;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
themeSwitcher();
|
95
|
+
|
96
|
+
// Move the nav to the header when there is room
|
97
|
+
// Responsive detection
|
98
|
+
function responsiveFeature(feature) {
|
99
|
+
var size = window
|
100
|
+
.getComputedStyle(document.body, ':after')
|
101
|
+
.getPropertyValue('content');
|
102
|
+
var has_feature = true;
|
103
|
+
if(size.indexOf(feature) === -1) {
|
104
|
+
has_feature = false;
|
105
|
+
}
|
106
|
+
return has_feature;
|
107
|
+
}
|
108
|
+
|
109
|
+
function ToggledNav() {
|
110
|
+
this.nav = document.querySelector('#full-nav .nav');
|
111
|
+
this.quick_nav = document.querySelector('#quick-nav .nav');
|
112
|
+
this.full_nav = document.querySelector('#full-nav');
|
113
|
+
this.nav_nav;
|
114
|
+
this.nav_items = [];
|
115
|
+
this.toggle = function() {
|
116
|
+
if (responsiveFeature('navbar') && !html.classList.contains('navbar')) {
|
117
|
+
while (this.nav.firstChild) {
|
118
|
+
if (this.nav.firstChild.tagName) {
|
119
|
+
this.nav_items.push(this.nav.removeChild(this.nav.firstChild));
|
120
|
+
} else {
|
121
|
+
this.nav.removeChild(this.nav.firstChild); // remove text nodes
|
122
|
+
}
|
123
|
+
}
|
124
|
+
for (var i = 0; i < this.nav_items.length; i++) {
|
125
|
+
this.quick_nav.appendChild(this.nav_items[i]);
|
126
|
+
}
|
127
|
+
this.nav_nav = this.quick_nav.removeChild(document.getElementById('nav-nav'));
|
128
|
+
html.classList.add('navbar');
|
129
|
+
this.full_nav.classList.add('hidden');
|
130
|
+
}
|
131
|
+
if (!responsiveFeature('navbar') && html.classList.contains('navbar')) {
|
132
|
+
console.log('nav-nav node name:', this.nav_nav.nodeName);
|
133
|
+
this.quick_nav.appendChild(this.nav_nav);
|
134
|
+
for (var i = 0; i < this.nav_items.length; i++) {
|
135
|
+
this.nav.appendChild(this.nav_items[i]);
|
136
|
+
}
|
137
|
+
this.full_nav.classList.remove('hidden');
|
138
|
+
html.classList.remove('navbar');
|
139
|
+
}
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
var tn = new ToggledNav();
|
144
|
+
tn.toggle();
|
145
|
+
|
146
|
+
window.addEventListener('resize', function() {
|
147
|
+
tn.toggle();
|
148
|
+
});
|
149
|
+
|
3
150
|
|
4
151
|
// Capture and replicate the current week at the top of the calendar
|
5
152
|
if (document.querySelector('#calendar')) {
|
@@ -10,9 +157,9 @@ if (document.querySelector('#calendar')) {
|
|
10
157
|
current_week.classList.remove('past'); // remove the past class
|
11
158
|
current_week.querySelector('#this-week small').innerText = "This Week";
|
12
159
|
document.querySelector('#content').prepend(current_week); // insert the copy at the top of the calendar
|
13
|
-
if (location.hash === '') {
|
14
|
-
|
15
|
-
}
|
160
|
+
// if (location.hash === '') {
|
161
|
+
// location.hash = '#this-week'; // point at the new hash position; viewport should show this one
|
162
|
+
// }
|
16
163
|
var btn_show_calendar = document.createElement('a');
|
17
164
|
btn_show_calendar.id = "btn-show-calendar";
|
18
165
|
btn_show_calendar.href = "#null";
|
@@ -29,10 +176,55 @@ if (document.querySelector('#calendar')) {
|
|
29
176
|
current_week.insertAdjacentElement('afterend', btn_show_calendar);
|
30
177
|
}
|
31
178
|
|
179
|
+
if ('fetch' in window) {
|
180
|
+
var namedDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
181
|
+
var namedMonths = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
|
182
|
+
|
183
|
+
var github_url = (function() {
|
184
|
+
var url = document.querySelector('#github').getAttribute('href'); // grab the href value of the repo link
|
185
|
+
if (typeof(url) !== 'undefined') {
|
186
|
+
var fragment = url.substring(url.indexOf('.com/') + 5); // find the tail end (5 = .com/)
|
187
|
+
return 'https://api.github.com/repos/' + fragment + '/commits?per_page=1'; // return the API url
|
188
|
+
}
|
189
|
+
})();
|
190
|
+
|
191
|
+
function escapeHTML(str) {
|
192
|
+
var div = document.createElement('div');
|
193
|
+
div.appendChild(document.createTextNode(str));
|
194
|
+
return div.innerHTML;
|
195
|
+
}
|
196
|
+
|
197
|
+
if(typeof(github_url) !== "undefined") {
|
198
|
+
fetch(github_url)
|
199
|
+
.then(function(response) {
|
200
|
+
return response.json();
|
201
|
+
})
|
202
|
+
.then(function(data) {
|
203
|
+
var commit = {};
|
204
|
+
data = data[0]; // only need most recent commit
|
205
|
+
// Lowercase commit message's first word to run in `...to XYZ` copy:
|
206
|
+
commit.message = data.commit.message.charAt(0).toLowerCase() + data.commit.message.slice(1);
|
207
|
+
commit.url = data.html_url;
|
208
|
+
commit.stamp = data.commit.author.date;
|
209
|
+
commit.date = new Date(commit.stamp);
|
210
|
+
// Put the date in Day, Month 31 at <Local Time String> format
|
211
|
+
commit.time_string = namedDays[commit.date.getDay()] + ', ' +
|
212
|
+
namedMonths[commit.date.getMonth()] + ' ' +
|
213
|
+
commit.date.getDate() + ' at ' + commit.date.toLocaleTimeString();
|
214
|
+
// Append to footer on calendar
|
215
|
+
document.querySelector('#footer p').innerHTML +=
|
216
|
+
' Course last updated on <time datetime="' + commit.stamp + '">' + commit.time_string +
|
217
|
+
'</time> to <a id="commit-message" href="' + commit.url + '">' + escapeHTML(commit.message) + '</a>.';
|
218
|
+
});
|
219
|
+
}
|
220
|
+
}
|
221
|
+
|
32
222
|
window.addEventListener('keyup', function(e) {
|
33
223
|
// console.log(e.keyCode);
|
34
224
|
// Toggle the visibility of gridlines when `g` is pressed
|
35
225
|
if (e.keyCode === 71) {
|
36
226
|
document.querySelector('html').classList.toggle('g');
|
37
227
|
}
|
38
|
-
})
|
228
|
+
});
|
229
|
+
|
230
|
+
html.classList.remove('loading');
|
data/assets/js/sw.js
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
var VERSION;
|
2
|
+
const version = VERSION ? VERSION : mockTenMinuteVersion();
|
3
|
+
|
4
|
+
// Update with all essential and supporting assets
|
5
|
+
// (supporting assets might be things URLs to externally hosted web fonts)
|
6
|
+
// There can be NO errors here, or nothing will be cached
|
7
|
+
const site_scope = detectSiteScope(location.href);
|
8
|
+
const site_offline_path = 'offline/';
|
9
|
+
const site_preloaded_assets = {
|
10
|
+
essential: [
|
11
|
+
'assets/css/screen.css',
|
12
|
+
'assets/css/print.css',
|
13
|
+
'assets/js/site.js',
|
14
|
+
site_offline_path
|
15
|
+
].map(function(asset){
|
16
|
+
return site_scope.path + asset;
|
17
|
+
}),
|
18
|
+
supporting: [].map(function(asset){
|
19
|
+
return site_scope.path + asset;
|
20
|
+
})
|
21
|
+
};
|
22
|
+
// Also preload the home page into the cache
|
23
|
+
site_preloaded_assets.essential.push(site_scope.path);
|
24
|
+
|
25
|
+
const site_cache_of = {
|
26
|
+
assets: `assets.${version}`,
|
27
|
+
pages: `pages`,
|
28
|
+
requests: `requests`
|
29
|
+
};
|
30
|
+
|
31
|
+
// prefix scoped values
|
32
|
+
if (site_scope.id.length > 0) {
|
33
|
+
for (c in site_cache_of) {
|
34
|
+
site_cache_of[c] = site_scope.id + '.' + site_cache_of[c];
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
|
39
|
+
// For convenience in the activation event, programmatically build a simple array
|
40
|
+
// of the names of all the caches listed in the site_caches object literal
|
41
|
+
const site_cache_list = [];
|
42
|
+
for (let c in site_cache_of) {
|
43
|
+
site_cache_list.push(site_cache_of[c]);
|
44
|
+
}
|
45
|
+
|
46
|
+
|
47
|
+
// The first step in a ServiceWorker's life cycle is to install it...
|
48
|
+
addEventListener('install', function(e) {
|
49
|
+
console.log('Preparing to install the service worker...');
|
50
|
+
// shorthand for ServiceWorkerGlobalScope.self
|
51
|
+
// see https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/self
|
52
|
+
// documentation for skipWaiting() at
|
53
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting
|
54
|
+
self.skipWaiting();
|
55
|
+
e.waitUntil(
|
56
|
+
caches.open(site_cache_of.assets)
|
57
|
+
.then(function(c) {
|
58
|
+
// non-essential/nice-to-have assets are added asynchronously
|
59
|
+
c.addAll(site_preloaded_assets.supporting);
|
60
|
+
// *synchronously* add only for essential assets and fallbacks
|
61
|
+
return c.addAll(site_preloaded_assets.essential);
|
62
|
+
})
|
63
|
+
.catch(function(e) {
|
64
|
+
console.error('Caches error:', e);
|
65
|
+
})
|
66
|
+
);
|
67
|
+
// end install event listener
|
68
|
+
});
|
69
|
+
|
70
|
+
// Once the ServiceWorker has been installed, it must be activated. Ordinarily, that will only
|
71
|
+
// happen if all tabs and windows open to your site on a user's computer are closed. But the
|
72
|
+
// call to skipWaiting() above is more aggressive, and activates the ServiceWorker immediately.
|
73
|
+
// The primary tasks of the activate event function is to check all existing caches and delete
|
74
|
+
// any that aren't listed in the site_cache_list created above
|
75
|
+
addEventListener('activate', function(e) {
|
76
|
+
console.log('The service worker is activated!');
|
77
|
+
e.waitUntil(
|
78
|
+
caches.keys()
|
79
|
+
.then(function(existing_caches) {
|
80
|
+
// Only work with relevant caches, e.g., those in the same site_scope
|
81
|
+
let relevant_caches = existing_caches.filter(function(c) {
|
82
|
+
return c.includes(site_scope.id);
|
83
|
+
});
|
84
|
+
return Promise.all(
|
85
|
+
relevant_caches.map(function(relevant_cache) {
|
86
|
+
if (!site_cache_list.includes(relevant_cache)) {
|
87
|
+
return caches.delete(relevant_cache);
|
88
|
+
}
|
89
|
+
})
|
90
|
+
);
|
91
|
+
})
|
92
|
+
.then(function(){
|
93
|
+
// see https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim
|
94
|
+
return clients.claim();
|
95
|
+
})
|
96
|
+
// end waitUntil
|
97
|
+
);
|
98
|
+
// end activate event listener
|
99
|
+
});
|
100
|
+
|
101
|
+
// Now we get to the main monkey business: intercepting fetch events and instructing the browser
|
102
|
+
// whether to read from a cache, try the network, and so on.
|
103
|
+
|
104
|
+
addEventListener('fetch', function(fe) {
|
105
|
+
// hold onto a copy of the original request
|
106
|
+
const request = fe.request;
|
107
|
+
// Pages: Try the network first; if it's available, cache and return the page; otherwise, serve
|
108
|
+
// from the cache if it exists, or respond with the offline HTML
|
109
|
+
if (request.headers.get('Accept').includes('text/html')) {
|
110
|
+
fe.respondWith(
|
111
|
+
fetch(request)
|
112
|
+
.then(function(fetch_response) {
|
113
|
+
const copy = fetch_response.clone();
|
114
|
+
fe.waitUntil(
|
115
|
+
caches.open(site_cache_of.pages)
|
116
|
+
.then(function(this_cache) {
|
117
|
+
this_cache.put(request,copy);
|
118
|
+
})
|
119
|
+
);
|
120
|
+
return fetch_response;
|
121
|
+
})
|
122
|
+
.catch(function(error) {
|
123
|
+
return caches.match(request)
|
124
|
+
.then(function(cached_response) {
|
125
|
+
if (cached_response) {
|
126
|
+
return cached_response;
|
127
|
+
}
|
128
|
+
return caches.match(site_scope.path + site_offline_path);
|
129
|
+
});
|
130
|
+
|
131
|
+
})
|
132
|
+
// end respondWith
|
133
|
+
);
|
134
|
+
return;
|
135
|
+
}
|
136
|
+
|
137
|
+
// External requests: Try the cache first; update the cache from the network
|
138
|
+
if (!request.url.includes(location.hostname)) {
|
139
|
+
fe.respondWith(
|
140
|
+
caches.match(request)
|
141
|
+
.then(function(cached_response) {
|
142
|
+
if (cached_response) {
|
143
|
+
fe.waitUntil(
|
144
|
+
fetch(request)
|
145
|
+
.then(function(fetch_response){
|
146
|
+
caches.open(site_cache_of.requests)
|
147
|
+
.then(function(this_cache){
|
148
|
+
return this_cache.put(request, fetch_response);
|
149
|
+
});
|
150
|
+
})
|
151
|
+
);
|
152
|
+
return cached_response;
|
153
|
+
}
|
154
|
+
return fetch(request)
|
155
|
+
.then(function(fetch_response) {
|
156
|
+
const copy = fetch_response.clone();
|
157
|
+
fe.waitUntil(
|
158
|
+
caches.open(site_cache_of.requests)
|
159
|
+
.then(function(this_cache) {
|
160
|
+
this_cache.put(request, copy);
|
161
|
+
})
|
162
|
+
);
|
163
|
+
return fetch_response;
|
164
|
+
});
|
165
|
+
})
|
166
|
+
// end respondWith
|
167
|
+
);
|
168
|
+
return;
|
169
|
+
}
|
170
|
+
|
171
|
+
// Everything else: try the cache first, then the network; if the network fails, respond with the
|
172
|
+
// offline site path
|
173
|
+
fe.respondWith(
|
174
|
+
caches.match(request)
|
175
|
+
.then(function(cached_response) {
|
176
|
+
if (cached_response) {
|
177
|
+
return cached_response;
|
178
|
+
}
|
179
|
+
return fetch(request)
|
180
|
+
.catch(function(error) {
|
181
|
+
return caches.match(site_scope.path + site_offline_path);
|
182
|
+
})
|
183
|
+
}
|
184
|
+
)
|
185
|
+
);
|
186
|
+
|
187
|
+
// end fetch event listener
|
188
|
+
});
|
189
|
+
|
190
|
+
|
191
|
+
// Helper and utility functions
|
192
|
+
|
193
|
+
function mockTenMinuteVersion() {
|
194
|
+
var d = new Date();
|
195
|
+
var year = d.getFullYear().toString();
|
196
|
+
var month = (d.getMonth() + 1).toString();
|
197
|
+
var date = d.getDate().toString();
|
198
|
+
var mins = new Date().getMinutes();
|
199
|
+
mins = Math.floor(mins/10).toString();
|
200
|
+
return year + '.' + zeroPad(month) + '.' + zeroPad(date) + '-' + zeroPad(mins);
|
201
|
+
function zeroPad(num,n = 2) {
|
202
|
+
num = num.toString();
|
203
|
+
while (num.length < n) {
|
204
|
+
num = '0' + num;
|
205
|
+
}
|
206
|
+
return num;
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
function detectSiteScope(url) {
|
211
|
+
var scope = {};
|
212
|
+
scope.id = url.split('/')[3];
|
213
|
+
scope.path = '/';
|
214
|
+
if (scope.id.length > 0) {
|
215
|
+
scope.path = '/' + scope.id + '/';
|
216
|
+
}
|
217
|
+
return scope;
|
218
|
+
}
|
219
|
+
|
220
|
+
// console.log(version);
|
data/lib/jtoc.rb
CHANGED
@@ -2,12 +2,16 @@
|
|
2
2
|
title: Books
|
3
3
|
---
|
4
4
|
|
5
|
-
<
|
5
|
+
<section class="required" markdown="1">
|
6
|
+
<h3>Required</h3>
|
6
7
|
|
7
8
|
- Required book one
|
8
9
|
- Required book two
|
10
|
+
</section>
|
9
11
|
|
10
|
-
<
|
12
|
+
<section class="recommended" markdown="1">
|
13
|
+
<h3>Recommended</h3>
|
11
14
|
|
12
15
|
- Recommended book one
|
13
16
|
- Recommended book two
|
17
|
+
</section>
|
@@ -2,12 +2,16 @@
|
|
2
2
|
title: Materials
|
3
3
|
---
|
4
4
|
|
5
|
-
<
|
5
|
+
<section class="required" markdown="1">
|
6
|
+
<h3>Required</h3>
|
6
7
|
|
7
8
|
- Required material one
|
8
9
|
- Required material two
|
10
|
+
</section>
|
9
11
|
|
10
|
-
<
|
12
|
+
<section class="recommended" markdown="1">
|
13
|
+
<h3>Recommended</h3>
|
11
14
|
|
12
15
|
- Recommended material one
|
13
16
|
- Recommended material two
|
17
|
+
</section>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-theme-open-course
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Karl Stolley
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jekyll
|
@@ -67,12 +67,14 @@ files:
|
|
67
67
|
- _data/utility.yml
|
68
68
|
- _layouts/calendar.html
|
69
69
|
- _layouts/default.html
|
70
|
+
- _layouts/error.html
|
70
71
|
- _layouts/policies.html
|
71
72
|
- _layouts/projects.html
|
72
73
|
- assets/css/print.css
|
73
74
|
- assets/css/screen.scss
|
74
75
|
- assets/img/te.png
|
75
76
|
- assets/js/site.js
|
77
|
+
- assets/js/sw.js
|
76
78
|
- exe/jtoc
|
77
79
|
- index.md
|
78
80
|
- lib/jtoc.rb
|
@@ -80,6 +82,7 @@ files:
|
|
80
82
|
- lib/starter_files/_data/calendar.yml.erb
|
81
83
|
- lib/starter_files/_data/utility.yml
|
82
84
|
- lib/starter_files/index.md.erb
|
85
|
+
- lib/starter_files/offline/index.md
|
83
86
|
- lib/starter_files/syllabus/_policies/academic-integrity.md
|
84
87
|
- lib/starter_files/syllabus/_policies/assignment-submission.md
|
85
88
|
- lib/starter_files/syllabus/_policies/books.md
|