// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. var PERMANENT_URL_PREFIX = './static/present/'; var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next']; var PM_TOUCH_SENSITIVITY = 15; var curSlide; /* ---------------------------------------------------------------------- */ /* classList polyfill by Eli Grey * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */ if ( typeof document !== 'undefined' && !('classList' in document.createElement('a')) ) { (function(view) { var classListProp = 'classList', protoProp = 'prototype', elemCtrProto = (view.HTMLElement || view.Element)[protoProp], objCtr = Object; (strTrim = String[protoProp].trim || function() { return this.replace(/^\s+|\s+$/g, ''); }), (arrIndexOf = Array[protoProp].indexOf || function(item) { for (var i = 0, len = this.length; i < len; i++) { if (i in this && this[i] === item) { return i; } } return -1; }), // Vendors: please allow content code to instantiate DOMExceptions (DOMEx = function(type, message) { this.name = type; this.code = DOMException[type]; this.message = message; }), (checkTokenAndGetIndex = function(classList, token) { if (token === '') { throw new DOMEx( 'SYNTAX_ERR', 'An invalid or illegal string was specified' ); } if (/\s/.test(token)) { throw new DOMEx( 'INVALID_CHARACTER_ERR', 'String contains an invalid character' ); } return arrIndexOf.call(classList, token); }), (ClassList = function(elem) { var trimmedClasses = strTrim.call(elem.className), classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []; for (var i = 0, len = classes.length; i < len; i++) { this.push(classes[i]); } this._updateClassName = function() { elem.className = this.toString(); }; }), (classListProto = ClassList[protoProp] = []), (classListGetter = function() { return new ClassList(this); }); // Most DOMException implementations don't allow calling DOMException's toString() // on non-DOMExceptions. Error's toString() is sufficient here. DOMEx[protoProp] = Error[protoProp]; classListProto.item = function(i) { return this[i] || null; }; classListProto.contains = function(token) { token += ''; return checkTokenAndGetIndex(this, token) !== -1; }; classListProto.add = function(token) { token += ''; if (checkTokenAndGetIndex(this, token) === -1) { this.push(token); this._updateClassName(); } }; classListProto.remove = function(token) { token += ''; var index = checkTokenAndGetIndex(this, token); if (index !== -1) { this.splice(index, 1); this._updateClassName(); } }; classListProto.toggle = function(token) { token += ''; if (checkTokenAndGetIndex(this, token) === -1) { this.add(token); } else { this.remove(token); } }; classListProto.toString = function() { return this.join(' '); }; if (objCtr.defineProperty) { var classListPropDesc = { get: classListGetter, enumerable: true, configurable: true, }; try { objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } catch (ex) { // IE 8 doesn't support enumerable:true if (ex.number === -0x7ff5ec54) { classListPropDesc.enumerable = false; objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } } } else if (objCtr[protoProp].__defineGetter__) { elemCtrProto.__defineGetter__(classListProp, classListGetter); } })(self); } /* ---------------------------------------------------------------------- */ /* Slide movement */ function hideHelpText() { document.getElementById('help').style.display = 'none'; } function getSlideEl(no) { if (no < 0 || no >= slideEls.length) { return null; } else { return slideEls[no]; } } function updateSlideClass(slideNo, className) { var el = getSlideEl(slideNo); if (!el) { return; } if (className) { el.classList.add(className); } for (var i in SLIDE_CLASSES) { if (className != SLIDE_CLASSES[i]) { el.classList.remove(SLIDE_CLASSES[i]); } } } function updateSlides() { if (window.trackPageview) window.trackPageview(); for (var i = 0; i < slideEls.length; i++) { switch (i) { case curSlide - 2: updateSlideClass(i, 'far-past'); break; case curSlide - 1: updateSlideClass(i, 'past'); break; case curSlide: updateSlideClass(i, 'current'); break; case curSlide + 1: updateSlideClass(i, 'next'); break; case curSlide + 2: updateSlideClass(i, 'far-next'); break; default: updateSlideClass(i); break; } } triggerLeaveEvent(curSlide - 1); triggerEnterEvent(curSlide); window.setTimeout(function() { // Hide after the slide disableSlideFrames(curSlide - 2); }, 301); enableSlideFrames(curSlide - 1); enableSlideFrames(curSlide + 2); updateHash(); } function prevSlide() { hideHelpText(); if (curSlide > 0) { curSlide--; updateSlides(); } if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide); } function nextSlide() { hideHelpText(); if (curSlide < slideEls.length - 1) { curSlide++; updateSlides(); } if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide); } /* Slide events */ function triggerEnterEvent(no) { var el = getSlideEl(no); if (!el) { return; } var onEnter = el.getAttribute('onslideenter'); if (onEnter) { new Function(onEnter).call(el); } var evt = document.createEvent('Event'); evt.initEvent('slideenter', true, true); evt.slideNumber = no + 1; // Make it readable el.dispatchEvent(evt); } function triggerLeaveEvent(no) { var el = getSlideEl(no); if (!el) { return; } var onLeave = el.getAttribute('onslideleave'); if (onLeave) { new Function(onLeave).call(el); } var evt = document.createEvent('Event'); evt.initEvent('slideleave', true, true); evt.slideNumber = no + 1; // Make it readable el.dispatchEvent(evt); } /* Touch events */ function handleTouchStart(event) { if (event.touches.length == 1) { touchDX = 0; touchDY = 0; touchStartX = event.touches[0].pageX; touchStartY = event.touches[0].pageY; document.body.addEventListener('touchmove', handleTouchMove, true); document.body.addEventListener('touchend', handleTouchEnd, true); } } function handleTouchMove(event) { if (event.touches.length > 1) { cancelTouch(); } else { touchDX = event.touches[0].pageX - touchStartX; touchDY = event.touches[0].pageY - touchStartY; event.preventDefault(); } } function handleTouchEnd(event) { var dx = Math.abs(touchDX); var dy = Math.abs(touchDY); if (dx > PM_TOUCH_SENSITIVITY && dy < (dx * 2) / 3) { if (touchDX > 0) { prevSlide(); } else { nextSlide(); } } cancelTouch(); } function cancelTouch() { document.body.removeEventListener('touchmove', handleTouchMove, true); document.body.removeEventListener('touchend', handleTouchEnd, true); } /* Preloading frames */ function disableSlideFrames(no) { var el = getSlideEl(no); if (!el) { return; } var frames = el.getElementsByTagName('iframe'); for (var i = 0, frame; (frame = frames[i]); i++) { disableFrame(frame); } } function enableSlideFrames(no) { var el = getSlideEl(no); if (!el) { return; } var frames = el.getElementsByTagName('iframe'); for (var i = 0, frame; (frame = frames[i]); i++) { enableFrame(frame); } } function disableFrame(frame) { frame.src = 'about:blank'; } function enableFrame(frame) { var src = frame._src; if (frame.src != src && src != 'about:blank') { frame.src = src; } } function setupFrames() { var frames = document.querySelectorAll('iframe'); for (var i = 0, frame; (frame = frames[i]); i++) { frame._src = frame.src; disableFrame(frame); } enableSlideFrames(curSlide); enableSlideFrames(curSlide + 1); enableSlideFrames(curSlide + 2); } function setupInteraction() { /* Clicking and tapping */ var el = document.createElement('div'); el.className = 'slide-area'; el.id = 'prev-slide-area'; el.addEventListener('click', prevSlide, false); document.querySelector('section.slides').appendChild(el); var el = document.createElement('div'); el.className = 'slide-area'; el.id = 'next-slide-area'; el.addEventListener('click', nextSlide, false); document.querySelector('section.slides').appendChild(el); /* Swiping */ document.body.addEventListener('touchstart', handleTouchStart, false); } /* Hash functions */ function getCurSlideFromHash() { var slideNo = parseInt(location.hash.substr(1)); if (slideNo) { curSlide = slideNo - 1; } else { curSlide = 0; } } function updateHash() { location.replace('#' + (curSlide + 1)); } /* Event listeners */ function handleBodyKeyDown(event) { // If we're in a code element, only handle pgup/down. var inCode = event.target.classList.contains('code'); switch (event.keyCode) { case 78: // 'N' opens presenter notes window if (!inCode && notesEnabled) toggleNotesWindow(); break; case 72: // 'H' hides the help text case 27: // escape key if (!inCode) hideHelpText(); break; case 39: // right arrow case 13: // Enter case 32: // space if (inCode) break; case 34: // PgDn nextSlide(); event.preventDefault(); break; case 37: // left arrow case 8: // Backspace if (inCode) break; case 33: // PgUp prevSlide(); event.preventDefault(); break; case 40: // down arrow if (inCode) break; nextSlide(); event.preventDefault(); break; case 38: // up arrow if (inCode) break; prevSlide(); event.preventDefault(); break; } } function scaleSmallViewports() { var el = document.querySelector('section.slides'); var transform = ''; var sWidthPx = 1250; var sHeightPx = 750; var sAspectRatio = sWidthPx / sHeightPx; var wAspectRatio = window.innerWidth / window.innerHeight; if (wAspectRatio <= sAspectRatio && window.innerWidth < sWidthPx) { transform = 'scale(' + window.innerWidth / sWidthPx + ')'; } else if (window.innerHeight < sHeightPx) { transform = 'scale(' + window.innerHeight / sHeightPx + ')'; } el.style.transform = transform; } function addEventListeners() { document.addEventListener('keydown', handleBodyKeyDown, false); var resizeTimeout; window.addEventListener('resize', function() { // throttle resize events window.clearTimeout(resizeTimeout); resizeTimeout = window.setTimeout(function() { resizeTimeout = null; scaleSmallViewports(); }, 50); }); // Force reset transform property of section.slides when printing page. // Use both onbeforeprint and matchMedia for compatibility with different browsers. var beforePrint = function() { var el = document.querySelector('section.slides'); el.style.transform = ''; }; window.onbeforeprint = beforePrint; if (window.matchMedia) { var mediaQueryList = window.matchMedia('print'); mediaQueryList.addListener(function(mql) { if (mql.matches) beforePrint(); }); } } /* Initialization */ function addFontStyle() { var el = document.createElement('link'); el.rel = 'stylesheet'; el.type = 'text/css'; //el.href = // '//fonts.googleapis.com/css?family=' + // 'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono'; el.href = PERMANENT_URL_PREFIX + 'font.css'; document.body.appendChild(el); } function addGeneralStyle() { var el = document.createElement('link'); el.rel = 'stylesheet'; el.type = 'text/css'; el.href = PERMANENT_URL_PREFIX + 'styles.css'; document.body.appendChild(el); var el = document.createElement('meta'); el.name = 'viewport'; el.content = 'width=device-width,height=device-height,initial-scale=1'; document.querySelector('head').appendChild(el); var el = document.createElement('meta'); el.name = 'apple-mobile-web-app-capable'; el.content = 'yes'; document.querySelector('head').appendChild(el); scaleSmallViewports(); } function handleDomLoaded() { slideEls = document.querySelectorAll('section.slides > article'); setupFrames(); addFontStyle(); addGeneralStyle(); addEventListeners(); updateSlides(); setupInteraction(); if ( window.location.hostname == 'localhost' || window.location.hostname == '127.0.0.1' || window.location.hostname == '::1' ) { hideHelpText(); } document.body.classList.add('loaded'); setupNotesSync(); } function initialize() { getCurSlideFromHash(); if (window['_DEBUG']) { PERMANENT_URL_PREFIX = '../'; } if (window['_DCL']) { handleDomLoaded(); } else { document.addEventListener('DOMContentLoaded', handleDomLoaded, false); } } // If ?debug exists then load the script relative instead of absolute if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) { document.addEventListener( 'DOMContentLoaded', function() { // Avoid missing the DomContentLoaded event window['_DCL'] = true; }, false ); window['_DEBUG'] = true; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = PERMANENT_URL_PREFIX + 'slides.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(script, s); // Remove this script s.parentNode.removeChild(s); } else { initialize(); } /* Synchronize windows when notes are enabled */ function setupNotesSync() { if (!notesEnabled) return; localStorage.setItem(destSlideKey(), curSlide); window.addEventListener('storage', updateOtherWindow, false); } // An update to local storage is caught only by the other window // The triggering window does not handle any sync actions function updateOtherWindow(e) { // Ignore remove storage events which are not meant to update the other window var isRemoveStorageEvent = !e.newValue; if (isRemoveStorageEvent) return; var destSlide = localStorage.getItem(destSlideKey()); while (destSlide > curSlide) { nextSlide(); } while (destSlide < curSlide) { prevSlide(); } updateNotes(); }