diff --git a/public/_partials/layout.jade b/public/_partials/layout.jade
index 4505acb..df3b3e0 100644
--- a/public/_partials/layout.jade
+++ b/public/_partials/layout.jade
@@ -117,6 +117,8 @@ html(id=id)
include ../_partials/footer
+ include ../_partials/sw
+
script(src='/js/sticky-pagination-fixer.js')
script(src='/js/raf.js')
diff --git a/public/_partials/sw.jade b/public/_partials/sw.jade
new file mode 100644
index 0000000..afdbbe1
--- /dev/null
+++ b/public/_partials/sw.jade
@@ -0,0 +1,76 @@
+script#service-worker-template(type='text/x-template')
+ |
+ |
+
+
+div#service-worker.service-worker-wrapper
+
+
+script.
+ if ('serviceWorker' in navigator) {
+ function swExists() {
+ document.querySelector('#service-worker').innerHTML = document.querySelector('#service-worker-template').innerHTML;
+ }
+ function swToggle(status) {
+ document.querySelector('#service-worker').classList.toggle('service-worker-wrapper--show', status);
+ }
+ function swMsg(msg) {
+ document.querySelector('#service-worker-message').innerHTML = msg;
+ swToggle(true);
+ }
+ swExists();
+
+ // Very fist activation
+ function onFirstLoad() {
+ var msg = 'Service Worker magic activated!';
+ console.log(msg);
+ swMsg(msg);
+ }
+
+ function onNewUpdate() {
+ var msg = 'New update available. Refresh!';
+ console.log(msg);
+ swMsg(msg);
+ }
+
+ function onInstalled() {
+ console.log('sw installed');
+ }
+
+ function onStateChange(newWorker) {
+ if (newWorker.state === 'activated') {
+ if (navigator.serviceWorker.controller) {
+ onNewUpdate();
+ }
+ else {
+ onFirstLoad();
+ }
+ }
+ else if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
+ onInstalled();
+ }
+ }
+
+ function onUpdateFound(registration) {
+ var newWorker = registration.installing;
+
+ registration.installing.addEventListener('statechange',
+ () => onStateChange(newWorker));
+ }
+
+ navigator.serviceWorker.register('/sw.js')
+ .then(function(registration) {
+ // Registration was successful
+ registration.addEventListener('updatefound', () => onUpdateFound(registration));
+ })
+ .catch(function(err) {
+ // registration failed :(
+ console.log('ServiceWorker registration failed: ', err);
+ });
+ }
\ No newline at end of file
diff --git a/public/css/_sw.scss b/public/css/_sw.scss
new file mode 100644
index 0000000..aeaa230
--- /dev/null
+++ b/public/css/_sw.scss
@@ -0,0 +1,111 @@
+/* Service Worker
+ ========================================================================== */
+$c-bubble: #000;
+$bubble-size: 30px;
+
+.service-worker-wrapper {
+ background: rgba(0, 0, 0, .75);
+ bottom: 0;
+ position: fixed;
+ padding: 10px 0 0;
+ transform: translate3d(0, 100%, 0);
+ transition: transform ease-in-out 0.5s;
+ width: 100%;
+
+ & img {
+ display: block;
+ max-width: 320px;
+ width: 100%
+ }
+}
+.service-worker-bubble {
+ align-items: center;
+ background: $c-white;
+ border: 2px solid $c-bubble;
+ border-radius: 100%;
+ color: $c-bubble;
+ display: flex;
+ font-size: 12px;
+ height: 120px;
+ justify-content: center;
+ line-height: 1.4;
+ margin: 5px 5px 0 0;
+ padding: 1em;
+ position: absolute;
+ right: 0;
+ text-align: center;
+ top: 0;
+ width: 40%;
+
+ @include mq-min-check(360px) {
+ & {
+ font-size: inherit;
+ line-height: inherit;
+ width: 170px;
+ }
+ }
+ @include mq-min-check(400px) {
+ & {
+ left: 200px
+ }
+ }
+ @include mq-min-check(768px) {
+ & {
+ height: 150px;
+ width: 200px;
+ }
+ }
+}
+.service-worker-bubble:before,
+.service-worker-bubble:after {
+ border-color: transparent $c-bubble;
+ border-style: solid;
+ border-width: ($bubble-size / 2) $bubble-size ($bubble-size / 2) 0;
+ content: '';
+ height: $bubble-size;
+ left: -26px;
+ position: absolute;
+ top: calc(67% - 15px);
+ transform: rotate(-20deg);
+ width: $bubble-size;
+}
+.service-worker-bubble:after {
+ border-color: transparent $c-white;
+ left: -22px;
+ top: calc(67% - 16px);
+}
+.service-worker-wrapper--show {
+ transform: translate3d(0, 0, 0);
+}
+.service-worker-dismiss {
+ background: $c-blue;
+ border: 0 none;
+ color: $c-white;
+ cursor: pointer;
+ display: block;
+ padding: 0;
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 32px;
+ -webkit-appearance: none;
+
+ &:hover, &:focus {
+ background: $c-green;
+ outline: 0 none;
+ }
+
+ & svg {
+ display: block;
+ fill: currentColor;
+ height: 100%;
+ width: 100%;
+ }
+
+ @include mq-min-check(400px) {
+ & {
+ left: auto;
+ right: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/public/css/style.scss b/public/css/style.scss
index 7e10ea2..24825db 100644
--- a/public/css/style.scss
+++ b/public/css/style.scss
@@ -83,6 +83,16 @@ ul, ol, li {
display: inline-block;
vertical-align: middle;
width: 100%;
+
+ &:after {
+ background: $c-grey-subtitle;
+ bottom: 0;
+ content: '';
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ }
}
@supports (object-fit: cover) {
@@ -442,3 +452,4 @@ ul, ol, li {
@import "mq";
+@import "sw";
diff --git a/public/images/sponsors/amex.png b/public/images/sponsors/amex.png
old mode 100755
new mode 100644
diff --git a/public/images/sw.png b/public/images/sw.png
new file mode 100644
index 0000000..91884a0
Binary files /dev/null and b/public/images/sw.png differ
diff --git a/public/sw.js b/public/sw.js
index c41e162..c442927 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -1,22 +1,80 @@
/* jshint esnext: true */
+var CACHE_NAME = 'ffconf-v1.2.6';
-// It's too early to implement offline support or other niceties
+// TODO: decide which files
+var urlsToCache = [
+ '/',
+ '/workshops',
+ '/css/style.css',
+ '/js/script.js',
+ '/images/left-logic.svg'
+];
-// https://remysharp.com/2016/03/22/the-copy--paste-guide-to-your-first-service-worker
-const cacheName = 'v1::static';
-self.addEventListener('install', e => {
- e.waitUntil(
- caches.open(cacheName).then(cache => {
- return cache.addAll([
- '/',
- ]).then(() => self.skipWaiting());
- })
+self.addEventListener('install', function(event) {
+ // Perform install steps
+ event.waitUntil(
+ caches.open(CACHE_NAME)
+ .then(function(cache) {
+ return cache.addAll(urlsToCache);
+ })
+ .then(function() {
+ return self.skipWaiting();
+ })
);
});
-self.addEventListener('fetch', event => {
+
+self.addEventListener('fetch', function(event) {
event.respondWith(
- caches.match(event.request).then(res => res || fetch(event.request))
+ caches.match(event.request)
+ .then(function(response) {
+ // Cache hit - return response
+ if (response) {
+ return response;
+ }
+
+ // IMPORTANT: Clone the request. A request is a stream and
+ // can only be consumed once. Since we are consuming this
+ // once by cache and once by the browser for fetch, we need
+ // to clone the response
+ var fetchRequest = event.request.clone();
+
+ return fetch(fetchRequest).then(
+ function(response) {
+ // Check if we received a valid response
+ if(!response || response.status !== 200 || response.type !== 'basic') {
+ return response;
+ }
+
+ // IMPORTANT: Clone the response. A response is a stream
+ // and because we want the browser to consume the response
+ // as well as the cache consuming the response, we need
+ // to clone it so we have 2 stream.
+ var responseToCache = response.clone();
+
+ caches.open(CACHE_NAME)
+ .then(function(cache) {
+ cache.put(event.request, responseToCache);
+ });
+
+ return response;
+ }
+ );
+ })
+ );
+});
+
+
+self.addEventListener('activate', function(event) {
+ event.waitUntil(
+ caches.keys()
+ .then(function(keyList) {
+ return Promise.all(keyList.map(function(key) {
+ if (key !== CACHE_NAME) {
+ return caches.delete(key);
+ }
+ }));
+ })
);
});
\ No newline at end of file