GoPLS Viewer

Home|gopls/cmd/present/static/slides.js
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
5var PERMANENT_URL_PREFIX = '/static/';
6
7var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next'];
8
9var PM_TOUCH_SENSITIVITY = 15;
10
11var curSlide;
12
13/* ---------------------------------------------------------------------- */
14/* classList polyfill by Eli Grey
15 * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */
16
17if (
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
137function hideHelpText() {
138  document.getElementById('help').style.display = 'none';
139}
140
141function getSlideEl(no) {
142  if (no < 0 || no >= slideEls.length) {
143    return null;
144  } else {
145    return slideEls[no];
146  }
147}
148
149function 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
167function 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
207function prevSlide() {
208  hideHelpText();
209  if (curSlide > 0) {
210    curSlide--;
211
212    updateSlides();
213  }
214
215  if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
216}
217
218function 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
231function 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
249function 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
269function 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
282function 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
292function 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
307function cancelTouch() {
308  document.body.removeEventListener('touchmove', handleTouchMove, true);
309  document.body.removeEventListener('touchend', handleTouchEnd, true);
310}
311
312/* Preloading frames */
313
314function 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
326function 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
338function disableFrame(frame) {
339  frame.src = 'about:blank';
340}
341
342function enableFrame(frame) {
343  var src = frame._src;
344
345  if (frame.src != src && src != 'about:blank') {
346    frame.src = src;
347  }
348}
349
350function 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
362function 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
384function 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
394function updateHash() {
395  location.replace('#' + (curSlide + 1));
396}
397
398/* Event listeners */
399
400function 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
444function 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
460function 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
489function 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
500function 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
520function 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
546function 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
561if (!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
586function 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
620function 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}
636
MembersX
Members
X