github.com/april1989/origin-go-tools@v0.0.32/cmd/present/static/slides.js (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 var PERMANENT_URL_PREFIX = '/static/'; 6 7 var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next']; 8 9 var PM_TOUCH_SENSITIVITY = 15; 10 11 var curSlide; 12 13 /* ---------------------------------------------------------------------- */ 14 /* classList polyfill by Eli Grey 15 * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */ 16 17 if ( 18 typeof document !== 'undefined' && 19 !('classList' in document.createElement('a')) 20 ) { 21 (function(view) { 22 var classListProp = 'classList', 23 protoProp = 'prototype', 24 elemCtrProto = (view.HTMLElement || view.Element)[protoProp], 25 objCtr = Object; 26 (strTrim = 27 String[protoProp].trim || 28 function() { 29 return this.replace(/^\s+|\s+$/g, ''); 30 }), 31 (arrIndexOf = 32 Array[protoProp].indexOf || 33 function(item) { 34 for (var i = 0, len = this.length; i < len; i++) { 35 if (i in this && this[i] === item) { 36 return i; 37 } 38 } 39 return -1; 40 }), 41 // Vendors: please allow content code to instantiate DOMExceptions 42 (DOMEx = function(type, message) { 43 this.name = type; 44 this.code = DOMException[type]; 45 this.message = message; 46 }), 47 (checkTokenAndGetIndex = function(classList, token) { 48 if (token === '') { 49 throw new DOMEx( 50 'SYNTAX_ERR', 51 'An invalid or illegal string was specified' 52 ); 53 } 54 if (/\s/.test(token)) { 55 throw new DOMEx( 56 'INVALID_CHARACTER_ERR', 57 'String contains an invalid character' 58 ); 59 } 60 return arrIndexOf.call(classList, token); 61 }), 62 (ClassList = function(elem) { 63 var trimmedClasses = strTrim.call(elem.className), 64 classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []; 65 for (var i = 0, len = classes.length; i < len; i++) { 66 this.push(classes[i]); 67 } 68 this._updateClassName = function() { 69 elem.className = this.toString(); 70 }; 71 }), 72 (classListProto = ClassList[protoProp] = []), 73 (classListGetter = function() { 74 return new ClassList(this); 75 }); 76 // Most DOMException implementations don't allow calling DOMException's toString() 77 // on non-DOMExceptions. Error's toString() is sufficient here. 78 DOMEx[protoProp] = Error[protoProp]; 79 classListProto.item = function(i) { 80 return this[i] || null; 81 }; 82 classListProto.contains = function(token) { 83 token += ''; 84 return checkTokenAndGetIndex(this, token) !== -1; 85 }; 86 classListProto.add = function(token) { 87 token += ''; 88 if (checkTokenAndGetIndex(this, token) === -1) { 89 this.push(token); 90 this._updateClassName(); 91 } 92 }; 93 classListProto.remove = function(token) { 94 token += ''; 95 var index = checkTokenAndGetIndex(this, token); 96 if (index !== -1) { 97 this.splice(index, 1); 98 this._updateClassName(); 99 } 100 }; 101 classListProto.toggle = function(token) { 102 token += ''; 103 if (checkTokenAndGetIndex(this, token) === -1) { 104 this.add(token); 105 } else { 106 this.remove(token); 107 } 108 }; 109 classListProto.toString = function() { 110 return this.join(' '); 111 }; 112 113 if (objCtr.defineProperty) { 114 var classListPropDesc = { 115 get: classListGetter, 116 enumerable: true, 117 configurable: true, 118 }; 119 try { 120 objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 121 } catch (ex) { 122 // IE 8 doesn't support enumerable:true 123 if (ex.number === -0x7ff5ec54) { 124 classListPropDesc.enumerable = false; 125 objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 126 } 127 } 128 } else if (objCtr[protoProp].__defineGetter__) { 129 elemCtrProto.__defineGetter__(classListProp, classListGetter); 130 } 131 })(self); 132 } 133 /* ---------------------------------------------------------------------- */ 134 135 /* Slide movement */ 136 137 function hideHelpText() { 138 document.getElementById('help').style.display = 'none'; 139 } 140 141 function getSlideEl(no) { 142 if (no < 0 || no >= slideEls.length) { 143 return null; 144 } else { 145 return slideEls[no]; 146 } 147 } 148 149 function updateSlideClass(slideNo, className) { 150 var el = getSlideEl(slideNo); 151 152 if (!el) { 153 return; 154 } 155 156 if (className) { 157 el.classList.add(className); 158 } 159 160 for (var i in SLIDE_CLASSES) { 161 if (className != SLIDE_CLASSES[i]) { 162 el.classList.remove(SLIDE_CLASSES[i]); 163 } 164 } 165 } 166 167 function updateSlides() { 168 if (window.trackPageview) window.trackPageview(); 169 170 for (var i = 0; i < slideEls.length; i++) { 171 switch (i) { 172 case curSlide - 2: 173 updateSlideClass(i, 'far-past'); 174 break; 175 case curSlide - 1: 176 updateSlideClass(i, 'past'); 177 break; 178 case curSlide: 179 updateSlideClass(i, 'current'); 180 break; 181 case curSlide + 1: 182 updateSlideClass(i, 'next'); 183 break; 184 case curSlide + 2: 185 updateSlideClass(i, 'far-next'); 186 break; 187 default: 188 updateSlideClass(i); 189 break; 190 } 191 } 192 193 triggerLeaveEvent(curSlide - 1); 194 triggerEnterEvent(curSlide); 195 196 window.setTimeout(function() { 197 // Hide after the slide 198 disableSlideFrames(curSlide - 2); 199 }, 301); 200 201 enableSlideFrames(curSlide - 1); 202 enableSlideFrames(curSlide + 2); 203 204 updateHash(); 205 } 206 207 function prevSlide() { 208 hideHelpText(); 209 if (curSlide > 0) { 210 curSlide--; 211 212 updateSlides(); 213 } 214 215 if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide); 216 } 217 218 function nextSlide() { 219 hideHelpText(); 220 if (curSlide < slideEls.length - 1) { 221 curSlide++; 222 223 updateSlides(); 224 } 225 226 if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide); 227 } 228 229 /* Slide events */ 230 231 function triggerEnterEvent(no) { 232 var el = getSlideEl(no); 233 if (!el) { 234 return; 235 } 236 237 var onEnter = el.getAttribute('onslideenter'); 238 if (onEnter) { 239 new Function(onEnter).call(el); 240 } 241 242 var evt = document.createEvent('Event'); 243 evt.initEvent('slideenter', true, true); 244 evt.slideNumber = no + 1; // Make it readable 245 246 el.dispatchEvent(evt); 247 } 248 249 function triggerLeaveEvent(no) { 250 var el = getSlideEl(no); 251 if (!el) { 252 return; 253 } 254 255 var onLeave = el.getAttribute('onslideleave'); 256 if (onLeave) { 257 new Function(onLeave).call(el); 258 } 259 260 var evt = document.createEvent('Event'); 261 evt.initEvent('slideleave', true, true); 262 evt.slideNumber = no + 1; // Make it readable 263 264 el.dispatchEvent(evt); 265 } 266 267 /* Touch events */ 268 269 function handleTouchStart(event) { 270 if (event.touches.length == 1) { 271 touchDX = 0; 272 touchDY = 0; 273 274 touchStartX = event.touches[0].pageX; 275 touchStartY = event.touches[0].pageY; 276 277 document.body.addEventListener('touchmove', handleTouchMove, true); 278 document.body.addEventListener('touchend', handleTouchEnd, true); 279 } 280 } 281 282 function handleTouchMove(event) { 283 if (event.touches.length > 1) { 284 cancelTouch(); 285 } else { 286 touchDX = event.touches[0].pageX - touchStartX; 287 touchDY = event.touches[0].pageY - touchStartY; 288 event.preventDefault(); 289 } 290 } 291 292 function handleTouchEnd(event) { 293 var dx = Math.abs(touchDX); 294 var dy = Math.abs(touchDY); 295 296 if (dx > PM_TOUCH_SENSITIVITY && dy < (dx * 2) / 3) { 297 if (touchDX > 0) { 298 prevSlide(); 299 } else { 300 nextSlide(); 301 } 302 } 303 304 cancelTouch(); 305 } 306 307 function cancelTouch() { 308 document.body.removeEventListener('touchmove', handleTouchMove, true); 309 document.body.removeEventListener('touchend', handleTouchEnd, true); 310 } 311 312 /* Preloading frames */ 313 314 function disableSlideFrames(no) { 315 var el = getSlideEl(no); 316 if (!el) { 317 return; 318 } 319 320 var frames = el.getElementsByTagName('iframe'); 321 for (var i = 0, frame; (frame = frames[i]); i++) { 322 disableFrame(frame); 323 } 324 } 325 326 function enableSlideFrames(no) { 327 var el = getSlideEl(no); 328 if (!el) { 329 return; 330 } 331 332 var frames = el.getElementsByTagName('iframe'); 333 for (var i = 0, frame; (frame = frames[i]); i++) { 334 enableFrame(frame); 335 } 336 } 337 338 function disableFrame(frame) { 339 frame.src = 'about:blank'; 340 } 341 342 function enableFrame(frame) { 343 var src = frame._src; 344 345 if (frame.src != src && src != 'about:blank') { 346 frame.src = src; 347 } 348 } 349 350 function setupFrames() { 351 var frames = document.querySelectorAll('iframe'); 352 for (var i = 0, frame; (frame = frames[i]); i++) { 353 frame._src = frame.src; 354 disableFrame(frame); 355 } 356 357 enableSlideFrames(curSlide); 358 enableSlideFrames(curSlide + 1); 359 enableSlideFrames(curSlide + 2); 360 } 361 362 function setupInteraction() { 363 /* Clicking and tapping */ 364 365 var el = document.createElement('div'); 366 el.className = 'slide-area'; 367 el.id = 'prev-slide-area'; 368 el.addEventListener('click', prevSlide, false); 369 document.querySelector('section.slides').appendChild(el); 370 371 var el = document.createElement('div'); 372 el.className = 'slide-area'; 373 el.id = 'next-slide-area'; 374 el.addEventListener('click', nextSlide, false); 375 document.querySelector('section.slides').appendChild(el); 376 377 /* Swiping */ 378 379 document.body.addEventListener('touchstart', handleTouchStart, false); 380 } 381 382 /* Hash functions */ 383 384 function getCurSlideFromHash() { 385 var slideNo = parseInt(location.hash.substr(1)); 386 387 if (slideNo) { 388 curSlide = slideNo - 1; 389 } else { 390 curSlide = 0; 391 } 392 } 393 394 function updateHash() { 395 location.replace('#' + (curSlide + 1)); 396 } 397 398 /* Event listeners */ 399 400 function handleBodyKeyDown(event) { 401 // If we're in a code element, only handle pgup/down. 402 var inCode = event.target.classList.contains('code'); 403 404 switch (event.keyCode) { 405 case 78: // 'N' opens presenter notes window 406 if (!inCode && notesEnabled) toggleNotesWindow(); 407 break; 408 case 72: // 'H' hides the help text 409 case 27: // escape key 410 if (!inCode) hideHelpText(); 411 break; 412 413 case 39: // right arrow 414 case 13: // Enter 415 case 32: // space 416 if (inCode) break; 417 case 34: // PgDn 418 nextSlide(); 419 event.preventDefault(); 420 break; 421 422 case 37: // left arrow 423 case 8: // Backspace 424 if (inCode) break; 425 case 33: // PgUp 426 prevSlide(); 427 event.preventDefault(); 428 break; 429 430 case 40: // down arrow 431 if (inCode) break; 432 nextSlide(); 433 event.preventDefault(); 434 break; 435 436 case 38: // up arrow 437 if (inCode) break; 438 prevSlide(); 439 event.preventDefault(); 440 break; 441 } 442 } 443 444 function scaleSmallViewports() { 445 var el = document.querySelector('section.slides'); 446 var transform = ''; 447 var sWidthPx = 1250; 448 var sHeightPx = 750; 449 var sAspectRatio = sWidthPx / sHeightPx; 450 var wAspectRatio = window.innerWidth / window.innerHeight; 451 452 if (wAspectRatio <= sAspectRatio && window.innerWidth < sWidthPx) { 453 transform = 'scale(' + window.innerWidth / sWidthPx + ')'; 454 } else if (window.innerHeight < sHeightPx) { 455 transform = 'scale(' + window.innerHeight / sHeightPx + ')'; 456 } 457 el.style.transform = transform; 458 } 459 460 function addEventListeners() { 461 document.addEventListener('keydown', handleBodyKeyDown, false); 462 var resizeTimeout; 463 window.addEventListener('resize', function() { 464 // throttle resize events 465 window.clearTimeout(resizeTimeout); 466 resizeTimeout = window.setTimeout(function() { 467 resizeTimeout = null; 468 scaleSmallViewports(); 469 }, 50); 470 }); 471 472 // Force reset transform property of section.slides when printing page. 473 // Use both onbeforeprint and matchMedia for compatibility with different browsers. 474 var beforePrint = function() { 475 var el = document.querySelector('section.slides'); 476 el.style.transform = ''; 477 }; 478 window.onbeforeprint = beforePrint; 479 if (window.matchMedia) { 480 var mediaQueryList = window.matchMedia('print'); 481 mediaQueryList.addListener(function(mql) { 482 if (mql.matches) beforePrint(); 483 }); 484 } 485 } 486 487 /* Initialization */ 488 489 function addFontStyle() { 490 var el = document.createElement('link'); 491 el.rel = 'stylesheet'; 492 el.type = 'text/css'; 493 el.href = 494 '//fonts.googleapis.com/css?family=' + 495 'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono'; 496 497 document.body.appendChild(el); 498 } 499 500 function addGeneralStyle() { 501 var el = document.createElement('link'); 502 el.rel = 'stylesheet'; 503 el.type = 'text/css'; 504 el.href = PERMANENT_URL_PREFIX + 'styles.css'; 505 document.body.appendChild(el); 506 507 var el = document.createElement('meta'); 508 el.name = 'viewport'; 509 el.content = 'width=device-width,height=device-height,initial-scale=1'; 510 document.querySelector('head').appendChild(el); 511 512 var el = document.createElement('meta'); 513 el.name = 'apple-mobile-web-app-capable'; 514 el.content = 'yes'; 515 document.querySelector('head').appendChild(el); 516 517 scaleSmallViewports(); 518 } 519 520 function handleDomLoaded() { 521 slideEls = document.querySelectorAll('section.slides > article'); 522 523 setupFrames(); 524 525 addFontStyle(); 526 addGeneralStyle(); 527 addEventListeners(); 528 529 updateSlides(); 530 531 setupInteraction(); 532 533 if ( 534 window.location.hostname == 'localhost' || 535 window.location.hostname == '127.0.0.1' || 536 window.location.hostname == '::1' 537 ) { 538 hideHelpText(); 539 } 540 541 document.body.classList.add('loaded'); 542 543 setupNotesSync(); 544 } 545 546 function initialize() { 547 getCurSlideFromHash(); 548 549 if (window['_DEBUG']) { 550 PERMANENT_URL_PREFIX = '../'; 551 } 552 553 if (window['_DCL']) { 554 handleDomLoaded(); 555 } else { 556 document.addEventListener('DOMContentLoaded', handleDomLoaded, false); 557 } 558 } 559 560 // If ?debug exists then load the script relative instead of absolute 561 if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) { 562 document.addEventListener( 563 'DOMContentLoaded', 564 function() { 565 // Avoid missing the DomContentLoaded event 566 window['_DCL'] = true; 567 }, 568 false 569 ); 570 571 window['_DEBUG'] = true; 572 var script = document.createElement('script'); 573 script.type = 'text/javascript'; 574 script.src = '../slides.js'; 575 var s = document.getElementsByTagName('script')[0]; 576 s.parentNode.insertBefore(script, s); 577 578 // Remove this script 579 s.parentNode.removeChild(s); 580 } else { 581 initialize(); 582 } 583 584 /* Synchronize windows when notes are enabled */ 585 586 function setupNotesSync() { 587 if (!notesEnabled) return; 588 589 function setupPlayResizeSync() { 590 var out = document.getElementsByClassName('output'); 591 for (var i = 0; i < out.length; i++) { 592 $(out[i]).bind('resize', function(event) { 593 if ($(event.target).hasClass('ui-resizable')) { 594 localStorage.setItem('play-index', i); 595 localStorage.setItem('output-style', out[i].style.cssText); 596 } 597 }); 598 } 599 } 600 function setupPlayCodeSync() { 601 var play = document.querySelectorAll('div.playground'); 602 for (var i = 0; i < play.length; i++) { 603 play[i].addEventListener('input', inputHandler, false); 604 605 function inputHandler(e) { 606 localStorage.setItem('play-index', i); 607 localStorage.setItem('play-code', e.target.innerHTML); 608 } 609 } 610 } 611 612 setupPlayCodeSync(); 613 setupPlayResizeSync(); 614 localStorage.setItem(destSlideKey(), curSlide); 615 window.addEventListener('storage', updateOtherWindow, false); 616 } 617 618 // An update to local storage is caught only by the other window 619 // The triggering window does not handle any sync actions 620 function updateOtherWindow(e) { 621 // Ignore remove storage events which are not meant to update the other window 622 var isRemoveStorageEvent = !e.newValue; 623 if (isRemoveStorageEvent) return; 624 625 var destSlide = localStorage.getItem(destSlideKey()); 626 while (destSlide > curSlide) { 627 nextSlide(); 628 } 629 while (destSlide < curSlide) { 630 prevSlide(); 631 } 632 633 updatePlay(e); 634 updateNotes(); 635 }