jekyll-theme-open-course 0.0.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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);
|