|  | // 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/'; | 
|  |  | 
|  | 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'; | 
|  |  | 
|  | 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 = '../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; | 
|  |  | 
|  | function setupPlayResizeSync() { | 
|  | var out = document.getElementsByClassName('output'); | 
|  | for (var i = 0; i < out.length; i++) { | 
|  | $(out[i]).bind('resize', function(event) { | 
|  | if ($(event.target).hasClass('ui-resizable')) { | 
|  | localStorage.setItem('play-index', i); | 
|  | localStorage.setItem('output-style', out[i].style.cssText); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | function setupPlayCodeSync() { | 
|  | var play = document.querySelectorAll('div.playground'); | 
|  | for (var i = 0; i < play.length; i++) { | 
|  | play[i].addEventListener('input', inputHandler, false); | 
|  |  | 
|  | function inputHandler(e) { | 
|  | localStorage.setItem('play-index', i); | 
|  | localStorage.setItem('play-code', e.target.innerHTML); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | setupPlayCodeSync(); | 
|  | setupPlayResizeSync(); | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | updatePlay(e); | 
|  | updateNotes(); | 
|  | } |