jekyll-theme-open-course 0.0.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +6 -6
- data/_config.yml +35 -2
- data/_data/utility.yml +1 -1
- data/_layouts/calendar.html +68 -17
- data/_layouts/default.html +39 -22
- data/_layouts/error.html +10 -0
- data/_layouts/policies.html +17 -0
- data/_layouts/projects.html +71 -0
- data/_sass/_base.scss +108 -0
- data/_sass/_colors.scss +190 -0
- data/_sass/_fonts.scss +10 -0
- data/_sass/_typography.scss +358 -0
- data/assets/css/print.css +26 -24
- data/assets/css/screen.scss +22 -0
- data/assets/js/site.js +289 -0
- data/assets/js/sw.js +220 -0
- data/exe/jtoc +76 -0
- data/lib/jtoc.rb +177 -0
- data/lib/starter_files/_config.yml.erb +84 -0
- data/lib/starter_files/_data/calendar.yml.erb +6 -0
- data/lib/starter_files/_data/utility.yml +35 -0
- data/lib/starter_files/index.md.erb +5 -0
- data/lib/starter_files/offline/index.md +7 -0
- data/lib/starter_files/syllabus/_policies/academic-integrity.md +3 -0
- data/lib/starter_files/syllabus/_policies/assignment-submission.md +3 -0
- data/lib/starter_files/syllabus/_policies/books.md +17 -0
- data/lib/starter_files/syllabus/_policies/description.md +3 -0
- data/lib/starter_files/syllabus/_policies/goals.md +3 -0
- data/lib/starter_files/syllabus/_policies/grading-criteria.md +3 -0
- data/lib/starter_files/syllabus/_policies/grading-policy.md +3 -0
- data/lib/starter_files/syllabus/_policies/late-work.md +3 -0
- data/lib/starter_files/syllabus/_policies/materials.md +17 -0
- data/lib/starter_files/syllabus/_policies/objectives.md +3 -0
- data/lib/starter_files/syllabus/_policies/outcomes.md +3 -0
- data/lib/starter_files/syllabus/_policies/participation.md +3 -0
- data/lib/starter_files/syllabus/_policies/special-needs.md +3 -0
- data/lib/starter_files/syllabus/_policies/technology-policy.md +3 -0
- data/lib/starter_files/syllabus/_projects/project-00.md.erb +23 -0
- data/lib/starter_files/syllabus/_weeks/week-00.md.erb +11 -0
- data/projects/index.md +1 -30
- metadata +39 -26
- data/_data/weeks/01.yml +0 -21
- data/_data/weeks/02.yml +0 -1
- data/_data/weeks/03.yml +0 -1
- data/_data/weeks/04.yml +0 -1
- data/_data/weeks/05.yml +0 -1
- data/_data/weeks/06.yml +0 -1
- data/_data/weeks/07.yml +0 -1
- data/_data/weeks/08.yml +0 -1
- data/_data/weeks/09.yml +0 -1
- data/_data/weeks/10.yml +0 -1
- data/_data/weeks/11.yml +0 -1
- data/_data/weeks/12.yml +0 -1
- data/_data/weeks/13.yml +0 -1
- data/_data/weeks/14.yml +0 -1
- data/_data/weeks/15.yml +0 -1
- data/_data/weeks/16.yml +0 -1
- data/_projects/project-01.md +0 -25
- data/_projects/project-02.md +0 -25
- data/_projects/project-03.md +0 -25
- data/assets/.keep +0 -0
- data/assets/css/screen.css +0 -408
data/assets/css/print.css
CHANGED
@@ -1,31 +1,33 @@
|
|
1
|
-
#navigation,
|
2
|
-
#this-week,
|
3
|
-
#links,
|
4
|
-
.fineprint + .fineprint,
|
5
|
-
.week h4 a {
|
6
|
-
display: none;
|
7
|
-
}
|
8
1
|
#header,
|
9
|
-
#this-week,
|
10
|
-
#content,
|
11
|
-
#links,
|
12
|
-
#instructor,
|
13
2
|
#footer {
|
14
|
-
|
15
|
-
padding-left: 0.3in;
|
3
|
+
color: inherit;
|
16
4
|
}
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
color: #000;
|
22
|
-
font-size: 11pt;
|
5
|
+
a,
|
6
|
+
#header a {
|
7
|
+
text-decoration: none;
|
8
|
+
color: inherit;
|
23
9
|
}
|
24
|
-
a {
|
25
|
-
|
10
|
+
#content a[href^="http"]::after {
|
11
|
+
display: inline;
|
12
|
+
content: "(" attr(href) ")";
|
13
|
+
font-family: Menlo, Monaco, "Droid Sans Mono", Courier, "Courier New", monospace;
|
14
|
+
font-size: 0.85em;
|
15
|
+
color: #CCC;
|
26
16
|
text-decoration: none;
|
27
17
|
}
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
p,li {
|
19
|
+
-webkit-hyphens: none;
|
20
|
+
hyphens: none;
|
21
|
+
}
|
22
|
+
h2 {
|
23
|
+
font-family: "moderno-fb-condensed";
|
24
|
+
font-weight: 700;
|
25
|
+
}
|
26
|
+
|
27
|
+
|
28
|
+
/* Hide Navigation Stuff */
|
29
|
+
|
30
|
+
#nav-nav,
|
31
|
+
#full-nav {
|
32
|
+
display: none;
|
31
33
|
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
---
|
2
|
+
---
|
3
|
+
@import url("{{site.course.css.font-url}}");
|
4
|
+
|
5
|
+
/* http://meyerweb.com/eric/tools/css/reset/
|
6
|
+
v2.0 | 20110126
|
7
|
+
License: none (public domain)
|
8
|
+
*/
|
9
|
+
/* stylelint-disable */
|
10
|
+
/* Keep reset CSS to one minified line */
|
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}
|
12
|
+
/* stylelint-enable */
|
13
|
+
|
14
|
+
html.loading,
|
15
|
+
html.loading * {
|
16
|
+
transition: none !important;
|
17
|
+
}
|
18
|
+
|
19
|
+
@import "fonts";
|
20
|
+
@import "colors";
|
21
|
+
@import "typography";
|
22
|
+
@import "base";
|
data/assets/js/site.js
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
// Add a .js utility class to <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
|
+
// Load up the theme switcher if @supports & custom properties available
|
29
|
+
if ('supports' in CSS && CSS.supports("(--foo: bar)")) {
|
30
|
+
|
31
|
+
function ThemeSwitch() {
|
32
|
+
var html = document.querySelector('html');
|
33
|
+
var icons = {
|
34
|
+
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>',
|
35
|
+
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>',
|
36
|
+
system: '<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 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2V4a8 8 0 1 0 0 16z"/></svg>'
|
37
|
+
// system: '<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 1l9.5 5.5v11L12 23l-9.5-5.5v-11L12 1zm0 2.311L4.5 7.653v8.694l7.5 4.342 7.5-4.342V7.653L12 3.311zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></svg>'
|
38
|
+
}
|
39
|
+
var mode = 'system';
|
40
|
+
var modes = ['dark','light'];
|
41
|
+
var button = document.createElement('a');
|
42
|
+
|
43
|
+
var cleanupOldPreferences = function() {
|
44
|
+
// Clean up earlier `modes` item
|
45
|
+
if (storageAvailable('localStorage')) {
|
46
|
+
localStorage.removeItem('modes');
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
var loadPreference = function() {
|
51
|
+
// read from local storage
|
52
|
+
if (storageAvailable('localStorage')) {
|
53
|
+
if (localStorage.getItem('mode')) {
|
54
|
+
mode = localStorage.getItem('mode');
|
55
|
+
} else {
|
56
|
+
mode = 'system';
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
var setModeOrder = function() {
|
62
|
+
// reverse the modes if dark mode is preferred,
|
63
|
+
// or if dark is set in preferences
|
64
|
+
if ('matchMedia' in window) {
|
65
|
+
if (window.matchMedia('(prefers-color-scheme: dark)').matches && (mode !== 'light')) {
|
66
|
+
modes.reverse(); // ['light','dark']
|
67
|
+
}
|
68
|
+
}
|
69
|
+
// system gets tacked onto the end as the last option, always
|
70
|
+
modes.push('system');
|
71
|
+
}
|
72
|
+
|
73
|
+
var storePreference = function() {
|
74
|
+
// store current mode (default or selected) in local storage
|
75
|
+
if (storageAvailable('localStorage')) {
|
76
|
+
localStorage.setItem('mode', mode);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
var setHTMLClass = function() {
|
81
|
+
html.classList.replace(mode,modes[0]);
|
82
|
+
}
|
83
|
+
|
84
|
+
var cycleModes = function() {
|
85
|
+
mode = modes.shift(); // grab the current mode from the front of the array...
|
86
|
+
modes.push(mode); // ...and push it to the end of the array
|
87
|
+
}
|
88
|
+
|
89
|
+
this.switcherButton = function() {
|
90
|
+
button.id = 'theme-button';
|
91
|
+
button.href = '#null';
|
92
|
+
button.title = 'Switch to ' + modes[0] + ' theme';
|
93
|
+
button.innerHTML = icons[modes[0]];
|
94
|
+
button.addEventListener('click', function(e) {
|
95
|
+
e.preventDefault();
|
96
|
+
// fix the class list on <html>
|
97
|
+
setHTMLClass();
|
98
|
+
cycleModes();
|
99
|
+
storePreference();
|
100
|
+
button.title = 'Switch to ' + modes[0] + ' theme';
|
101
|
+
button.innerHTML = icons[modes[0]];
|
102
|
+
});
|
103
|
+
return button;
|
104
|
+
}
|
105
|
+
|
106
|
+
// Do things on construction
|
107
|
+
cleanupOldPreferences();
|
108
|
+
loadPreference();
|
109
|
+
setModeOrder();
|
110
|
+
html.classList.add(mode);
|
111
|
+
}
|
112
|
+
|
113
|
+
var ts = new ThemeSwitch();
|
114
|
+
var ts_li = document.createElement('li');
|
115
|
+
ts_li.id = 'nav-thm';
|
116
|
+
ts_li.appendChild(ts.switcherButton());
|
117
|
+
document.querySelector('#quick-nav .nav').appendChild(ts_li);
|
118
|
+
}
|
119
|
+
|
120
|
+
|
121
|
+
function storageAvailable(type) {
|
122
|
+
try {
|
123
|
+
var storage = window[type];
|
124
|
+
var x = '__storage_test__';
|
125
|
+
storage.setItem(x, x);
|
126
|
+
storage.removeItem(x);
|
127
|
+
return true;
|
128
|
+
}
|
129
|
+
catch(e) {
|
130
|
+
return false;
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
// Move the nav to the header when there is room
|
135
|
+
// Responsive detection
|
136
|
+
function responsiveFeature(feature) {
|
137
|
+
var size = window
|
138
|
+
.getComputedStyle(document.body, ':after')
|
139
|
+
.getPropertyValue('content');
|
140
|
+
var has_feature = true;
|
141
|
+
if(size.indexOf(feature) === -1) {
|
142
|
+
has_feature = false;
|
143
|
+
}
|
144
|
+
return has_feature;
|
145
|
+
}
|
146
|
+
|
147
|
+
function ToggledNav() {
|
148
|
+
var nav = document.querySelector('#full-nav .nav');
|
149
|
+
var quick_nav = document.querySelector('#quick-nav .nav');
|
150
|
+
var full_nav = document.querySelector('#full-nav');
|
151
|
+
var thm_btn = document.querySelector('#theme-button')
|
152
|
+
var nav_thm;
|
153
|
+
var nav_nav;
|
154
|
+
var nav_items = [];
|
155
|
+
this.toggle = function() {
|
156
|
+
if (responsiveFeature('navbar') && !html.classList.contains('navbar')) {
|
157
|
+
if (thm_btn) {
|
158
|
+
nav_thm = quick_nav.removeChild(document.getElementById('nav-thm'));
|
159
|
+
}
|
160
|
+
while (nav.firstChild) {
|
161
|
+
if (nav.firstChild.tagName) {
|
162
|
+
nav_items.push(nav.removeChild(nav.firstChild));
|
163
|
+
} else {
|
164
|
+
nav.removeChild(nav.firstChild); // remove text nodes
|
165
|
+
}
|
166
|
+
}
|
167
|
+
for (var i = 0; i < nav_items.length; i++) {
|
168
|
+
quick_nav.appendChild(nav_items[i]);
|
169
|
+
}
|
170
|
+
if (thm_btn) {
|
171
|
+
quick_nav.appendChild(nav_thm);
|
172
|
+
}
|
173
|
+
nav_nav = quick_nav.removeChild(document.getElementById('nav-nav'));
|
174
|
+
html.classList.add('navbar');
|
175
|
+
full_nav.classList.add('hidden');
|
176
|
+
}
|
177
|
+
if (!responsiveFeature('navbar') && html.classList.contains('navbar')) {
|
178
|
+
if (thm_btn) {
|
179
|
+
nav_thm = quick_nav.removeChild(document.getElementById('nav-thm'));
|
180
|
+
}
|
181
|
+
quick_nav.appendChild(nav_nav);
|
182
|
+
for (var i = 0; i < nav_items.length; i++) {
|
183
|
+
nav.appendChild(nav_items[i]);
|
184
|
+
}
|
185
|
+
if (thm_btn) {
|
186
|
+
quick_nav.appendChild(nav_thm);
|
187
|
+
}
|
188
|
+
full_nav.classList.remove('hidden');
|
189
|
+
html.classList.remove('navbar');
|
190
|
+
}
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
var tn = new ToggledNav();
|
195
|
+
tn.toggle();
|
196
|
+
|
197
|
+
window.addEventListener('resize', function() {
|
198
|
+
tn.toggle();
|
199
|
+
});
|
200
|
+
|
201
|
+
|
202
|
+
// Capture and replicate the current week at the top of the calendar
|
203
|
+
if ((document.querySelector('#calendar')) && (document.querySelector('#this-week'))) {
|
204
|
+
var this_week = document.querySelector('#this-week').closest('article'); // grab this week's <article>
|
205
|
+
var current_week = this_week.cloneNode(true); // make a copy of it,
|
206
|
+
this_week.querySelector('#this-week').id = ''; // remove the original #this-week id
|
207
|
+
current_week.classList.add('current'); // add a class of current to this week's article copy
|
208
|
+
current_week.classList.remove('past'); // remove the past class
|
209
|
+
current_week.querySelector('#this-week small').innerText = "This Week";
|
210
|
+
document.querySelector('#content').prepend(current_week); // insert the copy at the top of the calendar
|
211
|
+
// if (location.hash === '') {
|
212
|
+
// location.hash = '#this-week'; // point at the new hash position; viewport should show this one
|
213
|
+
// }
|
214
|
+
var btn_show_calendar = document.createElement('a');
|
215
|
+
btn_show_calendar.id = "btn-show-calendar";
|
216
|
+
btn_show_calendar.href = "#null";
|
217
|
+
btn_show_calendar.text = "Show Previous Weeks"
|
218
|
+
btn_show_calendar.addEventListener('click', function(e) {
|
219
|
+
var past_weeks = document.querySelectorAll('article.past');
|
220
|
+
for (var week of past_weeks) {
|
221
|
+
week.classList.remove('past');
|
222
|
+
}
|
223
|
+
btn_show_calendar.remove();
|
224
|
+
e.preventDefault();
|
225
|
+
}
|
226
|
+
);
|
227
|
+
current_week.insertAdjacentElement('afterend', btn_show_calendar);
|
228
|
+
}
|
229
|
+
|
230
|
+
if ('fetch' in window) {
|
231
|
+
var namedDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
232
|
+
var namedMonths = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
|
233
|
+
|
234
|
+
var github_url = (function() {
|
235
|
+
var url = document.querySelector('#github').getAttribute('href'); // grab the href value of the repo link
|
236
|
+
if (typeof(url) !== 'undefined') {
|
237
|
+
var fragment = url.substring(url.indexOf('.com/') + 5); // find the tail end (5 = .com/)
|
238
|
+
var branch_regex = /tree/gi;
|
239
|
+
var branch_fragment = fragment.replace(branch_regex,'branches'); // replace 'tree' with 'branches'
|
240
|
+
// If not dealing with an archival branch (no replacement), append the `main` branch
|
241
|
+
if (fragment === branch_fragment) {
|
242
|
+
branch_fragment = fragment + '/branches/main';
|
243
|
+
}
|
244
|
+
return 'https://api.github.com/repos/' + branch_fragment; // return the branch API url
|
245
|
+
}
|
246
|
+
})();
|
247
|
+
|
248
|
+
function escapeHTML(str) {
|
249
|
+
var div = document.createElement('div');
|
250
|
+
div.appendChild(document.createTextNode(str));
|
251
|
+
return div.innerHTML;
|
252
|
+
}
|
253
|
+
|
254
|
+
if(typeof(github_url) !== "undefined") {
|
255
|
+
fetch(github_url)
|
256
|
+
.then(function(response) {
|
257
|
+
return response.json();
|
258
|
+
})
|
259
|
+
.then(function(data) {
|
260
|
+
var c = data.commit; // Work only with the commit property of the branch API response
|
261
|
+
var commit = {};
|
262
|
+
// Lowercase commit message's first word to run in `...to XYZ` copy:
|
263
|
+
commit.message = c.commit.message.charAt(0).toLowerCase() + c.commit.message.slice(1);
|
264
|
+
// Grab only the first line of a multiline message
|
265
|
+
commit.message = commit.message.split("\n\n")[0];
|
266
|
+
commit.url = c.html_url;
|
267
|
+
commit.stamp = c.commit.author.date;
|
268
|
+
commit.date = new Date(commit.stamp);
|
269
|
+
// Put the date in Day, Month 31 at <Local Time String> format
|
270
|
+
commit.time_string = namedDays[commit.date.getDay()] + ', ' +
|
271
|
+
namedMonths[commit.date.getMonth()] + ' ' +
|
272
|
+
commit.date.getDate() + ' at ' + commit.date.toLocaleTimeString();
|
273
|
+
// Append to footer on calendar
|
274
|
+
document.querySelector('#footer p').innerHTML +=
|
275
|
+
' Course last updated on <time datetime="' + commit.stamp + '">' + commit.time_string +
|
276
|
+
'</time> to <a id="commit-message" href="' + commit.url + '">' + escapeHTML(commit.message) + '</a>.';
|
277
|
+
});
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
window.addEventListener('keyup', function(e) {
|
282
|
+
// console.log(e.keyCode);
|
283
|
+
// Toggle the visibility of gridlines when `g` is pressed
|
284
|
+
if (e.keyCode === 71) {
|
285
|
+
document.querySelector('html').classList.toggle('g');
|
286
|
+
}
|
287
|
+
});
|
288
|
+
|
289
|
+
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);
|